JSON hijacking lets a hostile web page read data your API returns to a signed-in user, by loading the API response as a script instead of asking for it as data. MITRE CAPEC catalogues it as CAPEC-111 JSON Hijacking (aka JavaScript Hijacking), a Standard-level pattern rated High for likelihood and High for severity, with confidentiality ("Read Data") as the consequence. The original form, overriding the JavaScript array constructor to capture a top-level JSON array, is largely closed in current browsers. The class is not: JSONP endpoints, responses served with a JavaScript content type, missing nosniff headers and cookies sent on cross-site subresource requests all keep cross-site script inclusion alive. This guide is for application security engineers, API developers and reviewers. It explains the mechanism at a defensive level, the browser defences that now carry most of the weight, what your server still has to do, and how to find exposed endpoints. For how CAPEC entries are structured, see the MITRE CAPEC Guide.
Key takeaways
- The root cause is ambient credentials on a script load. A script tag sends the victim's cookies to your origin, and the same-origin policy, as CAPEC puts it, "protects the domain object model (DOM), but not the JSON".
- Modern browsers block most JSON, but not JavaScript. Chromium's Cross-Origin Read Blocking, and the Fetch-standard successor ORB, stop JSON, HTML and XML reaching a cross-origin page, but explicitly do not protect JSONP or responses served as JavaScript.
- Serve data as data. Use application/json with X-Content-Type-Options: nosniff, put an object at the top level, and add Cross-Origin-Resource-Policy: same-origin to authenticated API responses.
- Remove ambient authority. Bearer tokens in headers, SameSite cookies and an anti-forgery token on data endpoints stop the forged request from carrying the session at all.
- JSONP is the live exposure. Any endpoint that wraps sensitive data in a callback is readable cross-site by design; replace it with CORS and an explicit origin allow list.
How the attack pattern works
CAPEC-111 describes an attacker who targets a system that uses JSON between client and server and steals confidential data returned to the client "by taking advantage of the loophole in the browser's Same Origin Policy that does not prohibit JavaScript from one website to be included and executed in the context of another website". Its prerequisites are three:
- JSON is the transport between client and server.
- The server cannot tell real requests from forged ones.
- The JSON returned can be reached by the attacker's code through a script tag.
The execution flow CAPEC gives is an explore phase (the attacker uses their own account to learn which URLs return sensitive JSON), then a malicious page that does two things: it sets up a hook to capture objects as they are created, and it includes the victim application's JSON URL as a script source. The victim visits the page while signed in, the browser sends their cookies with the script request, and the hook reads the data as the browser evaluates the response. CAPEC's example instance is a webmail contacts endpoint whose address-book response was captured by overriding the array constructor, the case that made the pattern widely known.
The attack only reads; it cannot see the response directly, because the same-origin policy hides cross-origin responses. It works when the response, executed as script, exposes its contents through something the attacker controls: a callback name (JSONP), a global variable assignment, or a constructor or setter hook on a literal.
How it relates to neighbouring patterns
| Pattern | What the attacker gets | Main defence |
|---|---|---|
| CAPEC-111 JSON Hijacking | Reads authenticated data by including it as script | Data served as JSON with nosniff and CORP; no ambient cookies on cross-site loads |
| Cross-site request forgery | Makes the victim's browser perform state-changing actions | Anti-forgery tokens, SameSite cookies; see the CSRF Prevention Guide |
| CORS misconfiguration | Reads responses through fetch because the server trusts the attacker's origin | Strict origin allow lists; see the CORS Misconfiguration Guide |
| Cross-site scripting | Runs code inside your origin, so every response is readable | Output encoding and CSP; see the Cross-Site Scripting (XSS) Prevention Guide |
CAPEC-111 is a child of Functionality Misuse, and its mitigations overlap with CSRF: the MITRE CWE entry for Cross-Site Request Forgery lists CAPEC-111 among its related attack patterns. The difference is the goal. CSRF changes state; JSON hijacking reads.
What the browser now does for you, and what it does not
Chromium's Cross-Origin Read Blocking (CORB) explainer states that CORB blocks cross-origin JSON, HTML and XML responses before they reach the requesting page, sniffs bodies when nosniff is absent, and treats the known XSSI prefixes )]}' and for(;;); as protected regardless of what follows. Opaque Response Blocking (ORB), described as CORB++ and being upstreamed to the Fetch Standard, aims "to block as many opaque responses as possible while remaining web compatible" and only lets a no-cors response through when it is a safelisted type or its body parses as JavaScript and does not parse as JSON.
The same explainer is equally clear on the limits:
- JSONP and JavaScript content types: not protected, because they are meant to run cross-origin.
- Other formats: only JSON, HTML and XML are covered; other data formats need other defences, and the explainer suggests unguessable anti-forgery tokens.
- Mislabelled responses: a JSON body served as text/javascript is treated as script.
- Old and embedded browsers: clients without CORB or ORB, including some embedded web views, still rely on your server-side controls.
So the modern pattern is less about array constructors and more about responses that the browser legitimately treats as script. For the wider model, see the Same-Origin Policy and Browser Security Model Guide.
Spotting exposed endpoints in review
| Check | Exposed if | Fix |
|---|---|---|
| Content-Type of authenticated data responses | text/javascript, application/javascript, text/html or text/plain | application/json; charset=utf-8 |
| X-Content-Type-Options | Missing | nosniff on every response |
| Callback parameter | Endpoint honours callback=, jsonp= or similar | Remove JSONP; use CORS for genuine cross-origin consumers |
| Top-level JSON shape | Array at the top level | Wrap in an object, as the OWASP Web Frontend Security Cheat Sheet advises |
| Cross-Origin-Resource-Policy | Missing on authenticated responses | same-origin, or same-site where subdomains must load it |
| Session cookie SameSite | SameSite=None, or unset and relying on a browser default | SameSite=Lax or Strict, set explicitly |
| Authentication method | Cookie alone authorises GET data endpoints | Bearer token in a header, or an anti-forgery token checked on reads |
| Dynamic JavaScript with user data | Server renders user data into .js files (config.js, user.js) | Serve data through a JSON endpoint; keep scripts static |
Review OpenAPI definitions for endpoints that declare non-JSON response types and for legacy callback parameters; see the OpenAPI Specification Security Guide.
How to fix and harden
- Inventory data endpoints. List every authenticated route that returns user or tenant data, with its content type, top-level shape and authentication method. Include legacy and mobile-backend routes.
- Set response headers at the edge. Add X-Content-Type-Options: nosniff everywhere and Cross-Origin-Resource-Policy: same-origin on authenticated API responses. MDN documents that with nosniff the browser blocks a script-destination request whose MIME type is not a JavaScript type, and that CORP makes compatible browsers block cross-origin no-cors requests for the resource. Roll out CORP in report-only testing first by checking which legitimate cross-origin embeds exist.
- Retire JSONP. Replace each callback endpoint with a CORS-enabled JSON endpoint and an exact origin allow list. Keep a deprecation window, log callback usage, and remove the parameter.
- Change the shape and, where needed, add a prefix. Return an object at the top level. CAPEC's client-side mitigation is a non-executable prefix (it gives while(1) as the example) that legitimate code strips before parsing; the CORB explainer confirms browsers recognise
)]}'andfor(;;);. A prefix is defence in depth for clients outside CORB and ORB. - Remove ambient credentials. Set SameSite explicitly on session cookies (MDN notes only some browsers default to Lax), prefer bearer tokens in an Authorization header for APIs, and follow CAPEC's main mitigation: a per-session unguessable token the server checks on data requests. See the Cookie Security Guide.
- Keep sensitive data minimal. CAPEC also advises passing as little sensitive data to the client as possible and making data URLs unpredictable per session where practical.
- Test and roll back safely. Ship header changes behind a feature flag, run browser tests for every first-party client, and keep the old headers deployable for a quick rollback if a partner integration breaks.
Choosing between defences
| Situation | Best first control | Trade-off |
|---|---|---|
| Single-page app calling its own API | Bearer token in header plus nosniff and CORP | Token storage in the browser needs XSS discipline |
| Classic server-rendered app with cookie sessions | SameSite=Lax cookies plus anti-forgery token on data GETs | Token plumbing in every data call |
| Public API used by partners' web pages | CORS with exact origins, no JSONP | Partners must change their integration |
| Legacy clients without CORB or ORB | Object at top level plus a parser-breaking prefix | Client code must strip the prefix |
Detection and measurement
JSON hijacking is hard to see in server logs because the request looks like a normal authenticated GET. The useful signals are context, not content:
- Fetch metadata: browsers send Sec-Fetch-Dest and Sec-Fetch-Site headers. A request for a JSON endpoint with Sec-Fetch-Dest: script and Sec-Fetch-Site: cross-site is a script inclusion attempt, and a resource isolation policy can reject it outright.
- Referer and Origin: authenticated data requests whose Referer is a third-party site.
- Callback use: any request carrying a callback parameter after JSONP was retired.
- Scanner findings: DAST checks for JSONP, missing nosniff and script-typed data responses.
Metrics that show the work is landing:
- Percentage of authenticated data endpoints serving application/json with nosniff.
- Percentage carrying Cross-Origin-Resource-Policy.
- Number of endpoints still honouring a callback parameter (target zero).
- Cross-site script-destination requests to data endpoints per week, trending down after enforcement.
- Session cookies with SameSite set explicitly, as a share of all session cookies.
Where identity fits
JSON hijacking rides on the victim's session: the attacker never sees a credential, they borrow the browser's ambient authority. The identity fix is to stop sessions being usable from contexts the user did not intend, which means explicit SameSite settings, sender-constrained or header-carried tokens for APIs, and short session lifetimes for sensitive applications. Endpoints that return API keys, tokens or service credentials to signed-in administrators deserve the strictest treatment, because a stolen response hands the attacker a non-human identity that outlives the session. See the Session Hijacking and Cookie Theft Guide.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| MITRE CAPEC | CAPEC-111, child of Functionality Misuse | Pattern definition, prerequisites and four mitigations |
| MITRE CWE | Cross-Site Request Forgery entry lists CAPEC-111 as a related pattern | Weakness the forged read relies on |
| OWASP Web Frontend Security Cheat Sheet | Protect against JSON hijacking for older browsers | Object on the outside of every JSON response |
| NIST SP 800-53 Rev 5 | SC-23 Session Authenticity, SI-10 Information Input Validation, SA-11 Developer Testing and Evaluation | Bind requests to genuine sessions, validate inputs such as callback names, test for exposure |
Common failure patterns
- Keeping JSONP "for one partner": the endpoint stays readable by any site, and CORB and ORB do not help.
- Serving JSON as text/javascript: often a framework default on older stacks, and it turns data into script.
- Relying on a browser SameSite default: MDN notes only some browsers default to Lax; set it explicitly.
- Dynamic config.js with user data: a script that embeds the user's details is inclusion-ready by design.
- Treating the array fix as the whole fix: wrapping in an object does nothing for callback or global assignment responses.
Practitioner checklist
- Inventory authenticated data endpoints with content type, shape and authentication method.
- Serve all data as application/json with X-Content-Type-Options: nosniff.
- Add Cross-Origin-Resource-Policy: same-origin to authenticated API responses.
- Remove every JSONP callback and move partners to CORS with exact origins.
- Return objects at the top level and add a parser-breaking prefix for legacy clients.
- Set SameSite explicitly on session cookies and prefer header tokens for APIs.
- Reject cross-site requests with Sec-Fetch-Dest: script to data endpoints.
- Add DAST checks for JSONP, missing nosniff and script-typed data responses.
Standards and references
- MITRE CAPEC: CAPEC-111 JSON Hijacking (aka JavaScript Hijacking)
- MITRE CWE: Cross-Site Request Forgery (CSRF)
- OWASP: Web Frontend Security Cheat Sheet
- Chromium: Cross-Origin Read Blocking (CORB) explainer
- Opaque Response Blocking (ORB) explainer
- MDN: X-Content-Type-Options
- MDN: Cross-Origin-Resource-Policy
- MDN: Set-Cookie and the SameSite attribute
Related NHI Mgmt Group resources: MITRE CAPEC Guide · CORS Misconfiguration Guide · CSRF Prevention Guide · API Security Guide · Web Application Attacks Guide