本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。

最新情報

2025.04.14

CSRF、この罠いける?いけない?クイズ

本記事は、昨年12月に開催された弊社の「MBSDセキュリティ勉強会~LT大会~」にて行ったLTの内容をブログ化したものとなります。

早速ですが、みなさんはクロスサイトリクエストフォージェリ(以下、CSRF)という脆弱性をご存じでしょうか。一応説明しようかと思いましたが、ちょうどいいところに過去の弊社ブログ記事がありましたので、詳しい解説はそちらにお任せすることにします。

ざっくり言うと「攻撃者の用意した罠ページを閲覧した被害者が、意図しない処理を実行させられてしまう」というものです。

そして、世の中の多くのWebサイトではCookieを用いてセッション管理が行われていますので、「意図しない処理を実行させる」ためには、「攻撃者のWebサイトから脆弱なWebサイトに向けて、サイトをまたいでCookieが送出されるか」というのがキモになってきます。(Siteの詳しい概念についても、こちらの過去記事で説明されているのでぜひ!)

この点に関して、数年前からブラウザ側での対応が進んでおり、攻撃が成立する状況は限定的になってきました。しかしこの「攻撃が成立するかどうか」のパターンが微妙に覚えにくく、Web脆弱性診断の仕事をしていて頭がこんがらがることもしばしば。正確に把握していると自信を持って言える方は多くないのではないでしょうか。

そこで今回は、どのようなブラウザ、正常リクエスト、罠ページの組み合わせであればCSRFが成立しうるのか、クイズ形式で整理していきたいと思います。全部で5問ありますので、ぜひ考えながら読み進めてみてください。

前提条件

クイズの前に、今回使用したブラウザとそのバージョンを記載します。

  • デスクトップ版 Google Chrome 134.0.6998.36 (64bit)
  • デスクトップ版 Firefox 135.0.1 (64bit)

なるべく最新版を使用するようにしましたが、新しめのバージョンなら基本的に挙動は変わらないと思います。

また、セキュリティやプライバシーに関するブラウザの設定項目はデフォルトの状態としており、ユーザ自身があえてセキュリティ設定を緩めるなどはしていない前提となります。

※Safariは今回対象に含めていませんが、だいたいFirefoxと同じ結果になるはずです。

第1問 [POST + FORM]

設問

  • 正常リクエスト

CSRFに対して脆弱なサイト「victim.jp」があり、通常のブラウジングで下記のリクエストが送られるとします。

POST /post-commit HTTP/1.1
Host: victim.jp
Content-Length: 13
Cache-Control: max-age=0
Origin: http://victim.jp
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 …(以下略)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://victim.jp/
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: sessionid=test
Connection: keep-alive

name=hogehoge
HTTP
  • POSTメソッドによる更新処理である
  • セッションIDはCookie「sessionid」であり、HttpOnlyやSecure、SameSiteといったセキュリティ関連の属性値は未設定である
  • CSRFトークンの検証など、サーバ側で行われるCSRF対策は一切なし

  • 罠ページ

そして、攻撃者(attacker.com)が上記のリクエストを悪用するために用意する罠ページは、下記であるとします。

<html>
<
head
>
<meta http-equiv="Content-type" content="text/html; charset='UTF-8'">
<
script
type="text/javascript">

function
csrfPoC() {
document.forms[0].submit();
}

</
script
>
</head>
<
body
onload="csrfPoC();">

<!-- begen form -->

<
form
action="http://victim.jp/post-commit" method="POST">

<
input
type="hidden" name="name" value="hogehoge">

</
form
>

<!-- end form -->

</
body
>
</html>
HTML

やろうとしていることは単純で、このページを訪れた被害者のブラウザにて、自動的にformをsubmitさせるというオーソドックスなものです。

それではいよいよ問題です。この条件下で、「victim.jp」にログイン済みの被害者が「attacker.com」の罠ページを訪れた場合、CSRFは成功するでしょうか? Google Chrome, Firefoxそれぞれの場合についてお答えください。

  • ○:成功する
  • △:条件付きで成功する
  • ×:失敗する

なお、「条件付きで」といっても、攻撃者が前もって被害者に別の攻撃を仕掛けておく、などは含みません。

解答

Google Chromeの場合

△:条件付きで成功する(Lax by defaultの制約内で)

Firefoxの場合

