Code Day's Night

ichikawayのブログ

Stripeのクレジットカード入力フォームで郵便番号入力を不要にする

stripe.jsを使ったフォームを構築すると、例えばVISAのカード番号を入力した時に動的に郵便番号フィールドがでてきて、そこも必須入力欄となる。

この場合、下記のようにstripe.elements()のelements.create()のオプションに、
hidePostalCode trueを渡すと郵便番号欄はでなくなる。

var card = elements.create('card', {style: style, hidePostalCode: true});

 

このドキュメントのcardタイプのオプション指定欄に説明がある。
https://stripe.com/docs/stripe-js/reference#elements-create

 

ここにstripeのエレメントサンプル画面とコード例がある
https://stripe.dev/elements-examples/

PHPerKaigi2019で自作ファミコンエミュレータの話をしてきたら最高だった(前編)

PHPerKaigi2019で登壇してきました。全体の感想などは後編に書きます。

カンファレンスで発表するのは3年ぶりぐらい。今回はPHPのファミコンエミュレータ実装をGolangで写経してた時の話をしてきました。

 

ファミコンエミュレータはPHPやGolang, Rustなどたくさんの実装があり、厳密に決まってるCPUやメモリの仕様を元に実装されてます。その実装を通してコンピュータアーキテクチャの理解が進む、低レイヤーの入り口に立てる、という柱を立てて発表しました。PHPで読めて、コンピュータアーキテクチャが学べるって最高じゃないですか?

www.slideshare.net

 

登壇後は、ファミコンエミュレータに興味を持った、作ってみたいというフィードバックを頂きました。今回の発表のゴールに到達できたかと思います。

 

議論や交流が活発、これがPHPerKaigiだ!

PHPerKaigiでは、登壇後も別のホールで椅子に座って、質問やフィードバックがあれば気軽に話にいけるAsk the Speakerというシステムがあり、そこでファミコンのソフトを作ってたさっぴーさんとお話できました。

土曜日の懇親会(茶会)では、これまたファミコンのソフト制作に関わっていた郡山さんの昔話が聞けたり、それを起点にkoyhogeさんの昔話も聞けたり、最高の体験になりました。

ファミコンのソフト作ってた人が二人も会場にいるって、考えるとすごいですね。

 

PHPerKaigiは、正しいこと、完璧なことを一方的に話して終わる場ではなく、議論や交流ができることが重要というスタイルになっているので、発表者としては非常にありがたい場になります。たとえ間違ったところがあったとしても、それは問題ではなく、その後に議論ができて正しくなれば良いだけなのです。最高ですね。

そもそも、この内容で応募する時点で、私より詳しいPHPのファミコンエミュレータ作者がいるのがわかってますし、マイコンやアセンブラをバリバリやってたという方もちらほらいるので、発表するまですごく不安でしたが、発表することで私より詳しい人から色々なお話がきけて、それが共有できるのであれば、こんな素晴らしいことはありませんね。

 

登壇後も、Twitterでここがちょっと間違ってるよというツイートもありました。

色々な知見をいただけて最高でした。 
(エミュレータを作り始めた頃はハードウェアの知識がなさすぎて、このような意見をもらっても理解できなかったかもしれない)

 

スカイラブハリケーン

もう一つ、最高な体験がありました。

私の後のセッションで、 @DQNEO さんが「コンパイラ作りの魅力を語る」という話をしていました。

コンパイラ作りの魅力を語る by DQNEO | プロポーザル | PHPerKaigi 2019 - fortee.jp

私が作ってるファミコンエミュレータはGo言語で実装しています。@DQNEOさんは自作のGoコンパイラを書いてます。もうわかりますね?

懇親会で @DQNEOさんから、彼のコンパイラで私のファミコンエミュレータをコンパイルして動かしてみましょう、という夢の提案がありました。熱い!

 内部でパッケージを分けてたため、残念ながらうまくコンパイルできませんでした。まだそこが未実装だったためです。今後、そこが実装されたら動きそうなのでワクワクしますね。

こんな素敵なコラボレーションがあるとは、もう大興奮ですね。

 進捗がありました!

 

前編のまとめ

PHPerKaigiでは、議論や交流の場がうまく設けられていて、発表者としては最高の体験ができました。

私は低レイヤーの知識が乏しく、ファミコンエミュレータだって移植してみただけで、完全に理解している状態ではありませんでした。ですので登壇するまでは不安で、何度もスライド変えたり発表リハーサルしたりしました。

