CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains
Overview
This weakness occurs when a web-client protection mechanism names domains it should not trust. MITRE scopes it to any such mechanism - a Content Security Policy or a cross-domain policy file - but the form most developers meet is a web API that sets Access-Control-Allow-Origin: *, reflects the caller's Origin header back unchecked, or uses an overly broad pattern in its CORS configuration. The same mistake appears in older cross-domain policy files (Flash crossdomain.xml, Silverlight clientaccesspolicy.xml), in postMessage handlers that accept messages from any sender, and in a CSP whose script-src or connect-src lists a host the application does not control. This page treats CORS as the primary case and covers the others where the fix differs.
Relationship to Other CWEs
MITRE places CWE-942 at the Variant level in Research Concepts (view-1000), under three parents that each name a different aspect of the same policy:
- CWE-942 (this page) - a web-client protection mechanism, most often a CORS policy, that names domains it should not trust.
- CWE-183 (Permissive List of Allowed Inputs) - the list of trusted origins is wider than it should be. Reach for this parent when the finding is a pattern that matches more than it was written to match.
- CWE-863 (Incorrect Authorization) - the policy is an authorization decision, and it grants to the wrong callers.
- CWE-923 (Improper Restriction of Communication Channel to Intended Endpoints) - the channel between the browser and the application is open to endpoints that were never intended.
MITRE also records CWE-942 as CanPrecede CWE-668 (Exposure of Resource to Wrong Sphere), which is the consequence rather than the cause: data reaches an origin outside the sphere that should have held it.
CWE-346 (Origin Validation Error) is the failure to check who sent a request at all, or to check it with something that does not identify the sender - a Referer header, a guessable token. CWE-942 is the narrower case where the check exists and its allowlist is too wide. A reflected Access-Control-Allow-Origin sits on the line between them and scanners report it under either number; the remediation on this page applies to both readings. The CWE-346 page also covers the CSRF boundary, which is not repeated here.
OWASP Classification
A02:2025 - Security Misconfiguration
Risk
High: A malicious website can read data from the API in a victim's browser, including authenticated responses if credentials are also allowed, because the browser hands the response to the calling page once the server's own headers say that origin may read it.
Remediation Steps
Core Principle: Never extend cross-origin trust beyond a small, explicit set of known origins; a wildcard or a reflected origin header is not a policy, it is the absence of one.
Trace the Data Path
- Source: The
Originrequest header sent by the browser on a cross-origin request. - Sink: The
Access-Control-Allow-Origin(and relatedAccess-Control-*) response headers, or an equivalent cross-domain policy file. - Data Flow / Missing Controls: Look for a literal
*, theOriginheader copied straight into the response, or a regex/wildcard pattern broader than the domains that actually need access.
Restrict Allowed Origins with an Allowlist (Primary Defense)
- Maintain an explicit list of trusted origins and only echo
Access-Control-Allow-Originwhen the request's origin exactly matches an entry in it. - Never use
*in production, and never reflect theOriginheader without validating it first. - Add
Vary: Originto the response so shared caches do not serve one origin's response to another.
Limit Methods, Headers, and Credentials
- Restrict
Access-Control-Allow-MethodsandAccess-Control-Allow-Headersto what each endpoint actually needs. - Set
Access-Control-Allow-Credentials: trueonly alongside a specific, validated origin - browsers reject the combination of credentials with a wildcard origin, but a reflected origin bypasses that protection. - Give public, unauthenticated endpoints a separate, more permissive policy than authenticated ones rather than relaxing the policy for the whole application.
Apply the Same Rule to Policy Files and CSP
- Delete
crossdomain.xmlandclientaccesspolicy.xmlunless a Flash or Silverlight client still depends on them. Both runtimes are end-of-life, and a file left in the web root keeps granting whatever it last said. Where one is still required, name exact domains - never<allow-access-from domain="*"/>. - Treat a Content Security Policy's
script-src,style-src,connect-src,frame-srcandframe-ancestorsas the same kind of allowlist: every host named there can supply code that runs as your page, receive data your page sends, or frame it. A*, a bare scheme such ashttps:, and a wildcard host such as*.cdn.exampleall widen the list past what you control. - In a
postMessagehandler, compareevent.originagainst an exact expected origin before readingevent.data, and set thetargetOriginargument when sending rather than passing*.
Monitor and Audit CORS Usage
- Log the origin of cross-origin requests, especially ones that were rejected, to catch scanning or exploitation attempts.
- Periodically review the allowlist and remove origins that no longer need access.
Test with Malicious Inputs
- Send a request with
Origin: https://evil.exampleand confirm no matchingAccess-Control-Allow-Originis returned. - Confirm
Access-Control-Allow-Credentials: truenever appears together withAccess-Control-Allow-Origin: *. Read a pass here as ruling out one specific mistake, not as a clean result: a policy that reflects the origin never emits a literal*, so it satisfies this check unchanged. - From a browser on an untrusted origin, attempt a
fetch()withcredentials: 'include'against the API and confirm the browser blocks reading the response. - Re-scan with the security scanner to confirm the finding is resolved.
Common Vulnerable Patterns
// VULNERABLE - pseudo-code
on_request(req, res):
res.header("Access-Control-Allow-Origin", req.header("Origin")) // reflected: every origin passes
res.header("Access-Control-Allow-Credentials", "true")
// Attack: any website fetches the API with credentials included
// Result: attacker-controlled page reads authenticated response data
Why this is vulnerable: these headers are not access control on the API. They are an instruction to the browser to stop enforcing a restriction it was applying on the victim's behalf. An attacker could always send this request from their own server and read the reply; what the policy grants is something different and worse - permission for a page the attacker controls to send the request from the victim's browser, with the victim's cookies attached, and then read the answer.
That is why the credentials flag is the line that matters, and why the example reflects the Origin header rather than sending a literal *. The two cannot be combined: browsers reject Access-Control-Allow-Origin: * alongside Allow-Credentials: true outright, so that pairing fails closed and is the configuration that looks alarming while achieving nothing. Reflecting whatever arrived in Origin produces a header naming one specific origin, which satisfies the browser's check and permits every origin in turn. It is a wildcard that works, which is why it is the form that turns up in real findings.
Secure Patterns
// SECURE - pseudo-code
ALLOWED_ORIGINS = ["https://app.example.com", "https://www.example.com"]
on_request(req, res):
origin = req.header("Origin")
if origin in ALLOWED_ORIGINS:
res.header("Access-Control-Allow-Origin", origin)
res.header("Access-Control-Allow-Credentials", "true")
res.header("Vary", "Origin")
// else: no CORS header at all - the browser blocks the response
Why this works: An origin that is not in the fixed list gets no Access-Control-Allow-Origin header back, so the browser keeps enforcing the same-origin rule on the response. Credentials are only ever paired with an origin that matched, so an authenticated response cannot reach a site that was never on the list.
Common Pitfalls
- Suffix or substring origin matching: checking that the
Originheader ends with or contains a trusted domain instead of matching an exact origin or a properly anchored subdomain pattern - this also matches attacker-registered domains such asevil-example.comorexample.com.attacker.net, since both contain the trusted string as a substring. - Assuming a wildcard setting fails closed once credentials are enabled: the browser rule is real -
Access-Control-Allow-Origin: *alongsideAccess-Control-Allow-Credentials: trueis rejected - but the Python CORS libraries do not let a configuration reach it.flask-cors, Starlette (and so FastAPI) anddjango-cors-headersall stop emitting*and start reflecting the requesting origin the moment credentials are turned on, so a setting that reads as "wildcard" produces the exploitable reflected form rather than a broken one. Judge the policy from the response headers, not from the setting's name. - Allowlisting the literal
nullorigin: addingnullto the allowed list to support local file testing or a sandboxed use case - an attacker can force a request to carryOrigin: null(via a sandboxed iframe, adata:URL, or certain redirect chains), satisfying the allowlist from a page they fully control. - Applying the allowlist check inconsistently across response paths: validating the origin correctly on the main success response while a more permissive header is set in an error handler, on a fallback route, or by a reverse proxy or CDN in front of the application - none of which run the same check. The risk here is not one header overriding another. Where a framework writes the same header twice the last write usually wins (Node's
res.setHeaderoverwrites), and where two are genuinely emitted the browser joins them into one comma-separated value that matches no origin and blocks the response, which is a fail-closed outage rather than a bypass. What leaks data is a response path that emits a permissive header because the check was never wired into it.
Language-Specific Guidance
- JavaScript - Express/Node.js CORS configuration with the
corspackage and manual allowlist middleware - Python - Flask (
flask-cors), FastAPI (CORSMiddleware) and Django (django-cors-headers) allowlist configuration