XSSがある場合のCSRF対策としてクリックジャッキングを使う(没)案
最近話題になっているクロスサイトリクエストフォージェリの話を。
CSRF対策はXSSがある場合には効果的ではないと言われます。javascriptが実行できればhtml中のtokenもとれますし、リクエストも自由に送信できるので対策を回避できるためですね。
XSSがあっても万が一防げるようにするため、クリティカルな箇所ではパスワードなどユーザしか知らない情報を使って確認しているサイトが多いでしょうか。しかしユーザに何度もパスワードを入力を促すと、XSSによって入力フォームが作られたときに疑われずに入力されてしまうのではないかという懸念もあります。*1
javascriptから直接触ることができない領域を使えばXSSがあったとしてもなんとかなるはずです。思いつくのはhttponlyがついたクッキーの値や別オリジンでしょうか。httponlyがついたクッキーは基本的にリクエスト時に送信されているのでCSRF対策にはなりません。他にもFlashとかでなにかあるかもしれませんが、まあ置いておきます。
ここでは別オリジンを使った方法を考えます。最も単純なのはユーザがボタンを押したときに同時に別オリジンのボタンを押させることです。そうクリックジャッキングです。
クリックジャッキングについて
詳細はこのあたりで。
大体の場合はレスポンスヘッダにX-Frame-Options
を使ってiframeに読み込まれるのを防ぎます。
CSRF対策案
CSRFを防ぎたいhttps://example.com
と、別オリジンとなるhttps://prevent-csrf.example.com
を用意します。htmlの雰囲気としてはこんな形で。
// https://example.com の画面 ... <form> <input type="token" value="何かしらのcsrf_token" /> <button type="submit">XSSがあってもCSRFを防ぎたい処理のボタン</button> </form> <iframe src="https://prevent-csrf.example.com/prevent" style="...."> <form action ="/prevent"> <input type="referrer" value="読み込みサイトのURL" /> <input type="token" value="何かしらのcsrf_token_2" /> <button type="submit">元のドメインのボタンと同じ位置に表示されるボタン</button> </form> </iframe>
ボタンが押されたときにそれぞれのドメインに向けてほぼ同じタイミングでリクエストが飛ぶようにし、それをサーバ側で検証することでユーザ操作によるものか、XSSか何かを使って操作されたものかどうかを判断します。
ボタンが押されたときに同時にリクエストされることを想定していますが、ちゃんと動くかどうかは微妙なのでhttps://example.com
側はjavascriptで制御してタイミングを遅らせるかSingle Page Applicationのような画面遷移にしたほうが良いでしょうか。
問題点
アクセスビリティ
マウスイベントはクリックジャッキングによってiframeの埋め込み先に貫通しますが、tabキーでのフォーカスは一緒に遷移はしませんし、エンターキーでのフォームの送信もされません。スクリーンリーダーとの組み合わせも悪いでしょう。ユーザに対して、"マウスでクリックして"と説明をつけることである程度回避できるかもしれませんが、使いづらそうです。
CSRF対策
例のhttps://prevent-csrf.example.com
側にもCSRFとXSS対策も必要です。CSRFが行われると結局元のドメインにもCSRFができてしまうためです。この用途にしか使わないドメインとしておいて、さらにCSPなど入れてガチガチにするとこんな感じでしょうか。
- 用途を限定して他の目的に使わない
- httpsのみにし、さらにHSTSをかける
- CSPでXSSをブロックする
- X-Frame-Optionsで
ALLOW-FROM
を使ってiframeとして読み込み元を限定する*2
XSS対策の一部として全くjavascriptを使わない実装にするか、javascirptを使ってカスタムヘッダなどをつけて送信することでCSRF対策にするかは迷うところですね。
https://prevent-csrf.example.com
を攻撃者が自分で開く(or サーバから自動で叩く)こともありえるので、*.example.com
に対してhttponlyとsecureフラグをつけたトークンを更に別に発行して、サーバ側で同じユーザかどうかを判断する必要もあります。サブドメインであればこのあたりはやりやすいですが、そうでない場合はOAuthとかリダイレクトでトークンを渡してやる必要があって、更にその仕組の安全性が…となっていくのでひたすら面倒ですね。
CSRF対策としての機能を目指しているのに、更にCSRF対策をしているのは何と言うか、何でしょうね。
クリックジャッキング対策(追記22:12)
元のオリジンにクリックジャッキング対策が必要なのはもちろんですが、XSSによってjavascriptが実行された時ユーザのマウスに追随する形で、https://prevent-csrf.example.com
側のiframeを移動させることも可能です。そのため多少面倒ですが、XSSとクリックジャッキングの組み合わせによってCSRFができてしまう可能性があります。window.screenX
を使って、iframeが読み込まれている位置を特定できるかもしれませんが確認できていません。
まとめ
*1:この辺どっちがいいか資料などがあれば誰か教えてください。
*2:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options#Browser_compatibility によるとChromeではALLOW-FROMが使えないようです