いま思うのは、完璧な状態や強い状態になってから登壇するよりも、ある程度の知見や経験がたまり、伝えたいという想いがあれば、とにかくピッチに立つことが大事なんだなと感じます。とくにPHPerKaigiのようなスタイルのカンファレンスには。

もし来年PHPerKaigi2020があるのであれば、絶対に登壇者応募するのをオススメします。

後半へつづく・・

 

URLにアンカーの#(シャープ)がある場合に送信されるGETリクエスト

URLに含まれる#(シャープ)は、ページ内リンクアンカー、ハッシュ、フラグメントなどの名称があるが、#が入る場合のGETリクエストはどのようになっているか。

例えば

http://example.com/foo/bar/#/1

というようなURLにGETリクエストする場合、ブラウザは#以降の文字列を付けずに

GET /foo/bar/

というURLパスでリクエストを送る。

#以降の文字列はレスポンス取得後にJavaScriptやブラウザが使うものとして扱われる。

 

curlコマンドで上記のようなURLにリクエストを送信し、tcpdumpして確認。

tcpdump -A dst host xxx.xxx.xxx.xxx

 

なるほどわからない!と実装を繰り返して楽しむ自作ファミコンエミュレータの世界

最近、趣味でファミコンエミュレータをGo言語で実装しています。

github.com

 

世の中にはたくさんのファミコンエミュレータ実装があり、golangだけでもいくつもあるため何で同じ言語で実装するのかと言われると、楽しそうだからとしか言いようがありませんが、趣味の話なので気にしないようにしましょう :)

私は下記のPHP実装のファミコンエミュレータを読んで、PHPなら読める・・読めるぞ!と思って写経しています。せっかくなので勉強し始めたgolangで実装をしています。

www.hasegawa-tomoki.com

 

ここ1ヶ月ぐらい、毎朝20分ほどファミコンの仕様を読みつつPHPのコードを読みつつgolangに移植しているのですが、まだHello Worldが出力できていませんw

そんな時間のかかるHello Worldですが、そこにたどり着くまでにファミコンのROMデータのロード、CPU命令の実装、PPU命令の実装までをやる必要があります。気の遠くなる話ですね・・だが楽しい! プラモデルを毎日コツコツ作っている感覚です。

 

今はPHP実装をgolangに移植してるのですが、PHPの場合は8bit intでも16bit intでもすべてint型になっています。そのため細かい型のところや、書いている内容がわからない場合は他の言語実装も読みます。一番読んでるのはRust実装のこれです。

github.com

 

Rustの文法は知らないし、コードも読んだことなかったのですが、問題なく読めました。ファミコンエミュレータに使われるコードはビットの演算やデータの管理(クラス・構造体)、バイト列の操作、条件分岐がメインになるため基本的な言語文法のみが使われているためです。

ちなみに、ファミコンは海外ではNESと呼ばれていますので、検索する場合はNES Emulator というキーワードを組み合わせると便利です。

 

golangで書いてても使っているのは、構造体、レシーバ、ビット演算、条件分岐、byte操作ぐらいです。ですので、新しいプログラミング言語を覚えるのにファミコンエミュレータを実装するのは良い練習になります。

CPUやメモリのこと、アセンブラのこと、写経しながらこれらも学んでいけますので、ハードに近い場所での操作がどうなっているか体験できるのは面白いですね。いままでアセンブラとか避けてきていたので・・

 

Hello Worldを出力するまでは、まずはこの記事を読むべしという感じです。

qiita.com

私はもう何回も読んで、毎回、なるほどわからん!って感じを繰り返してます。 今はプリンタで印刷してカバンにいれて暇な時に読んでます。
それでもわからないまま写経しながら実装してると、だんだんとわからん!の範囲が小さくなっていきます。この感覚は楽しいですね。

1ヶ月以上も実装しつつ、わからないことが多いですが、最近このように分からないものに長い時間をかけて挑戦することがしたくて、ファミコンエミュレータは良い題材となってます。

 

dockerコマンドでよく使うもの amazonlinuxベース

普段はdocker-composeで操作していても、たまに使い捨てコンテナを扱いたい時がある。
そんな時にdockerコマンドだけで済ませる方法。
dockerイメージの作成、コンテナ起動、コンテナのシェル操作、コンテナ削除の説明。

Dockerfile

amazonlinuxの最新を取得して、最低限のコマンドを入れる

