Inline JSONP XSS: Risks & Safer Alternatives - ITU Online IT Training

What is JSONP (JSON with Padding)

Ready to start learning? Individual Plans →Team Plans →

Inline JSONP XSS issues usually start with a simple mistake: a team wants cross-domain data to “just work,” so they load it with a <script> tag and assume the response is safe. That convenience is exactly why inline JSONP became popular, and exactly why it can become a problem when the endpoint is compromised or the callback is not tightly controlled.

This post explains inline JSONP XSS in plain terms. You’ll see what JSONP is, how it works behind the scenes, where it was useful, why it creates security risk, and what to use instead when you control the application.

You’ll also get practical guidance on spotting weak JSONP implementations, understanding callback handling, and deciding when a legacy integration should be retired. For secure web development references, see MDN Web Docs on CORS, OWASP JSONP guidance, and web.dev on the same-origin policy.

What JSONP Is and Why It Exists

JSONP stands for JSON with Padding. It is not a data format in the same way JSON is. It is a workaround that lets a browser load data from another domain by treating the response as executable JavaScript instead of a standard XHR or fetch response.

The reason JSONP existed is the same-origin policy. Browsers block most cross-origin AJAX requests unless the remote server explicitly allows them. That rule protects users from hostile sites reading data from another site they are logged into. Early web apps still needed public third-party data, so developers used script loading as a loophole.

JSON versus JSONP

Plain JSON looks like data:

{"name":"Ada","role":"admin"}

A JSONP response wraps that data in a function call:

handleData({"name":"Ada","role":"admin"});

The browser executes the second form because it looks like JavaScript. That is the key difference. JSONP is a browser-side technique, not a general-purpose server-to-server exchange method. If your backend calls another backend, you do not need JSONP. You use HTTP directly and parse the response normally.

JSONP works because browsers execute script responses, even cross-origin. That makes it useful and dangerous at the same time.

For a practical browser-security baseline, review MDN’s same-origin policy documentation and RFC 8259 for JSON.

How JSONP Works Behind the Scenes

The mechanics are simple, but the security implications are not. The browser creates a script request with a src URL that includes a callback name. The server reads that callback name and returns JavaScript that calls it with the data payload.

Because browsers allow cross-origin script loading, the response runs automatically. That is how JSONP bypasses the normal cross-origin restriction. It does not “break” the same-origin policy. It uses a different browser rule entirely.

Request and response flow

  1. The page defines a global callback such as window.handleFeed = function(data) { ... }.
  2. The page inserts a script tag like <script src="https://example.com/api?callback=handleFeed"></script>.
  3. The server returns JavaScript such as handleFeed({"items":[1,2,3]});.
  4. The browser executes that JavaScript as soon as it loads.
  5. The callback receives the data and updates the page.

This is why JSONP can feel elegant in a demo and fragile in production. The browser is not parsing a structured API response. It is running code from another origin.

Warning

If the remote endpoint is compromised, the browser executes attacker-controlled JavaScript with the same trust level as your page. That is the core risk behind inline JSONP XSS.

For browser behavior and script execution model details, see WHATWG HTML Standard and MDN on the script element.

Anatomy of a JSONP Request and Response

A typical JSONP URL has three parts: the endpoint, the query parameters, and the callback name. The callback is the piece that makes the response executable. Without it, the server would return plain JSON, which a script tag cannot consume in the same way.

Example:

https://api.example.com/public/news?category=security&callback=renderNews

In this example, renderNews is a client-side function name. The browser expects that function to already exist or to exist by the time the script response arrives.

What the server sends back

The response usually looks like this:

renderNews({"headline":"Patch released","severity":"high","tags":["web","security"]});

That payload can contain objects, arrays, nested structures, and strings, as long as the final output is valid JavaScript syntax. The server must be careful to escape values properly. If it inserts untrusted content into the callback wrapper without validation, it can create a direct XSS vector.

JSONP is executable code, so even small formatting mistakes matter. A missing parenthesis, bad escaping, or malformed callback name can break the entire request.

Why the callback name matters

The callback name is not decorative. It determines which function receives the data. Good implementations restrict that name to safe characters, usually letters, numbers, underscores, and dots if nested namespaces are allowed.

Weak implementations sometimes accept arbitrary callback values. That creates injection risk because attackers can attempt to force execution of unexpected code fragments. OWASP documents JSONP as a common source of reflected script injection when the callback parameter is not validated.

For a strict reference on JavaScript syntax and JSON structure, review MDN JSON reference and OWASP’s JSONP attack notes.

Common Use Cases for JSONP

JSONP showed up anywhere developers needed public cross-domain data before CORS became common. It was especially useful when the API owner controlled the server response but the browser could not read it directly through AJAX.

