Skip to content

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:

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 Origin request header sent by the browser on a cross-origin request.
  • Sink: The Access-Control-Allow-Origin (and related Access-Control-*) response headers, or an equivalent cross-domain policy file.
  • Data Flow / Missing Controls: Look for a literal *, the Origin header 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-Origin when the request's origin exactly matches an entry in it.
  • Never use * in production, and never reflect the Origin header without validating it first.
  • Add Vary: Origin to the response so shared caches do not serve one origin's response to another.

Limit Methods, Headers, and Credentials

  • Restrict Access-Control-Allow-Methods and Access-Control-Allow-Headers to what each endpoint actually needs.
  • Set Access-Control-Allow-Credentials: true only 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.xml and clientaccesspolicy.xml unless 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-src and frame-ancestors as 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 as https:, and a wildcard host such as *.cdn.example all widen the list past what you control.
  • In a postMessage handler, compare event.origin against an exact expected origin before reading event.data, and set the targetOrigin argument 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.example and confirm no matching Access-Control-Allow-Origin is returned.
  • Confirm Access-Control-Allow-Credentials: true never appears together with Access-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() with credentials: '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 Origin header 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 as evil-example.com or example.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: * alongside Access-Control-Allow-Credentials: true is rejected - but the Python CORS libraries do not let a configuration reach it. flask-cors, Starlette (and so FastAPI) and django-cors-headers all 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 null origin: adding null to the allowed list to support local file testing or a sandboxed use case - an attacker can force a request to carry Origin: null (via a sandboxed iframe, a data: 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.setHeader overwrites), 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 cors package and manual allowlist middleware
  • Python - Flask (flask-cors), FastAPI (CORSMiddleware) and Django (django-cors-headers) allowlist configuration

Additional Resources