FROM amazonlinux:latest

RUN yum update -y && yum install -y \
less \
procps \
net-tools 

build

Dockerfileを置いて、それを元にタグ名 ichi/awslinux を起動

docker build -t ichi/awslinux:1.0 .

コンテナ起動

nameオプションでコンテナの名前をつける。 最後にプロセスが終了しないようにtail -fを使ってコンテナを起動し続ける。

docker run -d --name ichi-awstest ichi/awslinux:1.0  tail -f /dev/null

docker ps

動いているコンテナを表示するには、 docker ps を使う。
ビルドして動作していない、もしくは動作終了したコンテナまで表示するには docker ps -a を使う。

コンテナにログイン

コンテナ名を指定してbashを起動する。これでコンテナの中のシェルが操作できる。

docker exec -it ichi-awstest bash

コンテナ削除

コンテナを停止し、docker rmでコンテナ削除。 image rmでビルドしたイメージを削除。

docker stop ichi-awstest
docker rm ichi-awstest
docker images
docker image rm ichi/awslinux:1.0

Dockerfileを使わない場合

docker pull node:8.9.4-alpine

docker run -d --name node-alpine -it node:8.9.4-alpine

上記--nameでコンテナ名を指定していない場合は自動で付与されるため
docker ps のNAMESにある名前を使う

docker exec -it node-alpine /bin/sh

SQLでdatetimeの差を秒の整数で取得

MySQLのDATETIMEカラムの差を秒で取得する方法

startとendというカラムがDATETIMEだった場合、時間の差は

select TIMEDIFF(`end`, `start`) 

で取得できる。この時、 00:12:13のようなフォーマットで返る

これをさらにtime_to_sec関数で整数値の秒にする。

select TIME_TO_SEC( TIMEDIFF(`end`, `start`) ) 

この時間の平均が出したければ、下記のようにすればよい

select AVG( TIME_TO_SEC( TIMEDIFF(`end`, `start`) ) ) 

CakePHPではDBカラムのSQLインジェクションに注意!

CakePHPアドベントカレンダー12日目の記事です。

市川@cakephperです。ちゃんとCakePHP使ってますよ!
丹精込めてVAddyを作ってます。
最近コンビニで常陸野ネストビール セッションIPAが売っててテンションが上がりますね。

CakePHP2,3のDBカラム名の扱い

CakePHP2の公式ドキュメントでは下記の注意書きがあります。

CakePHP は、配列の値部分のみエスケープします。決して キーにユーザーデータをセットしないでください。SQLインジェクションの脆弱性になります。

CakePHPのORMでWHERE句を生成する場合に、カラム名やSQLスニペットはエスケープされません。
あまりないパターンかもしれませんが、ユーザから送信されたリクエストデータの配列をそのまま条件に指定している場合はSQLインジェクションにつながります。

CakePHP3 公式ドキュメントでも同じく注意書きがあります。(該当箇所はリンク先の下の方の赤い枠で囲まれた箇所です)

具体例

例えば何かしらの検索条件のフォームがあったとして、ユーザが条件を選択して送信した
$this->request->data['Model']

['type' => 1, 'keyword' => 'あいうえお'];

であった場合、この送信された配列データをそのまま下記のようにfindメソッドの条件に渡している場合は危険です。

$this->Model->find('all', ['conditions' => $this->request->data['Model']]);

このように送信されたデータが連想配列になっていて、フォームの検索項目を増やしていけば自然と検索条件も増えていくため一見便利なように見えますが、配列のキーは何もエスケープされずにWHERE句のカラム名として使われます。
受け取ったリクエストデータの配列のキーが改ざんされた場合はSQLインジェクションの脆弱性が発生します。

CakePHP3の場合は、where()メソッドに渡す配列のキーがエスケープされずそのまま渡されますので同様に注意が必要です。

対応策

ユーザからのリクエストデータが配列の場合は、キーを信用せず値だけ使うようにしましょう。

$condition['type'] = $this->request->data('Model.type');
$condition['keyword'] = $this->request->data('Model.keyword');

追記(2017/12/26)

CakePHP2の、ControllerクラスのpostConditions()にこのSQLインジェクションの脆弱性があり、2017/12/16に修正されています。
Securityコンポーネントを使わずに、postConditions()だけを使ってfindの条件(conditions)を作成してfind()に渡している場合は危険ですのでご注意ください。
Make postConditions() less permissive. by markstory · Pull Request #11526 · cakephp/cakephp · GitHub

