なぜPHPアプリにセキュリティホールが多いのか?

第23回まだまだ残っているCSRF攻撃

CSRF(Cross Site Request Forgeries)は数年前にその危険性が広く認知された攻撃手法です。Webページを見ただけで、普段自分が利用しているログインが必要なサイトに意図しないリクエストが送信されたりする問題です。

CSRFの動作原理

CSRFは攻撃用の情報を含んだWebページやEメールを利用して攻撃します。被害者が攻撃用のページを表示したり、URLをクリックすると、攻撃対象のWebサイトに利用者が意図していないリクエストを送信します。

図1 CSRF攻撃
図1 CSRF攻撃

CSRFによる不正なリクエストは被害者からリクエストなので、ログインが必要なサイトであっても既にユーザがログイン済みの場合、正規のユーザからのリクエストとして攻撃対象のWebサーバはリクエストを受け付けてしまいます。

ログインが必要ない公開サイトであっても、問い合わせフォームから大量の不正な情報が登録される、などの問題が発生します。

認証が必要なサイト/ページにのみCSRF対策が必要というわけではありません。認証の有無に関わらずCSRF対策は必要です。例えば、問い合わせフォームがCSRFに脆弱だと意味不明なデータを大量に送信されたりします。

SQLインジェクション対策と同様にCSRF対策も簡単な対策で防げるのですが、まだまだ多くのWebアプリケーションに脆弱性が残っているのが現状です。

CSRFを利用した攻撃

CSRFを利用した攻撃は、意図しないリクエストによる情報の登録/改ざん/削除が可能になります。

CSRFに似た攻撃にJavaScriptハイジャックやJSONハイジャックがあります。最近、TwitterのJSON情報が漏洩する問題が発生しました。CSRF類似の攻撃によって情報が漏洩するよい例です。しばらく前になりますが、GMailのコンタクトリストにも同じような問題がありました。

なぜ情報漏洩が発生するのか?

XMLHttpRequestはクロスサイトリクエストを許可しません。通常のXMLHttpRequestを利用してJSONデータを要求しようとしても、ほかのWebサイトがJSONデータを取得することはできません。しかし、IEを除く最近のブラウザでは

<script type="text/javascript">
Object.prototype.__defineSetter__("user",
  function(value) { /* do something with user's value */ }
);
</script>
<script src="https://twitter.com/statuses/friends_timeline/"></script>

のように__defineSetter__を利用して情報を盗むことが可能です。このJSONスクリプトのハイジャックはよく知られた攻撃手法です。

この攻撃を防ぐにはwhile(1)をJOSNデータの最初の行に挿入する対策[1]もありますが、CSRFを防ぐ方法と同じ手法で防ぐことも可能です。

なぜCSRF対策で情報漏洩が防げるのか?

JavaScript/JSONハイジャックによる情報漏洩はCSRF攻撃と非常によく似ています。被害者は攻撃用のJavaScriptコードが埋め込まれたページにアクセスすることにより、JSONデータを盗まれます。

図2 JavaScript/JSONハイジャック
図2 JavaScript/JSONハイジャック

一般的なCSRFとの違いは、意図しないリクエストが送信されることと不正なリクエ ストの後に情報が漏洩する点です。

攻撃の手順がほぼ同じなので、CSRF対策と同じ対策がJavaScript/JSONハイジャックにも有効です。

CSRFの対策

CSRF対策には幾つかの方法が知られており、拙著Webアプリセキュリティ対策入門でも二つの方法を紹介しています。今回はその2つの方法に1つ加えて、3つの対策を紹介します。

使い捨てのリクエストIDを利用する方法

リクエスト用にランダムな使い捨てのリクエストID[2]を設定すると、有効なリクエストIDは攻撃者や攻撃者には分からないので、CSRFが行えなくなります。リクエストIDは有効期限を設けると共に、使用済みのIDは使用済みであるとマークしておくと便利です。フォームの2重送信も使い捨てのリクエストIDを利用することにより防ぐことが可能です。

この方法の欠点は、IDをデータベース等で管理しなければならないことです。フォーム送信以外に利用すると、大規模サイトでは膨大な量のIDを管理しなければならなくなる問題が発生します。

セッションIDを利用する方法

セッションIDは攻撃者には分からないので攻撃の踏み台ページに埋め込めません。セッションIDをクエリ文字列やPOSTリクエスト等に埋め込み、リクエストが送信された場合に照合すれば、ユーザが意図しないリクエストであるか分かります。

この方法は単純なGETリクエストが大量に有っても、データベース等でIDを管理する必要がないので効率がよいです。しかし、この方法ではフォームの2重送信を防ぐことはできません。

セッションIDの安全性確保はセキュリティ維持に非常に重要であるため、ページやURLに埋め込むことに抵抗を感じる方も居ると思います。次に紹介する方法ではセッションIDをページやURLに埋め込む必要はありません。

セッション変数を利用する方法

CSRFを防ぐにはセッションIDを利用する方法でも十分です。しかし、セッションIDを記載したページを保存したり、メールで送信するなど、セッションIDが漏洩してしまう可能性もあります。秘密の文字列をセッションに保存する方法ならこの欠点もありません。

秘密の文字列は何でも構いません。UNIX OSなら/dev/urandomから数十バイト読み込み、SHA-1のハッシュ関数でハッシュ値をセッション変数として保存し、秘密の文字列として利用します。ほかの2つ方法と同様に、この秘密の文字列をクエリ文字列やフォームに埋め込みます。

この方法であれば、セッションIDを利用した方法と異なり、セッションIDが漏洩する可能性もありません。リクエストIDのようにデータベースで管理する必要もありません。

まとめ

リファラ(Referer)を利用したCSRF対策もありますが、これはブラウザ側の実装とセキュリティに頼った方法であるためお奨めしません。実際、FlashやAdobe Readerのバグでリファラ改ざんが可能になるなどの事例もあります。リファラを送信しないように設定するブラウザ拡張やプロキシもあります。

利用者が意図しないリクエストの送受信が発生しないようにするにはCSRF対策が有効だと覚えておいてください。CSRF対策は難しくありません。SQLインジェクションと同様に確実に防げる脆弱性です。上記の3つうちのいずれかの対策を利用されるとよいでしょう。

おすすめ記事

記事・ニュース一覧