Typical historical use cases included search suggestions, public news feeds, weather widgets, stock tickers, and social widgets. These were often read-only and not sensitive, which made them safer candidates than authenticated APIs.

Why developers used it

  • Public data widgets that needed to embed easily on many sites.
  • Legacy browser support before CORS was widely available.
  • Low integration effort when the server already supported callback wrapping.
  • Fast prototypes that needed cross-origin access without backend changes.
  • Third-party feeds where the response was intended for display, not sensitive processing.

JSONP was rarely a good choice for private data. It was a convenience layer for public, read-only information. The moment authentication, authorization, or sensitive user state entered the picture, the risk went up fast.

Note

JSONP is still seen in older widgets and vendor APIs that were designed before CORS became the default. If you inherit one of those integrations, treat it as legacy code and review it carefully.

For context on current browser security patterns, see MDN CORS and NIST Cybersecurity Framework.

Benefits of JSONP

JSONP earned a place in early web development because it was easy. You could get a cross-domain response without configuring headers, custom servers, or special browser support.

That simplicity mattered when many teams were trying to ship lightweight pages quickly. A single script tag could load data from another domain, and the callback could render it immediately.

Where JSONP was attractive

Benefit Why it mattered
Simplicity Minimal client-side code and little server setup.
Browser compatibility Worked in older browsers that did not fully support modern cross-origin APIs.
Fast implementation Useful for prototypes and public widgets.
Low backend impact Often required only a callback wrapper around an existing JSON response.

The tradeoff was that this convenience came at the cost of control. JSONP is flexible only because it treats the response as code. That is exactly why security teams tend to reject it now unless there is a legacy constraint.

For broader browser standards and web security guidance, review web.dev on Fetch and OWASP Top 10.

Limitations and Security Risks of JSONP

JSONP has serious limitations. The biggest one is that it only works with GET-style requests through script loading. You cannot use it like a normal AJAX call for POST, PUT, PATCH, or DELETE.

That alone makes it unsuitable for many modern applications. But the bigger issue is security. When you load remote JavaScript, you are trusting that the remote server will always return safe code. If that endpoint is compromised, misconfigured, or manipulated, your page can execute attacker-controlled script.

Why security teams dislike JSONP

  • Remote code execution in the browser is built into the design.
  • Callback injection is possible if callback names are not validated.
  • No standard error handling like you get with fetch or XHR.
  • Poor fit for sensitive data because script requests expose more attack surface.
  • Debugging is harder because the browser treats the response as executable code, not a data payload.

JSONP also complicates logging and incident response. If a malicious response runs in the browser, it can be hard to distinguish a data issue from a script compromise unless you already have strong telemetry.

JSONP is not insecure because it is old. It is insecure because it asks the browser to trust remote code as data.

For security validation guidance, see OWASP Cheat Sheet Series, NIST CSRC, and MDN on Subresource Integrity.

JSONP vs. CORS: What’s the Difference?

CORS, or Cross-Origin Resource Sharing, is the modern standard for controlled cross-origin browser requests. It uses HTTP headers to tell the browser which origins are allowed, which methods are permitted, and which headers can be used.

JSONP, by contrast, avoids the browser’s cross-origin read restriction by using script loading. That gives it broader reach on old systems, but far less control.

Direct comparison

JSONP CORS
Uses script tags and callback functions. Uses browser-supported HTTP requests with server-approved headers.
Limited to GET-style fetch patterns. Supports GET, POST, PUT, DELETE, and more depending on policy.
Executes returned JavaScript. Returns data as data, usually JSON.
Higher XSS risk if the endpoint is compromised. Safer and more explicit trust model.
Legacy workaround. Current standard for cross-origin browser access.

In practical terms, CORS is usually the better long-term option because it supports real HTTP behavior and keeps data separate from executable code. JSONP only makes sense when you cannot change the server or when you are stuck with a legacy API that never adopted CORS.

For official browser and protocol references, see MDN CORS and RFC 6454: The Web Origin Concept.

How to Implement JSONP Step by Step

If you need to understand how JSONP works in code, keep the example small and explicit. The client defines a callback, creates a script tag, and cleans up after execution. The server reads the callback name and returns JavaScript that calls it.

Client-side example

function handleWeather(data) {<br> console.log(data);<br> document.getElementById('weather').textContent = data.temp + '°';<br>}<br><br>const script = document.createElement('script');<br>script.src = 'https://api.example.com/weather?callback=handleWeather';<br>document.body.appendChild(script);

In a real application, you should generate unique callback names so multiple requests do not collide. You should also remove the script element after it runs to keep the DOM clean.

Server-side behavior

The server should read the callback parameter, validate it against a strict pattern, and return something like:

handleWeather({"temp":72,"condition":"Clear"});

