Skip to main content

Content Security Policy (CSP) Bypass

Overview

Content Security Policy (CSP) is a browser security mechanism used to define where scripts and other resources can be loaded or executed from. This module demonstrates common implementation mistakes that allow attackers to bypass CSP protections. Important: The vulnerabilities demonstrated here are not flaws in CSP itself, but rather mistakes in how developers implement the policy.

Objective

Bypass Content Security Policy protections and execute JavaScript in the page across different security levels.

Security Levels

Low Level

Vulnerable Code

The low level uses a permissive CSP that allows scripts from multiple external domains:
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com www.toptal.com example.com code.jquery.com https://ssl.google-analytics.com unpkg.com cdn.jsdelivr.net digi.ninja ;";

header($headerCSP);

if (isset ($_POST['include'])) {
    $page['body'] .= "
        <script src='" . $_POST['include'] . "'></script>
    ";
}
```bash

**Location**: `vulnerabilities/csp/source/low.php:3-16`

#### Exploitation

Examine the CSP policy to identify allowed script sources. The policy permits scripts from several third-party domains, creating multiple bypass opportunities:

**Working Bypass URLs**:
- `https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js` - Using jsDelivr to serve JavaScript from GitHub
- `https://unpkg.com/@digininja/[email protected]/index.js` - Using UNPKG to access NPM package files
- `https://digi.ninja/dvwa/alert.js` - Direct JavaScript file with correct headers
- `https://digi.ninja/dvwa/cookie.js` - Displays cookies

**Blocked URLs** (for learning purposes):
- `https://digi.ninja/dvwa/alert.txt` - Wrong content type due to file extension
- `https://digi.ninja/dvwa/forced_download.js` - Server sets `Content-Disposition: attachment` header
- `https://digi.ninja/dvwa/wrong_content_type.js` - Server forces `plain/text` content type

#### Key Vulnerability

Allowing too many external script sources creates an attack surface. Services like UNPKG and jsDelivr are designed to serve raw files without restrictive headers, making them ideal for CSP bypass.

---

### Medium Level

#### Vulnerable Code

The medium level attempts to use a nonce to prevent inline script injection:

```php
$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';

header($headerCSP);

// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");

if (isset ($_POST['include'])) {
    $page['body'] .= "
        " . $_POST['include'] . "
    ";
}
Location: vulnerabilities/csp/source/medium.php:3-18

Exploitation

The implementation makes critical mistakes:
  1. Static Nonce: The nonce value TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA= never changes
  2. unsafe-inline allowed: This directive defeats the purpose of using a nonce
  3. Direct output: User input is directly included in the page
Exploit:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
```bash

Since the nonce is static and predictable, attackers can include it in their malicious scripts.

#### Key Vulnerability

Nonces must be cryptographically random and unique for each request. A static nonce provides no security benefit.

---

### High Level

#### Vulnerable Code

The high level uses a strict CSP with only `'self'` allowed, but makes a JSONP call:

```php
$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

if (isset ($_POST['include'])) {
    $page['body'] .= "
        " . $_POST['include'] . "
    ";
}

$page['body'] .= '
<form name="csp" method="POST">
    <p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
    <p>1+2+3+4+5=<span id="answer"></span></p>
    <input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/high.js"></script>
';
Location: vulnerabilities/csp/source/high.php:2-21

Exploitation

The page loads source/high.js which makes a JSONP call to jsonp.php with a callback parameter. Since JSONP responses are executed as JavaScript, you can:
  1. Modify source/jsonp.php to return malicious code instead of the expected callback
  2. The code will execute because it comes from the same origin ('self')
Attack Vector: JSONP endpoints on the same domain can be abused to bypass strict CSP policies.

Key Vulnerability

JSONP is inherently incompatible with strict CSP because it requires dynamic code execution. Any JSONP endpoint becomes a potential CSP bypass.

Impossible Level

Secure Code

$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

$page['body'] .= '
<form name="csp" method="POST">
    <p>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p>
    <p>The CSP settings only allow external JavaScript on the local server and no inline code.</p>
    <p>1+2+3+4+5=<span id="answer"></span></p>
    <input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/impossible.js"></script>
';
```bash

**Location**: `vulnerabilities/csp/source/impossible.php:2-22`

#### Protection Mechanisms

1. **Strict CSP**: Only allows scripts from `'self'`
2. **No dynamic callbacks**: The JSONP function name is hardcoded, not user-controlled
3. **No inline scripts**: All JavaScript must come from external files on the same origin

---

## CSP Headers Reference

### Common CSP Directives

| Directive | Purpose |
|-----------|--------|
| `script-src 'self'` | Only allow scripts from the same origin |
| `script-src 'unsafe-inline'` | Allow inline `<script>` tags (insecure) |
| `script-src 'nonce-{random}'` | Allow scripts with matching nonce attribute |
| `script-src https://cdn.example.com` | Allow scripts from specific domain |

### Real-World CSP Example

```http
Content-Security-Policy: 
    default-src 'self'; 
    script-src 'self' 'nonce-{random}'; 
    style-src 'self' 'unsafe-inline'; 
    img-src 'self' data: https:; 
    font-src 'self' data:;

Best Practices

Do NOT:
  • Use static nonces
  • Allow 'unsafe-inline' with nonces
  • Whitelist CDNs that serve arbitrary user content (UNPKG, jsDelivr)
  • Implement JSONP with user-controlled callbacks under strict CSP
DO:
  • Generate cryptographically random nonces for each request
  • Use 'strict-dynamic' for modern browsers
  • Minimize external script sources
  • Use subresource integrity (SRI) for third-party scripts
  • Implement CSP in report-only mode first, then enforce

Testing Tools

References


Module developed by Digininja

Build docs developers (and LLMs) love