基本的には信用するフィールドのみ受け付けるか、最新のCakePHP2を使うか、私が作っているSecurePHPライブラリのように配列のキーで受け付ける文字列を制限してNGのものは削除するのが良いかと思います。 GitHub - ichikaway/securephp: SecurePHP is the library for security

Webアプリケーションにおけるタイミング攻撃の実現性 リモートから簡単にパスワードクラックできるのか?

これはPHPアドベントカレンダー2017 8日目の記事です。

今年もBrewDog ホッピークリスマスIPAの季節になりましたね。美味しいクラフトビールに恵まれて幸せです。 市川@cakephperです。 丹精込めてVAddyを作ってます。

経緯

2017年9月にLaravel5.5にタイミング攻撃が存在するという脆弱性が CVE-2017-14775 として公開されました。 修正のPull Requestはこちらです。
[5.5] [Security] Close remember_me Timing Attack Vector

日本語の記事として、Innovator Japan Engineers’ Blogのlocakdiskさんの記事があります。
Laravel の脆弱性 CVE-2017-14775 の対処法

この修正を見て、そもそも最近の高速なCPUや複雑なネットワーク、サーバ環境においてリモートからのタイミング攻撃は可能なのか?という疑問が浮かびました。 今回はPHPを例に説明しますが、結論の箇所はPHPに関係なく他の言語でも同様の結論です。

タイミング攻撃とは

タイミング攻撃とは、処理速度や消費電力の違いを利用して機密情報を取得するサイドチャネル攻撃の一種です。 例として、処理が遅いデバイスで入力値の時間の変化によってパスワードを1文字ずつ解析するものがあります。

PHPのタイミング攻撃

PHPの比較演算子 ===== では、文字列がマッチする長さによって処理時間が異なります。

例えば、'abcde' == 'aaaaa' と、'abcde' == 'abcde' では微妙に処理時間が異なります。 PHPはC言語で実装され、==, ===では下記のような動作になります。

  • 比較する文字列の長さが異なると即falseを返す
  • 同じ長さの文字列は、前方から1文字ずつ比較し、異なった時点でfalseを返す

これにより、パスワード文字列などが効率的に取得できます。

PHPでの対応策

PHPでは、安全な文字列比較関数として hash_equals()がPHP5.6から用意されています。

タイミング攻撃の利点

パスワードを破る場合、総当たりのブルートフォース攻撃がありますが、これは多くのパターンを試行しなくてはならず、時間がかかります。 例えば数字のみのパスワード6桁の場合、最大で100万回の試行が必要です。

タイミング攻撃がうまくいくと仮定すると、最小で60回程度の試行、1回では判別できないことが多いので、何度か試行してその100倍、1000倍程度の回数になります。1000倍でも6万回。 ネットワークの遅延など、ランダムな遅延が発生するので、1文字に対してある程度の回数を試行し、統計処理をすると解析できるというものです。 ただ、揺れが大きく多くの試行が発生する場合は、結局ブルートフォースと同じぐらいの試行回数となる可能性もあるため、どれぐらいの精度で判別できるかが焦点になります。

疑問点の整理

文字列比較のようなμsec程度の処理の差を、ネットワーク越しのWebサーバから検出できるのかというのが今回の疑問点です。 文字列比較の処理時間に比べて、下記のような他の要素が圧倒的に時間がかかるものがある環境で統計的に解析できるのが信じられません。

  • 同じネットワークの経路を通るとは限らず、混雑などの影響の遅延がばらつく
  • Webサーバのアクセス数や、同時に起こっている別の人の処理内容は異なっている
  • Webサーバの手前のキャッシュサーバや多段構成の要素の影響

また解析できたとしても試行回数が多くなって、それはブルートフォースと比べてパフォーマンスが良いのかという点も気になります。

結論

同じような疑問をもっている人がいて、検証した論文が2015年に公開されています。
Web Timing Attacks Made Practical

ここでの結論としては、

  • インターネット経由の場合はμsecレベルの差は検出できない
  • LAN内であっても1μsec程度の処理差だと検出できない(一般的なサーバCPUの文字比較程度だと無理)

となっています。1μsecを超えるようなもう少し重い処理であれば、LAN内なら検出できる可能性はあるようです。

インターネットで広く提供しているサイトであればタイミング攻撃を恐れる必要はないと言えますが、今回hash_equals()を覚えた人は今後は使えるところは使っていきましょう!

