A misconfigured Cross-Origin Resource Sharing (CORS) policy lets a website the attacker controls read responses from your application using the victim's own browser session. CORS exists to relax the browser's same-origin policy in a controlled way, so every header it adds is a grant of trust, and a careless grant turns an authenticated API into a data source for any site the victim visits. The flaws are rarely exotic: an origin echoed back without checking, an unanchored regular expression, the null origin on an allowlist, or a framework default left on. This guide explains how the browser applies CORS, where misconfigurations appear in common frameworks, how to detect them and how to build an origin policy that holds up. It is written for application security engineers, developers and testers who own web applications and APIs.
Key takeaways
- CORS loosens security, it does not add it. The browser enforces it, so it never protects an endpoint from scripts, servers or tools outside a browser; authentication and authorisation still have to be enforced on the server.
- Credentialed CORS is the high-risk case. Any origin allowed together with
Access-Control-Allow-Credentials: truecan read whatever the logged-in user can read. - Reflecting the Origin header is the most damaging mistake. Echoing the request's
OriginintoAccess-Control-Allow-Originwith credentials allowed is equivalent to trusting every website on the internet. - Match origins exactly. Compare the full scheme, host and port against a fixed allowlist; avoid substring, prefix or unanchored pattern checks, and never allowlist
null. - A trusted origin inherits your risk. Every allowed origin, including subdomains and HTTP origins, becomes part of your attack surface: an XSS flaw or takeover there exposes your API.
How the attack works
Browsers send cookies and HTTP authentication with many cross-origin requests, but by default they do not let the calling page read the response. CORS is the mechanism a server uses to tell the browser "this other origin may read my response". An origin is the combination of scheme, host and port, so https://app.example.com and http://app.example.com are different origins.
Simple and preflighted requests
- Simple requests: GET, HEAD or POST using only CORS-safelisted request headers (such as
Accept,Accept-Language,Content-LanguageandContent-Typelimited toapplication/x-www-form-urlencoded,multipart/form-dataortext/plain). The browser sends the request straight away and decides afterwards whether script may read the response. - Preflighted requests: anything else (PUT, DELETE, JSON bodies, custom headers) triggers an
OPTIONSrequest carryingAccess-Control-Request-MethodandAccess-Control-Request-Headers. Preflights never carry credentials. The server answers withAccess-Control-Allow-Methods,Access-Control-Allow-Headersand optionallyAccess-Control-Max-Age. - Credentialed requests: the page must opt in (
credentials: "include"infetch()orwithCredentialsin XMLHttpRequest) and the response must carryAccess-Control-Allow-Credentials: trueplus an explicit origin. Browsers refuse a credentialed response that uses*.
The abuse pattern
The attacker lures a signed-in user to a page they control. Script on that page sends a credentialed request to the target API. If the API's response names the attacker's origin (or reflects whatever it receives) and allows credentials, the browser hands the response body to the attacker's script, which can exfiltrate account data, API keys shown in a settings page or anti-CSRF tokens that unlock further actions. Because the request comes from the victim's browser, it also passes IP allowlists and VPN-only restrictions. The browser rule against * with credentials is why vulnerable servers usually reflect origins: developers who hit that error often "fix" it by echoing the request origin, which removes the protection entirely.
A related variant needs no credentials at all. A permissive policy on an internal service that relies on network position rather than authentication lets a public website read intranet responses through any employee's browser.
Where it shows up
Most CORS flaws come from framework configuration rather than hand-written headers. The table lists the settings that matter in widely used stacks, taken from each project's documentation.
| Framework or library | Risky setting | Safer setting |
|---|---|---|
| ASP.NET Core CORS policy | AllowAnyOrigin(); SetIsOriginAllowed(_ => true) with AllowCredentials(), which Microsoft documents as bypassing built-in checks | Named policy with WithOrigins(...) (no trailing slash), WithMethods/WithHeaders, applied with [EnableCors("Policy")] or RequireCors |
Express cors middleware | Default origin: "*"; origin: true, which reflects the request origin; unanchored RegExp | Array of exact origin strings, or a function that checks an allowlist; credentials: true only where needed |
Spring MVC @CrossOrigin and global config | Annotation defaults allow all origins; broad allowedOriginPatterns with allowCredentials(true) | Explicit allowedOrigins per mapping; Spring Security's built-in CORS support rather than a separate filter |
| Rack::Cors (Ruby, Rails) | origins '*' on sensitive resources; regex without \A and \z anchors | Exact origins in the allow block, resource rules scoped to the paths that need them |
FastAPI / Starlette CORSMiddleware | Loose allow_origin_regex; allow_methods=["*"] and allow_headers=["*"] by habit | Explicit allow_origins, methods and headers; the documentation states none may be ["*"] when allow_credentials=True |
| Django (django-cors-headers, the package the Django REST framework docs name) | Allow-all settings copied from development | Explicit origin allowlist kept per environment |
| API gateways, CDNs and reverse proxies | Header injected at the edge for every path, independent of the application's own policy | One owner for CORS headers; edge rules limited to named paths and origins |
Typical misconfiguration patterns
- Origin reflection: code or a library setting copies
Originstraight intoAccess-Control-Allow-Origin. - Weak matching: checks that a value "contains", "starts with" or "ends with" a domain. OWASP and the Rack::Cors maintainers both warn that unanchored patterns match attacker-registered lookalike domains.
- Trusting
null: thenullorigin is sent from sandboxed iframes and some redirect and file contexts, so allowlisting it (often to make local testing work) lets attacker content qualify. OWASP WSTG treats it as an exploitable wildcard. - Wildcard subdomains: trusting
*.example.comtrusts every marketing microsite, legacy host and dangling DNS record under it. - HTTP origins: allowing
http://versions of trusted hosts lets anyone able to tamper with network traffic inject script into the trusted origin. - Missing
Vary: Origin: a dynamically chosen origin withoutVary: Originlets a shared cache serve one origin's grant to another. - Policy applied to the whole site: the OWASP HTML5 Security Cheat Sheet advises setting CORS headers only on the URLs that need cross-origin access, not for the whole domain.
Impact
| Configuration | What an attacker's page can do | Severity |
|---|---|---|
| Reflected or arbitrary origin plus credentials | Read any response the signed-in user can see, including personal data, tokens and CSRF tokens | High to critical |
null or weakly matched origin plus credentials | Same as above, with a little more set-up | High |
| Trusted subdomain or HTTP origin plus credentials | Read data once the trusted origin is compromised through XSS, takeover or traffic tampering | Medium to high |
* without credentials on internal or IP-restricted services | Read intranet data through an employee's browser | Medium to high |
* without credentials on genuinely public data | Nothing beyond what anyone can fetch directly | Low or none |
MITRE classifies the weakness as CWE-942, Permissive Cross-domain Security Policy with Untrusted Domains, whose listed consequences include bypassing the same-origin policy and reading application data. Reading JSON across origins is also the goal of CAPEC-111 (JSON Hijacking), which CORS misconfiguration makes trivial. Data exposed this way often includes API keys and personal access tokens displayed in account pages, which turns a browser flaw into a non-human identity exposure that outlives the session. See the API Security Guide.
Detection
Testing
- Origin probing: OWASP WSTG test WSTG-CLNT-07 sends requests with a range of
Originvalues (an unrelated domain, a lookalike suffix or prefix of your domain,null, an HTTP version of a trusted host) and records theAccess-Control-Allow-OriginandAccess-Control-Allow-Credentialsvalues returned. Any echo of an untrusted value is a finding. - Preflight review: inspect
OPTIONSresponses for overly broadAccess-Control-Allow-Methods,Access-Control-Allow-Headersand longAccess-Control-Max-Agevalues. Remember that command-line tools ignore CORS, so they show what the server sends, not what the browser would allow. - Code and configuration search: grep repositories and infrastructure code for
AllowAnyOrigin,SetIsOriginAllowed,origin: true,@CrossOriginwithout arguments,origins '*',allow_origin_regexand hand-writtenAccess-Control-Allow-Originheaders. Add these as static analysis or linting rules so they fail the build. - DAST coverage: include authenticated CORS checks in dynamic scans, because the dangerous combinations only appear on endpoints that need a session. See the Web Application Security Testing Guide.
Monitoring
- Log the
Originheader and the CORS decision (allowed or refused) for sensitive endpoints, and alert on credentialed requests from origins outside the allowlist. - Monitor DNS for dangling records under any domain you trust in CORS, since a takeover there becomes a trusted origin.
- Track header drift at the edge: a CDN or gateway rule that starts emitting CORS headers on new paths should raise a change alert.
Metrics
- Percentage of internet-facing applications with a documented, owned CORS allowlist.
- Number of endpoints returning a reflected or
nullorigin with credentials (target: zero). - Count of wildcard-subdomain or HTTP origins in production policies, trending down.
- Share of repositories with build-breaking rules for permissive CORS settings.
- Median time to fix CORS findings from scans and penetration tests.
Prevention and hardening
Design rules
- Do you need CORS at all? Serving the front end and API from the same origin, or routing through a same-origin backend-for-frontend, removes the problem. Add CORS only for real cross-origin consumers.
- Fixed allowlist: ASVS 5.0 requirement V3.4.2 asks that
Access-Control-Allow-Originis either a fixed value or, if derived fromOrigin, validated against an allowlist of trusted origins, and that responses using*contain nothing sensitive. - Exact comparison: compare the full origin string (scheme, host, port) against configured values. If subdomain patterns are unavoidable, anchor both ends and restrict the pattern to HTTPS.
- Credentials only where needed: enable
Access-Control-Allow-Credentialson the specific routes that require cookies. Token-based APIs that send anAuthorizationheader often need no credentialed CORS. - Minimal methods and headers: list methods and headers explicitly rather than using
*, and expose response headers withAccess-Control-Expose-Headersonly when script needs them. - Always send
Vary: Originwhen the allowed origin is chosen per request. - Never trust
nullor HTTP origins in production policies. - Keep CSRF defences: ASVS V3.5.1 and V3.5.2 point out that simple requests reach the server without a preflight, so state-changing endpoints still need anti-forgery tokens, a non-safelisted header requirement, or checks on
OriginandContent-Type. The OWASP HTML5 Security Cheat Sheet makes the same point. - Do not use Origin as authentication: a page cannot set the
Originheader (the Fetch Standard lists it as forbidden), but any non-browser client can, so the server must still authenticate and authorise every request.
Choosing a policy model
| Model | When to choose it | Trade-off |
|---|---|---|
| No CORS (same origin or backend-for-frontend) | First-party web apps and their APIs | Needs routing or proxy work; the strongest option |
| Static allowlist of exact origins | A known, small set of partner or first-party front ends | Changes need a deployment; easy to audit |
| Anchored pattern for one parent domain | Many tenant subdomains under a domain you fully control | Inherits risk of every subdomain; needs DNS hygiene |
| Allowlist held in a database or config service | Multi-tenant platforms where customers register their own origins | Registration becomes a security workflow: validate, approve, expire |
* without credentials | Public, non-sensitive data such as open datasets or public assets | Never for internal, IP-restricted or personalised content |
Small organisations with a handful of applications usually do best with no CORS or a static allowlist in code review. Large organisations should centralise policy in a shared library or gateway module with a single owner, so teams do not each write their own matching logic.
Rollout
- Inventory: scan every internet-facing host and API for CORS response headers and list the policy, owner and consumers for each.
- Triage: fix reflected,
nulland wildcard-with-credentials cases first, then HTTP and wildcard-subdomain origins. - Observe before enforcing: log the
Originvalues reaching each endpoint for a few weeks to learn which origins genuinely call it. - Enforce in rings: deploy the tightened allowlist to staging, then a canary slice of production, watching browser console errors and client error rates.
- Roll back safely: keep the previous policy as a configuration value so it can be restored quickly, but never roll back to reflection; add a missed origin to the allowlist instead.
- Prevent regression: add build-breaking rules, DAST checks and a CORS item in design review for new services.
CORS sits alongside the other browser controls such as Content Security Policy, cookie SameSite attributes and Cross-Origin-Opener-Policy. See the Security Headers Guide.
Common failure patterns
- Fixing a browser error by reflecting the origin: a developer sees the "wildcard with credentials" error and switches to echoing
Origin, creating the worst case. - Development settings in production:
localhost,nullor allow-all values added for local testing ship with the release. - Two layers, two policies: the application sends a strict policy while the gateway or CDN adds a permissive one, and the browser sees whichever wins.
- Trusting everything under the parent domain: a forgotten subdomain with an XSS flaw or a dangling DNS record becomes a trusted origin for the core API.
- Treating CORS as access control: internal APIs left unauthenticated because "CORS blocks other sites", which does nothing against non-browser clients and fails as soon as a wildcard appears.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| OWASP ASVS 5.0 | V3.4.2, V3.5.1, V3.5.2, V3.5.5 | Origin allowlisting, CSRF when relying or not relying on preflight, postMessage origin checks |
| MITRE CWE / CAPEC | CWE-942; CAPEC-111 | Permissive cross-domain policy; cross-origin JSON reading |
| NIST SP 800-53 Rev. 5 | CM-6, AC-4, SA-11 | Secure configuration settings, information flow enforcement, developer testing |
| NIST CSF 2.0 | PR.PS-01, ID.RA-01 | Configuration management; identifying and recording vulnerabilities |
| OWASP WSTG | WSTG-CLNT-07 | Test procedure for CORS |
Practitioner checklist
- Inventory every host and API that emits
Access-Control-Allow-Originand assign an owner. - Remove any configuration that reflects the request
Originor allowlistsnull. - Replace substring, prefix and unanchored regex checks with exact origin comparison.
- Restrict
Access-Control-Allow-Credentials: trueto routes that need cookies, with explicit origins only. - Remove HTTP origins and review every wildcard subdomain against current DNS records.
- Send
Vary: Originon every dynamically chosen CORS response. - Keep CSRF protection on state-changing endpoints regardless of CORS.
- Make sure only one layer (application or edge) owns CORS headers for each path.
- Add build-breaking rules for permissive framework settings and authenticated CORS checks to DAST.
Standards and references
- MDN Web Docs: Cross-Origin Resource Sharing (CORS)
- OWASP: Application Security Verification Standard 5.0
- OWASP: Web Security Testing Guide, Testing Cross Origin Resource Sharing
- OWASP: HTML5 Security Cheat Sheet
- MITRE CWE: CWE-942 Permissive Cross-domain Security Policy with Untrusted Domains
- Microsoft Learn: Enable Cross-Origin Requests (CORS) in ASP.NET Core
- Spring Framework: CORS
- Rack::Cors: project documentation
Related NHI Mgmt Group resources: Security Headers Guide · Cross-Site Scripting (XSS) Prevention Guide · API Security Guide · Web Application Attacks Guide · CWE Top 25 Guide