domains / security / csrf-hunt / hunt-csrf
Both copies describe the same CSRF hunting workflow (state-changing endpoints, SameSite/CORS constraints, token bypasses, high-value targets), but each contains material steps the other lacks. The uphiago copy emphasizes the browser security model, a SameSite cheat sheet, and a PATCH-method middleware bypass. The sickn33 copy adds a mandatory authorization gate, a raw-HTTP-client shortcut for naive Origin/Referer string checks, and a concrete four-step autonomous testing flow including text/plain JSON-CSRF. Crowning one would hide the other's unique constraints and techniques.
| Repository | Path |
|---|---|
| uphiago/recon-skills | redteam/hunt-csrf/SKILL.md |
| sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills-claude/skills/hunt-csrf/SKILL.md |
| Role | Repository | Path |
|---|---|---|
| peer | uphiago/recon-skills | redteam/hunt-csrf/SKILL.md |
| peer | sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills-claude/skills/hunt-csrf/SKILL.md |
| adapter | sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills/skills/hunt-csrf/SKILL.md |
| adapter | sickn33/agentic-awesome-skills | skills/hunt-csrf/SKILL.md |
---
name: hunt-csrf
description: Hunting skill for CSRF vulnerabilities, covering modern variants and browser security model constraints.
category: security
risk: offensive
license: MIT
---
## When to Use
Use when the target has any state-changing endpoint that a logged-in user can trigger — POST/PUT/DELETE on account settings, email changes, social account linking, OAuth flows, API calls, or file operations. CSRF exploits the trust a site has in a user's browser by forging cross-origin requests. Every form submission, AJAX call, OAuth callback, and API mutation is a candidate. Highest-value targets: account takeover vectors (OAuth/SSO flows, social account linking), authentication infrastructure (login CSRF, session fixation), JSON APIs accepting cross-origin POST, and third-party integrations (Grafana, monitoring dashboards).
## ⚠️ AUTHORIZED USE ONLY
This skill is for educational purposes or authorized security assessments only. You must have explicit, written permission from the system owner before using this tool. Misuse of this tool is illegal and strictly prohibited.
**Mandatory confirmation gate:**
Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target:
1. Ask the user to state the exact target URL, IP, account, or resource.
2. Ask the user to confirm written authorization and the permitted scope.
3. Show the exact command(s) and explain their expected effect.
4. Wait for explicit confirmation in the current conversation.
Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab.
## ⚠️ CRITICAL: curl ≠ browser. Model the browser security model.
**CSRF PoC MUST work in a real browser, not just curl.** The browser enforces rules that curl ignores:
| Browser Rule | curl Behavior | Real CSRF Impact |
|-------------|---------------|-----------------|
| `SameSite=Lax` | curl sends cookie anyway | **Blocks cookie on cross-site POST** — no CSRF |
| `SameSite=Strict` | curl sends cookie anyway | **Blocks cookie on all cross-site requests** — no CSRF |
| CORS preflight | curl skips OPTIONS | Browser blocks if `Content-Type: application/json` |
| `Sec-Fetch-Site: cross-site` | curl doesn't send | Server can reject cross-site requests via this header |
**CSRF verification checklist:**
1. ✅ Endpoint changes state (POST/PUT/DELETE)
2. ✅ Cookie has `SameSite=None` OR `SameSite=Lax` with GET-based action
3. ✅ No custom CSRF token/header required
4. ✅ PoC works from a **different origin in a real browser**
**If step 2 fails (SameSite=Lax on POST endpoint) → NOT exploitable CSRF via curl alone.** SameSite=Lax allows cookies on top-level navigation GET, not cross-site POST. A curl POST succeeding with the cookie is a **false positive** — the browser would block it.
**SameSite cheat sheet:**
| Cookie Attribute | Browser sends cookie on... |
|-----------------|---------------------------|
| `SameSite=None` | All cross-site requests (needs `Secure`) |
| `SameSite=Lax` | Cross-site GET (top-level nav only). **Blocks POST** |
| `SameSite=Strict` | Same-site only. Blocks everything cross-site |
| Not set (default) | Treated as `Lax` in modern browsers |
## Shortcut: a raw HTTP client beats a real cross-origin page for header-check CSRF
A raw HTTP client (curl, Burp Repeater, any scripting client) is not a browser: it will send whatever `Origin`/`Referer` header VALUE you set, from any path, on the same connection as your authenticated cookie. Many apps that claim to defend against CSRF only do a naive **string check** on the incoming `Origin`/`Referer` header (does it contain/equal some expected value?) rather than real same-origin enforcement — you can satisfy that check directly by setting the header, with no actual cross-site delivery (hosting an HTML page, a headless browser) required. This is faster and more reliable than building a real attacker page for this exact pattern:
```
POST /profile HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Origin: https://a-domain-the-app-treats-as-trusted-or-attacker-controlled.example
Cookie: <authenticated session>
username=csrf_poc
```
If some text names a SPECIFIC origin/domain as the "expected" attacker page, that literal value is often exactly what the server's check is looking for — try it verbatim in `Origin` (fall back to `Referer` if `Origin` alone doesn't flip it). Only build a real cross-origin page (actual browser delivery) when the target does genuine SameSite/fetch-based origin enforcement that a spoofed header can't satisfy.
## Autonomous Testing Priority
**CSRF only matters on state-changing actions that a browser could be tricked into making cross-site.**
**Testing flow:**
1. **GET the form endpoint** to establish a baseline and check what fields exist (look for hidden `csrf_token`, `authenticity_token`, `_token`, `csrfmiddlewaretoken` fields).
2. **POST the state-changing action without any CSRF token field.** Send only the functional parameters (email, amount, etc.).
3. **Use a "simple-request" Content-Type** — `application/x-www-form-urlencoded`, `multipart/form-data`, OR `text/plain` are the three CORS "simple" content-types a cross-origin form can send with no preflight. A JSON endpoint is CSRF-resistant **only if the server rejects those** — if it also accepts a `text/plain` body (common), craft a `text/plain` payload that parses as valid JSON (see the JSON-CSRF-via-text/plain section). Don't skip a JSON endpoint on the assumption that `application/json` alone is protective.
4. **If the action succeeds (2xx, no "invalid token" error) → CSRF is confirmed.**
**High-value targets (in order of impact):**
- Email/password change → account takeover
- Money transfer or payment → financial fraud
- Admin actions (role assignment, user deletion)
- OAuth social-account linking → persistent ATO
**Token bypass techniques when a token IS present:**
- Omit the token field entirely — some frameworks only validate if the field exists, not if it's absent
- Send an empty value (`_token=`) — some validate format, not presence
- Copy a token from another session — some tokens aren't tied to the session
**Scope:** Don't test CSRF on login forms (no existing session to exploit), logout (no real impact), or read-only GET endpoints.
**CSRF Middleware Protection Bypass**:
- Many modern web frameworks (for example, those with CSRF protection enabled by default) reject state-changing requests such as POST, PUT, or DELETE with a `403 Forbidden` response when the CSRF token is missing or invalid.
- In some applications, if the `PATCH` method is not covered by the same CSRF middleware or is handled differently, it may be possible to perform the same state-changing action using PATCH instead. This can result in a CSRF protection bypass if the server accepts the request without validating a CSRF token.
## Crown Jewel Targets
CSRF becomes high-value when it touches **state-changing actions with account-level or financial consequences**. The highest-paying targets are:
- **Account takeover vectors**: OAuth/SSO flows (RelayState manipulation), social account linking/unlinking (Oculus-Facebook, SocialClub), import-friends features that expose OAuth tokens
- **Authentication infrastructure**: Login CSRF, session fixation via CSRF, forced account association
- **API endpoints accepting cross-origin POST**: JSON APIs, heartbeat/activity APIs, anything that skips Content-Type enforcement
- **Third-party integrations**: Grafana, monitoring dashboards, embedded analytics — often lag on CSRF protections
- **Social platforms**: Twitter/X collections, friend imports, social graph mutations — high-volume, authenticated actions with real user impact
**Asset types that pay most:** Core product auth flows > API gateways > third-party integrations running on subdomains > admin panels.
## Attack Surface Signals
### URL Patterns
```
/oauth/authorize?RelayState=
/accounts/link
/import/friends
/api/v*/heartbeat
/api/v*/collect
/monitoring/* (Grafana, Prow, Prometheus)
/auth/saml/callback
/connect/* (social integrations)
```
### Response Header Signals
```
# Missing or weak SameSite cookie attributes
Set-Cookie: session=abc123; HttpOnly # no SameSite = vulnerable
Set-Cookie: session=abc123; SameSite=None # explicitly allows cross-site
# Missing CSRF headers
# No X-Frame-Options or permissive CORS
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true # dangerous combo
```
### JS / DOM Patterns
```javascript
// Static or predictable CSRF tokens
meta[name="csrf-token"] // grep if value changes across sessions
authenticity_token // Rails — check if reused across page loads
// JSON endpoints without Content-Type enforcement
fetch('/api/heartbeat', {method: 'POST', body: JSON.stringify(data)})
// No CSRF token in form at all
<form method="POST" action="/accounts/link"> // no hidden token field
```
### Tech Stack Signals
- **Rails apps**: Look for `authenticity_token` — test if it's static per session
- **Django apps**: Check `csrfmiddlewaretoken` — test cross-user/session reuse
- **Grafana instances**: CVE-2022-21703 — check version via `/api/health`
- **SAMLv2/OIDC flows**: `RelayState`