技術者ブログ

クラウド型WAF「Scutum(スキュータム)」の開発者/エンジニアによるブログです。
金床“Kanatoko”をはじめとする株式会社ビットフォレストの技術チームが、“WAFを支える技術”をテーマに幅広く、不定期に更新中!

2018年3月

        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
  • お問い合わせはこちら
Scutum開発者/エンジニアによる技術ブログ WAF Tech Blog

さよならCSRF(?) 2017

はじめに

2017年、ついにOWASP Top 10が更新されました。筆者が一番印象的だったのは「Top 10にCSRFが入っていない」ということです。

なぜCSRFが圏外になってしまったのかは4ページのリリースノートで軽く説明されています。「retired, but not forgotten」つまり「引退したね...でも君の事は忘れてないよ」という感じでしょうか。全米がCSRFのために泣きそうです。

それはさておき、具体的には「as many frameworks include CSRF defenses, it was found in only 5% of applications.」という部分が引退理由だと思われます。「多くのフレームワークがCSRF対策を備えた結果、5%のアプリケーションにしかCSRFは見つからなかった」というのが引退の理由です。

この理由を読むと、「ということは、XXEは5%よりも多いのか?」「5%に対して”only”という表現になっているが、5%というのは少ない数値なのか?」「そもそも5%の母集団は何なのか?」など色々な疑問が湧き上がりますが、まぁOWASP Top 10というからには10個にまとめないといけないという性質上、皆が飽き気味のCSRFがスケープゴートにされたというところでしょうか。

さて、OWASP Top 10から引退してしまったとはいえ、CSRFは対策が必要な攻撃手法であり続けます。今回の記事では「2017年時点のCSRF対策」について簡単にまとめてみたいと思います。

誰がこの記事を読むべきか?

先述のようにCSRF対策はフレームワークが行ってくれるケースが大部分です。そのため一般的なウェブアプリケーション開発者は自身が使うフレームワークのマニュアルを読み、CSRF対策が必要な箇所に施す方法を知れば基本的にそれでOKだと考えます。

したがってこの記事は「それ以上」を求める人、具体的には「フレームワークの枠に収まらない挙動を実装することがあるウェブアプリケーション開発者」、「ウェブアプリケーションフレームワークの作者」、そして「ウェブアプリケーションセキュリティに技術的な興味を持っている人」などを想定して書いています。

対策1: CSRFトークン(hidden)

多くのフレームワークで古くから使われ、現在も愛用されている対策です。サーバ側のユーザ固有のセッションオブジェクトにランダムに生成したトークンを格納し、同時にformのhidden要素にも埋め込んでおく方法です。submitされた際に、リクエスト内の値とサーバ側の値が同じであることをチェックすることでCSRF攻撃を判別します。2017年現在でも、CSRF対策の代表的な存在です。10年以上前のブラウザでも動作する互換性の高さが最大の利点でしょうか。

対策2: CSRFトークン(HTTPヘッダ)

前項と同じようにサーバ側でトークンを管理しつつ、ブラウザ側ではJavaScriptによって独自のHTTPヘッダ(例えばx-csrf-tokenなど)を追加し、その値にCSRFトークンをセットする手法です。ウェブアプリケーションをより柔軟に実装することが可能になる(JavaScript/XHRを使いやすい)ため、こちらも広く使われています。

対策3: 独自HTTPヘッダの追加

前項に似ていますが、トークンを使わず、単にヘッダ(例: X-Requested-With)を追加し、そのヘッダが存在すればCSRF攻撃ではない、と判断する方法です。CSRF攻撃の場合は必ずクロスサイトでのリクエスト送信となりますが、その場合は攻撃者の用意したスクリプトは基本的に独自ヘッダを付与することができないことを使った防御方法です。この対策は非常に低コストに実装できますが、Flash等のバグ(ヘッダを付与できてしまう)で攻略される可能性を気にする人もいます。その場合は対策2を選べばよいでしょう。

対策4: Double Submit Cookie

