これは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()
を覚えた人は今後は使えるところは使っていきましょう!
修正箇所
今回の修正箇所を見ると、
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のお作法的にはこうなってるからしたほうがいいよ(詳細は知らんけど)ぐらいの理解の人が声をあげていくのは良いのだろうか、コア開発者をうんざりさせるだけなのではないか、そう感じました。
この話とはちょっとそれますが、面白い記事があったのでリンクを貼っておきます。
「セキュリティを「必要悪」にした犯人は業界自身ではないか?」
それでは良い金曜日を!