Never echo raw callback input back without checking it. A good server implementation permits only safe identifiers, such as ^[a-zA-Z_$][0-9a-zA-Z_$.]*$, and rejects anything else.

  1. Define the callback before loading the script.
  2. Insert the script tag dynamically.
  3. Validate the callback name on the server.
  4. Return executable JavaScript with valid data.
  5. Remove the script tag after completion.

Pro Tip

If you are maintaining a legacy JSONP endpoint, log the callback parameter and response origin. That makes abuse patterns easier to detect during incident review.

For implementation details on secure client-side handling, review MDN createElement and MDN JavaScript functions.

Best Practices and Safety Tips for Working with JSONP

If you must use JSONP, keep the blast radius small. Restrict it to trusted, public, read-only endpoints and treat it as a temporary compatibility layer, not a primary API strategy.

Practical safety checklist

  • Validate callback names with a strict allowlist pattern.
  • Use only trusted endpoints that you control or have audited.
  • Keep payloads small to limit exposure and improve load time.
  • Handle timeouts manually because script requests do not behave like normal AJAX.
  • Remove script tags after the callback fires.
  • Prefer public data only; do not use JSONP for sensitive or authenticated content.

Another common mistake is assuming you will get a clean error event if the endpoint fails. Script-tag requests are not the same as fetch. You may need timeout logic, fallback states, or an alternate transport if the script never loads.

Also remember that JSONP can be abused through third-party supply-chain compromise. Even when the endpoint is legitimate, if it is serving a script wrapper, a breach at the provider can become a client-side compromise on every page that includes it.

For current web security guidance, see CISA Secure Our World and OWASP.

Modern Alternatives to JSONP

For most browser applications, CORS is the replacement you want. It keeps the response as data, uses server-side access control, and supports standard HTTP methods. That makes it easier to secure, test, and maintain.

If you cannot call an external service directly from the browser, a server-side proxy is often the next best option. Your backend calls the remote service, applies validation and authentication, and then returns a safe response to the frontend.

Common modern patterns

  • Fetch with CORS for direct browser-to-API communication.
  • Reverse proxy or API gateway when the browser should not see the upstream service directly.
  • Backend aggregation when multiple APIs need to be combined before reaching the front end.
  • Signed URLs or tokenized access for controlled public content delivery.

The reason these approaches are preferred is simple: they preserve the data/code boundary. You request data, you receive data, and you decide how to render it. That is much easier to reason about than loading remote code that happens to contain your data.

For vendor-level implementation guidance, see Microsoft Learn on CORS, AWS documentation, and MDN Fetch API.

When JSONP Still Might Be Relevant

There are still a few narrow cases where JSONP shows up. The most common is a legacy API that cannot be changed. If the service is old, public, and already depended on by external sites, JSONP may remain in place until a migration is feasible.

Older browsers or constrained runtime environments can also limit your options, though this is increasingly rare in standard enterprise web apps. Some public widgets and embeds still use script-based delivery because that model is easy to drop into third-party sites.

When to tolerate it temporarily

  • Legacy third-party APIs with no CORS support.
  • Public widgets that were designed around script injection.
  • Migration bridges while an older integration is being replaced.
  • Compatibility requirements in older browsers that cannot be retired yet.

Even then, the use should be tightly bounded. Review callback validation, confirm the endpoint is read-only, and plan the exit path. Legacy patterns have a way of becoming permanent if nobody owns the retirement plan.

Key Takeaway

If you still depend on JSONP, treat it as technical debt with a deadline. Document why it exists, who owns it, and what will replace it.

For workforce and modern security practice context, see NIST software security guidance and CISA.

Conclusion

JSONP was a practical workaround for a real browser limitation. It allowed public data to move across domains before CORS was widely implemented, and it helped power many early widgets, feeds, and lightweight integrations.

The tradeoff is clear: JSONP is simple, but it is also risky. It turns a data request into executable JavaScript, which creates XSS exposure, makes debugging harder, and limits you to GET-style patterns. That is why inline JSONP XSS concerns still matter whenever a legacy endpoint is involved.

For new development, use CORS or a server-side proxy instead. Reserve JSONP for narrow legacy cases, validate callbacks aggressively, and keep the payload public and read-only.

Practical takeaway: if you control the API, retire JSONP. If you do not control it, isolate it, document it, and plan to replace it as soon as possible.

All certification names and trademarks mentioned in this article are the property of their respective trademark holders. This article is intended for educational purposes and does not imply endorsement by or affiliation with any certification body.

CEH™ and Certified Ethical Hacker™ are trademarks of EC-Council®.

[ FAQ ]

Frequently Asked Questions.

What is JSONP and how does it work?

JSONP, which stands for JSON with Padding, is a technique used to bypass the same-origin policy in web browsers, allowing web pages to request data from servers in different domains. Unlike traditional AJAX requests that are restricted to the same domain due to security policies, JSONP leverages the

Discover More, Learn More