修正箇所

今回の修正箇所を見ると、 f:id:ichikaway:20171208090127p:plain

Pull Requestの修正コードは一応問題ないのですが、修正された内容は、まず $userを評価してfalseならhash_equals()は処理されない点が興味深いですね。 今回のコードでは問題になりませんが、$userの結果がtrue/falseによって時間差が生じますので、下手をすると別の問題を引き起こしてたかもしれません。

そもそも元のコードPHPは比較演算子を使ってるわけではなく、SQLのWHERE句でトークンの条件を指定していただけです。それをPHPのタイミング攻撃が!と言ってわざわざSQLから取得した結果をもとにトークンの一致をhash_equals()を使ってやっています。 たぶんこのPull Requestを送ってる人もタイミング攻撃の詳細はあまりよくわかってなく、とりあえずそれっぽいところにはhash_equals()を使うという表面的な考えしかないように見えます。

感想

今回はLaravelの脆弱性修正箇所を見て、これは本当に必要な修正だったのだろうかという疑問が残ります。 フレームワーク作者はフレームワークの機能を作るのに注力しているため、セキュリティの問題があるという指摘とPull Requestがきた場合、SQLインジェクションのようなわかりやすいものは別として、 今回のような影響小さそうだけど良くわらからないから面倒だからという場合は、あまり考えずに取り込んでしまうのではないでしょうか。

そもそも現在の環境でほぼ起こらない問題を指摘して、PHPのお作法的にはこうなってるからしたほうがいいよ(詳細は知らんけど)ぐらいの理解の人が声をあげていくのは良いのだろうか、コア開発者をうんざりさせるだけなのではないか、そう感じました。

この話とはちょっとそれますが、面白い記事があったのでリンクを貼っておきます。
「セキュリティを「必要悪」にした犯人は業界自身ではないか?」

それでは良い金曜日を!

Route53のDNSレコード操作のみ許可するIAMロール

Route53を操作するIAMで、下記の要件を満たすロール設定

  • コンソール画面での操作を想定
  • 特定ゾーンのみのレコードの作成、更新、削除を許可
  • ゾーン一覧の表示
  • ゾーンの削除は不可
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:GetHostedZone",
                "route53:ListHostedZonesByName",
                "route53:ListResourceRecordSets",
                "route53:ChangeResourceRecordSets",
                "route53:ListTagsForResource",
                "route53:ListTagsForResources"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z3PYYYYYY",
                "arn:aws:route53:::hostedzone/Z3PXXXXXX"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Resourceの arn:aws:route53:::hostedzone/Z3PYYYYYY は、アクセスを許可したいドメインのホストゾーンIDを記載する。今回は2つのゾーンを許可している。

Route53の管理ページで、ゾーンの一覧が表示できないとコンソールでの操作が面倒(ゾーンIDを含むURLに直接遷移が必要)なため、 route53:ListHostedZonesを全てのリソース*で許可する。
全てのリソースで許可する理由は、route53:ListHostedZonesは、リソースの範囲を特定ゾーンに絞れないため。

参考URL docs.aws.amazon.com

口座振替の依頼書で銀行印が必要なのは1ページ目だけ(捨印は任意)

支払い方法が口座振替(引き落とし)に限られている場合、自分の銀行印を押して提出する必要がある。

一般的には、1枚目は銀行用、2枚目が委託業者用、3枚目がお客様控えになっていると思う。2、3枚目は写しになっているが、なぜかそこにも銀行印を押すようなフォーマットの用紙がある。(写しだから1枚目と同じ記述になっている)

 

しかし、2枚目、3枚目は銀行以外のところで保管されるため、銀行印は押したくない。そこで口座振替依頼書に記載された電話番号(今回はSMBC系列)にかけて聞いてみた。

結論は下記。

  • 銀行用と書かれた1枚目のみ銀行印を押せば良い
  • 2枚目以降は銀行印は不要。何も押印しないか、認印を押せばよい
  • 捨印は、1枚目も含めて任意であり必須ではない

 

まぁ、普通に考えればそうだよねという結論だが、振替依頼書のフォーマットによっては2枚目、3枚目にも銀行印が必要という記述があって(写しだから)、何も考えないとそのまま銀行印を押してしまうケースは多いと思う。

 

2枚目は基本的に支払先が管理する用紙のため、申し込み先のお店やスクールなどが管理する。ここに銀行印を押したものを管理されたくないので今回調査した。