対策1は基本的にCookieによってセッションが管理されていることを前提にしています。つまり情報としてはリクエストに含まれるCookieの値とパラメータの値の間に正しい関係があれば良いわけです。そのため必ずしもサーバ側でセッションオブジェクトを生成する必要はなく、例えば同一のトークンをSet-Cookieしつつhiddenに埋め込む方法でもCSRF攻撃を判別することは可能です。この方法はDouble Submit Cookieと呼ばれるようです。(参考: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet)

同一の値であることに不安を感じる場合には、どちらかからもう一方が計算できる(逆はできなくてよい)ようなハッシュ値などを使用し、2つの値を異なるものにするとよいでしょう。ScutumのCSRF対策(オプションメニュー)ではこの方法を採っています。

対策5: Originヘッダのチェック

Originヘッダの値が正しいことをサーバ側でチェックするだけの非常に明瞭かつ実装コストの低い優れた方法です。数年後にはこれがCSRF対策の代表になるのではないかと思います。現時点ではブラウザ間の挙動に差が見られたりする(Originヘッダを送ってくれないケースがある等)ので使いづらい面も残っているようです。REST APIでのCSRF対策には非常に便利だと考えられます。

対策6: Same-site Cookies

CSRF対策のために提案されている新たなCookie属性です。「クロスサイトのリクエストにもCookieは自動的に付加されてしまう」というこれまでの常識だった挙動を変え、コントロールできるようにするもので、CSRF対策のために考えられたものです。httpOnlyに似た感覚で使えそうなイメージです。

個人的な意見として、TLS/SSLへの攻撃であるPOODLE/BEAST/CRIME等はCSRF攻撃の一種であると考えています。CSRF対策であるSame-site Cookieによって、これらの攻撃も防ぐことができるようになります。

この対策には少し課題が残っています。

Aというサイトから単純にリンクをクリックしてBというサイトに飛ぶ場合もクロスサイトのリクエストになります。この際BへのCookieが送られなければ、Bには「ログインしていない状態」で訪れるという挙動になります。これはユーザビリティの面で違和感があるでしょう。この問題を解決するため「Lax」というモードが用意されているようですが、(ウェブアプリケーション開発者の視点で考えると)これによって動作確認が必要となる挙動が増えてしまい、なかなか厄介そうにも思えます。

Same-site Cookiesの実装が本格的に各ブラウザへ普及するにはまだ時間がかかりそうです。

まとめ

非常に簡単にですが、2017年時点で良く知られたCSRF対策を列挙してみました。個人的には対策5が最も優れていると思いますが、まだブラウザ間での挙動の差異(特に、送って欲しいときに送ってくれないケース)があることから、全面的に採用するのは難しい状況です。

一方で対策6はPOODLE等の攻撃によるCookieの値の窃取(セッションハイジャック)を防ぐことができる利点を備えており、こちらにも期待しています。

参考リンク集

この記事は下記を参考に作成しました。

https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet

http://d.hatena.ne.jp/hasegawayosuke/20130302/p1

https://tools.ietf.org/html/draft-west-first-party-cookies-07

余談

とあるWAFのユーザ向けテクニカルペーパー(英語)に、そのWAFを使って「CSRF攻撃を緩和する方法」として「HTTPヘッダに含まれるCSRFトークンの長さをチェックする」という手法が紹介されていました。これは技術的に間違っています。

正常なユーザが送ってくるリクエストのヘッダにx-csrf-tokenのような項目があるのであれば、それは上記の対策2か対策3がなされているということを意味します。つまりそのウェブアプリケーションは既にCSRF対策済みです。長さはもちろん、トークンの内容もウェブアプリケーション側がチェックしてくれるでしょう。WAFが長さだけチェックしてあげても殆ど意味がないと言えます。

もしWAFでCSRF対策を行いたいならば(つまり、ウェブアプリケーションそのものにCSRFの脆弱性が存在するならば)、Scutumが行うようにHTTPレスポンスを動的に書き換えてトークンを埋め込むか、あるいはOriginヘッダを監視する、などの方が良いでしょう。