○:成功する(Lax by defaultがデフォルトでOFFのため)

(※クリックで答えが表示されます)

以下、ネタバレ解説です。

  • Tips: Lax by default

2020年2月、Google ChromeはSameSite属性未設定のCookieのデフォルト挙動をLaxに変更しました。これをよくLax by defaultと呼びます。(参考)

Lax by defaultは以下のルールで適用されます。(参考)

  1. SameSiteが設定されていないCookieはLax相当として扱われる
  2. Noneを明示的に設定するには、Secure属性の付与も必要になる
  3. Lax by defaultによって保存したCookieは2分間None相当の動作をする

今回重要なのは3つ目で、いわゆる2分間ルールというものですね。

では、Noneの時の動作はどのようになるでしょうか。SameSite属性の設定されたCookieがブラウザから送出されるかどうかを、ナビゲーション別に表にしたのが下記です。

None Lax Strict
href
form[GET]
form[POST]
XHR
img

見ての通り、SameSite属性がNoneの時、form[POST]のリクエストでCookieは送出されます。Chromeの場合の正解が△となるのは、「発行後2分の間なら攻撃は成功する」から、という理由です。

一方Firefoxの場合はというと、最初の時期だけLax by defaultが適用されたりもしたのですが、結局現在はデフォルトでOFFになっています。よって、form[POST]の攻撃は普通に成立します。なお、SafariはLax by defaultをサポートしていません。

第2問 [POST + XHR]

設問

  • 正常リクエスト

こちらは第1問と同じPOSTメソッドによる更新処理となるため、割愛いたします。

  • 罠ページ

攻撃者が用意する罠ページの内容は第1問と異なります。

<html>
<
head
>
<meta http-equiv="Content-type" content="text/html; charset='UTF-8'">
<
script
type="text/javascript">

// begen script
function csrfPoC() {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://victim.jp/post-commit', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('name=hogehoge');
}
// end script

</
script
>

</
head
>

<
body
onload="csrfPoC();">

</
body
>
</html>
HTML

今度はformをsubmitするのではなく、XHRを使ってリクエストを送信させています。

では、この条件下で、被害者が罠ページを訪れた場合、CSRFは成功するでしょうか? Google Chrome, Firefoxそれぞれの場合についてお答えください。

  • ○:成功する
  • △:条件付きで成功する
  • ×:失敗する

解答

Google Chromeの場合

×:失敗する(Lax by defaultのため)

Firefoxの場合

×:失敗する(Total Cookie Protectionのため)

以下、ネタバレ解説です。

  • Tips: Lax + POST

実は、先ほど説明したGoogle ChromeにおけるLax by defaultの2分間ルールには、ある落とし穴があります。

というのも、Lax by defaultでセットされたCookieは2分間None相当の動作をする、という説明がよくなされますが、実のところ完全にNoneと同じ動作をするわけではありません。第1問の解説ではあえて伏せていましたが、「トップレベルナビゲーションに限ってNone相当になる」、という表現がより正確となります。

トップレベルナビゲーションとは、ざっくり言うとリクエストしたURLがアドレスバーに表示されるような動作を指します。

つまり、None相当と言いつつ実際にはLax動作に加えてform[POST]が許可されることになるため、XHRのような画面遷移を伴わない(トップレベルナビゲーションではない)リクエストではCookieが送出されません。このことをLax + POSTと言います。(参考)

なぜこんなにややこしいことになっているかというと、あくまで本仕様はLax by defaultのもたらす影響に対する一時的な緩和策だから、とのことです。今後いつ変更されてもおかしくないということは意識しておくべきだと思います。

  • Tips: Total Cookie Protection

次にFirefoxについてですが、こちらでは別の理由で攻撃が失敗します。まず、Firefoxはユーザのプライバシー保護に力を入れており、これは「強化型トラッキング防止(Enhanced Tracking Protection)」として実装されています。(参考)

ここに内包される形で、2021年に「包括的Cookie保護(Total Cookie Protection)」が導入されました。

image.png

Firefoxの設定画面

これは3rd Party Cookieを規制するための仕組みで、アクセス元のサイトごとにCookieの容れ物(Cookie Jar)を分けることで実現している、と説明されています。

image.png

Mozillaブログより引用

今回の例で言うと、被害者サイト(victim.jp)内で発生する通信と、罠サイト(attacker.com)内で発生する通信とではCookie Jarが別となっているため、サイトを横断するトラッキングを防ぐことができる(結果的に、裏でこっそり通信を飛ばすようなパターンのCSRFも防御される)ということになります。合理的ですね。

ちなみに、Safariの場合はIntelligent Tracking Preventionというこれと似た仕組みがあります。

第3問 [GET + FORM]

設問

  • 正常リクエスト

第1,2問と異なり、今度はPOSTではなくGETメソッドによる更新リクエストです。その点以外は変わりありません。

GET /get-commit?id=1234 HTTP/1.1
Host: victim.jp
User-Agent: Mozilla/5.0 …(以下略)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: http://victim.jp/
Cookie: sessionid=test
Upgrade-Insecure-Requests: 1
Priority: u=0, i
HTTP

  • 罠ページ

罠ページは第1問と同じformを送信させるもので、メソッドがGETである点だけが違います。

<html>
<
head
>
<meta http-equiv="Content-type" content="text/html; charset='UTF-8'">
<
script
type="text/javascript">

function
csrfPoC() {
document.forms[0].submit();
}

</
script
>
</head>
<
body
onload="csrfPoC();">

<!-- begen form -->

<
form
action="http://victim.jp/get-commit" method="GET" >

<
input
type="hidden" name="id" value="1234">

</
form
>

<!-- end form -->

</
body
>
</html>
HTML

では、この条件下で、被害者が罠ページを訪れた場合、CSRFは成功するでしょうか? Google Chrome, Firefoxそれぞれの場合についてお答えください。

  • ○:成功する
  • △:条件付きで成功する
  • ×:失敗する

解答

Google Chromeの場合

○:成功する(Lax by Defaultの影響を受けない)

Firefoxの場合

○:成功する(特に制約がないため)

以下、ネタバレ解説です。

  • Tips: GETで副作用のある機能を実装してはいけない

第3問はここまでで紹介した知識だけでも正解が導けるので、分かった方も多いのではないでしょうか。

GETメソッドはRFC9110により、読み取り専用のSafeなメソッドとして定義されています。つまりGETでデータの更新をするべきではないのですが、残念ながら現実にはそのような実装が後を絶ちません。

この場合はブラウザのデフォルト設定で保護されないので、攻撃者からするとおいしいですね。

第4問 [GET + ???]

設問

  • 正常リクエスト

第3問と同じくGETメソッドによる更新リクエストとなるため、割愛いたします。

  • 罠ページ

こちらは趣向を変えて、以下の4パターンの罠ページがあるとします。

  • パターン①
<style>
body { background: url('http://victim.jp/get-commit?id=1234'); }

</
style
>
HTML

  • パターン②
<link rel="prerender" href="http://victim.jp/get-commit?id=1234">
HTML

  • パターン③
<iframe src="http://victim.jp/get-commit?id=1234"></iframe>
HTML

  • パターン④
<img src="http://victim.jp/get-commit?id=1234">
HTML

では、この条件下で、被害者が罠ページを訪れた場合、CSRFは成功するでしょうか? Google Chrome, Firefoxそれぞれの場合についてお答えください。

今回は複数選択ありとします!

  • ①:styleのbackground:url
  • ②:linkのprerender
  • ③:iframeのsrc
  • ④:imgのsrc
  • ⑤:全部失敗する

解答

Google Chromeの場合

②:linkのprerender

Firefoxの場合

⑤:全部失敗する

以下、ネタバレ解説です。

  • Tips: 投機的読み込み

まず、パターン①③④の罠ページは、いずれもこれまで説明してきたLax by defaultやTotal Cookie Protectionによって攻撃を防がれます。一方でパターン②だけは性質が異なり、これは投機的読み込みという仕組みを使用しています。

投機的読み込みとは、ユーザが実際にそのリソースにアクセスする前に、あらかじめ次に必要になりそうなリソースをロードしておくことです。preconnectやprefetchなどいくつか種類がありますが、この状況下でCookieが送出されるのは今回用いたprerenderのみとなります。

リクエスト送信時点ではブラウザ上での画面遷移は発生していないので、第2問で説明したトップレベルナビゲーションの条件には当てはまっていないような気もしますが、事前に画面をレンダリングしておくという性質上Cookieが飛ばないと困りますからね。

ただし、現在は非推奨の機能であり、FirefoxやSafariではサポートされていません。(参考)

第5問 [POST + XHR + OPEN]

設問

  • 正常リクエスト

第1,2問と同じくPOSTメソッドによる更新リクエストとなるため、割愛いたします。

  • 罠ページ

罠ページは第2問を改変したような形となっており、ユーザが画面のどこかをクリックすると、まずvictim.jpのトップ画面が新しいタブで開き、次に攻撃リクエストがXHRで送信されるという流れになっています。

<body>
<
p
>
Click anywhere.</p>
<
script
>

document.addEventListener("DOMContentLoaded", () => {
document.onclick = () => {
open('http://victim.jp/');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://victim.jp/post-commit', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('name=hogehoge');
}

}
);

</
script
>

</
body
>
HTML

それでは最終問題です。

第2問とそう変わらないように見えますが、この条件下で被害者が罠ページを訪れ、画面をクリックした場合、CSRFは成功するでしょうか? Google Chrome, Firefoxそれぞれの場合についてお答えください。

  • ○:成功する
  • △:条件付きで成功する
  • ×:失敗する

解答

Google Chromeの場合

×:失敗する(Lax by Defaultのため)

Firefoxの場合

○:成功する(Opener Heuristicsのため)

以下、ネタバレ解説です。

  • Tips: Opener Heuristics

まず、Google Chromeの場合はこれまで通り、Lax by defaultによってCookieの送出が防がれます。

一方、Firefoxのドキュメントには以下のような記載があります。

  • When a partitioned third-party opens a pop-up window that has opener access to the originating document, the third-party is granted storage access to its embedder for 30 days.
  • When a first-party a.example opens a third-party pop-up b.example, b.example is granted third-party storage access to a.example for 30 days.

Storage access heuristics」より引用

つまり、ユーザのアクションを起点にサードパーティのサイトをポップアップや新しいタブとして開いた場合、「ユーザ自らの意思でアクセスしてるんだから制限を緩めてもいいだろう」ということで、30日間はストレージへのアクセスが許可されるのです。この仕様をOpener Heuristicsといいます。

これをCSRFに利用すると、SameSite属性を明示的に設定していない場合、ユーザ自身の操作で一度 attacker.com から victim.jp のページを開かせれば、 その後30日間強化型トラッキング防止による保護をバイパスすることが可能となります。このアクセス権が有効な間は、iframeなど他の罠ページのパターンもすべて成功するようになります。

この手法はPT SWARMのブログで紹介されており、SafariのIntelligent Tracking Preventionでも工夫すればバイパスができたそうです。 (参考)

余談ですが、このバイパス手法はCSRFに限らず、他の受動的攻撃にも利用することができます。「JavaScript Hijack / JSONP Hijack」のような懐かしの脆弱性も、これを利用すれば一応まだ戦えると言えそうです。

まとめ

ここまでの説明を踏まえて、再度SameSite属性に応じたCookie送出可否を表にまとめると、以下のようになります。

None Lax Strict
href
link[rel=prerender]
form[GET]
form[POST]
XHR
iframe
img

そして、SameSite属性が未設定の場合の挙動はそれぞれ以下のようになります。

  • Google Chromeの場合:Lax by defaultによって、2分間はform[POST]も許可されるが、それ以降はLaxとして振る舞う。
  • Firefoxの場合:Lax by defaultはOFFだが、Total Cookie ProtectionによってXHRなどのパターンは防がれる。ただし、あくまでプライバシー保護の機能であるため、バイパスされる可能性がある。

とにかくGETメソッドで更新処理を実装してはならない!というのは大前提として、POSTメソッドであっても攻撃が成り立つ余地はあることが理解いただけたと思います。ブラウザのデフォルト挙動やSameSite Cookieに頼りすぎず、サーバ側でリクエストを検証するという基本的な対策は当分欠かせないですね。

なお、近年のブラウザのプライバシー保護関連の動向はめまぐるしいため、今回紹介した挙動もいつの間にか再現しなくなる可能性がありますことをご了承ください。

それでは、今回の記事はここまでとなります。特に新規性のある話題ではありませんが、1つでも皆さんにとって新たな知見があれば幸いです。

全問正解した方は……CORSが絡んだバージョンとか、ぜひもっと込み入ったパターンのクイズを作ってみてください!

プロフェッショナルサービス事業部
小山凌弥
関連ソリューション