Your web app hung a 'come on in' sign on the browser's same-origin fence
Tenable WAS plugin 98983 flags a web application whose Access-Control-Allow-Origin response reflects an arbitrary requesting origin (or is set to *) *while also* returning Access-Control-Allow-Credentials: true — or otherwise permits untrusted origins to make authenticated cross-site XHR/fetch calls. The affected component is any HTTP endpoint on the scanned application that returns those headers; there is no single vendor/version to patch. This is a configuration class, not a shipped-code CVE, so 'affected versions' = every deployment that emits the offending header combination, regardless of framework (Nginx add_header misuse, Express cors({origin: true, credentials: true}), Spring @CrossOrigin(origins='*'), ASP.NET AllowAnyOrigin().AllowCredentials(), API-gateway wildcards, etc.).
Tenable rates this Medium (CVSS v3 6.1 / v4 5.1) and that is honest. It maps to CWE-346 (Origin Validation Error) and OWASP A07. The severity is fair *as a class* — the impact ceiling is CSRF-with-response-read against authenticated users, which is meaningful but requires a victim to visit an attacker-controlled page while logged in to the target. It is not RCE, it is not unauthenticated data exfil at the server, and browser same-site cookie defaults (SameSite=Lax default since Chrome 80 / 2020) already blunt the credentialed variant on most modern stacks. Medium is the right bucket; we hold it there.
4 steps from start to impact.
Confirm the reflective/permissive CORS policy
Origin: https://evil.tld request header and observes the response. If the server echoes back Access-Control-Allow-Origin: https://evil.tld (or *) together with Access-Control-Allow-Credentials: true, the misconfiguration is confirmed. Tools of choice: Burp Suite Pro with the *CORS* scanner extension, corsy (s0md3v/Corsy), or manual curl -H 'Origin: https://evil.tld' -I.- Endpoint reachable by attacker (does not need auth)
- Application returns CORS headers on the endpoint
- Modern frameworks default to strict-origin allowlists
- WAFs and API gateways frequently strip or normalize CORS headers
cors-misconfig covers it.Identify a state-changing or data-returning authenticated endpoint
/api/me, /api/transfer, /graphql, /admin/users). Attacker maps these via directory brute-force (ffuf, feroxbuster) or by reading published API docs / OpenAPI specs.- Session-bearing endpoints exist on the vulnerable origin
- Endpoints do not require additional CSRF token / custom header the attacker cannot forge cross-origin
- Well-designed APIs require a custom header like
X-Requested-Withthat triggers a preflight and blocks bypass - Bearer tokens in
Authorizationheader are NOT sent cross-origin automatically — only cookies are
Origin headers from unknown domains combined with 200-response Set-Cookie-authenticated calls.Host attacker-controlled page and lure an authenticated victim
https://evil.tld/pwn.html containing JavaScript that issues fetch('https://target/api/me', {credentials:'include'}).then(r=>r.text()).then(exfil). Victim must be logged in to the target application *and* click / visit the attacker's link in the same browser profile. Delivery is phishing, malvertising, or a watering-hole compromise.- Victim has active session with target
- Victim visits attacker page in same browser context
SameSite=LaxorNoneon session cookie (Strict blocks this entirely)
- Chrome/Edge/Firefox default
SameSite=Laxsince 2020 blocks credentialed cross-site fetch initiated top-level GET only after nav — but blocks POST/XHR entirely - Enterprise victims often use isolated browser profiles or Zscaler/Netskope proxies that strip third-party cookies
- Corporate email gateways (Proofpoint, Mimecast) sandbox suspicious links
fetch to newly-registered domains; browser isolation catches it.Exfiltrate response body to attacker origin
ACAO header lets attacker JS read the cross-origin response body (normally forbidden by SOP). Attacker POSTs the harvested JSON — session identity, PII, API tokens, CSRF tokens for follow-on attacks — to their own collection endpoint. Repeat across all discovered endpoints for full account takeover material.- Response body contains sensitive data or actionable secrets
- Endpoints returning only opaque IDs / no sensitive payload yield little
- Rate limiting and anomaly detection on server side may throttle bulk enumeration
Origin header from single external domain, followed by anomalous data volume.The supporting signals.
| Finding type | Tenable WAS plugin 98983 — misconfiguration signature, *not* a CVE. Maps to CWE-346 (Origin Validation Error). |
|---|---|
| Vendor severity | Medium — CVSS v2 4.3 / v3 6.1 / v4 5.1 (Tenable-assigned; no NVD entry because this is not a CVE) |
| CVSS v3.1 vector (Tenable) | AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:N — network vector but UI:R (victim must visit attacker page) and AC:H are the honest constraints |
| In-the-wild status | Class of bug has been abused in bug-bounty payouts against Facebook (2016 Bitquark), Twitter, Uber, Slack, GitHub Enterprise. No CISA KEV entry (KEV is CVE-only). |
| Public tooling | Corsy (s0md3v/Corsy), CORScanner (chenjj/CORScanner), Burp Suite CORS scanner, Nuclei cors-misconfig template |
| EPSS / KEV | N/A — EPSS and KEV are keyed to CVE IDs; this Tenable plugin has none |
| Exposure population | High class-frequency — Detectify's 2023 web-security study found permissive CORS in ~7% of enterprise web assets; PortSwigger research puts the credentialed-variant subset at <1% |
| Blast radius | Per-user, per-endpoint — one victim's session, on one app, per phish. Not lateral, not fleet-scale. |
| Modern browser mitigation | SameSite=Lax default (Chrome 80, Feb 2020) neuters most credentialed cross-site XHR; SameSite=Strict eliminates it |
| Fixed configuration | Explicit domain allowlist; never combine ACAO: * or reflected origin with Access-Control-Allow-Credentials: true |
noisgate verdict.
The single decisive factor is impact ceiling: the worst realistic outcome is credentialed cross-origin data read against a phished, currently-authenticated user — a per-session, per-victim compromise, not a server-side or fleet-scale breach. Modern browser SameSite=Lax cookie defaults and the required user-interaction step keep this firmly in the MEDIUM bucket regardless of endpoint count.
Why this verdict
- No vendor CVSS baseline exists — this is a Tenable WAS signature (plugin 98983), not a CVE. Assessment starts from Tenable's own Medium (6.1) rating and adjusts against real-world friction.
- Requires victim interaction and active session — victim must be logged in to the target AND visit an attacker page in the same browser profile. Two compounding preconditions drop the effective severity.
- SameSite=Lax cookie default (Chrome 80+, Firefox, Edge) blocks the credentialed cross-site XHR variant entirely for POST and blocks GET-initiated fetch without a top-level navigation. This eliminates the exploit path on most modern applications.
- Role multiplier — customer-facing web app / API: chain succeeds; blast radius = one victim's session per successful phish. Not a fleet event.
- Role multiplier — internal admin console / IdP / SSO portal: chain succeeds; blast radius scales to *whichever admin* got phished but is still bounded by that admin's own session. If the admin's session grants tenant/domain control, that is a role-escalation risk — but the CORS bug is the amplifier, not the primary. Floor still MEDIUM because attacker must still land the phish; the AD/IdP itself is not directly reachable via CORS from the internet.
- Role multiplier — unauthenticated public API: no credentials to steal, chain does not monetize. Effectively LOW in this role.
- No lateral movement, no server-side RCE, no unauthenticated bulk exfil — CORS bugs do not compound into fleet compromise on their own.
Why not higher?
HIGH would require either unauthenticated server-side impact or a chain that succeeds without victim interaction and without an active session cookie. Neither holds: browser SOP still enforces same-origin *without* the victim's cookies, and SameSite=Lax defaults strip those cookies from cross-site requests. The bug is a real amplifier, not a primary compromise vector.
Why not lower?
LOW / IGNORE is wrong because the class of bug has produced material bug-bounty payouts (Facebook, Uber, Slack, GitHub Enterprise) where authenticated tokens, PII, and CSRF tokens were exfiltrated cross-origin. Any app that combines reflected/wildcard ACAO with Allow-Credentials: true and returns session-bearing JSON *is* exploitable against a phished user — that is not backlog hygiene, it is an actionable finding.
What to do — in priority order.
- Remove
Access-Control-Allow-Credentials: trueunless absolutely required — This single header change eliminates the high-impact variant. Without it, cross-origin JS cannot read responses that depend on cookies orAuthorizationheaders. Deploy the config change within the standard MEDIUM window — no mitigation SLA, so target the 365-day remediation cycle, but this is a one-line config fix that should ship in the next release train. - Replace wildcard / reflected origins with an explicit allowlist — Never emit
Access-Control-Allow-Origin: *when credentials are in play, and never blindly echo the requestingOrigin. Maintain a static allowlist of trusted partner/subdomain origins in the reverse proxy (Nginxmap $http_origin, Envoycors_policy.allow_origin_string_match, or the framework's typed CORS middleware). Ship within 90 days. - Set session cookies to
SameSite=Strict(or at minimumLax) withSecureandHttpOnly— Belt-and-braces defense: even if a rogue CORS policy slips through code review,SameSite=Strictprevents the browser from attaching the session cookie to the attacker's cross-site fetch. Roll this out across all first-party session cookies in the next sprint. - Require a custom header (e.g.
X-Requested-With: XMLHttpRequest) on state-changing endpoints — Custom headers force a CORS preflight; if the server rejects preflights from untrusted origins, the exploit chain is broken before the credentialed request lands. Combine with double-submit CSRF tokens. - Add a WAF rule that strips or rejects reflected
Originresponses — At F5, Cloudflare, Akamai, AWS WAF: inspect outbound response headers and block responses whereACAOechoes an origin not on the allowlist. This is a compensating control while the underlying app is being fixed.
- CSRF tokens alone — a permissive CORS policy lets the attacker's JS *read* the CSRF token from a preflight-allowed endpoint and reuse it. CSRF tokens are necessary but not sufficient.
- HTTPS / HSTS — TLS does nothing to prevent a browser from honoring a permissive
ACAOheader; the attack happens over TLS end-to-end. - Content Security Policy (CSP) — CSP restricts what the *victim's* app can load; it does not constrain what an *attacker's* origin can request from the victim's app.
- IP allowlisting the API — the request originates from the victim's browser (a trusted client IP), so IP filtering does not fire.
Crowdsourced verification payload.
Run this from any workstation or CI runner with network access to the target. Invoke as ./cors-check.sh https://api.example.com/me — no privileges required. It probes the endpoint with a spoofed Origin header and inspects the response.
#!/usr/bin/env bash
# cors-check.sh — detects Tenable WAS 98983 (Insecure CORS Configuration)
# Usage: ./cors-check.sh <url>
# Exits 2 = VULNERABLE, 0 = PATCHED, 1 = UNKNOWN/error
set -u
TARGET="${1:-}"
if [[ -z "$TARGET" ]]; then
echo "UNKNOWN: usage: $0 <url>" >&2
exit 1
fi
EVIL_ORIGIN="https://evil.attacker.tld"
# Fetch response headers with spoofed Origin
HEADERS=$(curl -sS -D - -o /dev/null \
-H "Origin: ${EVIL_ORIGIN}" \
-H "User-Agent: noisgate-cors-check/1.0" \
--max-time 15 \
"$TARGET" 2>/dev/null) || {
echo "UNKNOWN: curl failed against $TARGET"
exit 1
}
ACAO=$(echo "$HEADERS" | grep -i '^access-control-allow-origin:' | tr -d '\r' | awk -F': ' '{print $2}')
ACAC=$(echo "$HEADERS" | grep -i '^access-control-allow-credentials:' | tr -d '\r' | awk -F': ' '{print $2}')
if [[ -z "$ACAO" ]]; then
echo "PATCHED: no Access-Control-Allow-Origin header returned for untrusted origin"
exit 0
fi
# Case 1: reflected origin
if [[ "$ACAO" == "$EVIL_ORIGIN" ]]; then
if [[ "${ACAC,,}" == "true" ]]; then
echo "VULNERABLE: reflected Origin + Allow-Credentials:true — credentialed cross-origin read possible"
exit 2
fi
echo "VULNERABLE: reflected Origin (no credentials, but still unauthenticated data leak risk)"
exit 2
fi
# Case 2: wildcard with credentials (browser will actually reject this combo, but signal is still a misconfig)
if [[ "$ACAO" == "*" && "${ACAC,,}" == "true" ]]; then
echo "VULNERABLE: ACAO:* with Allow-Credentials:true (invalid combo, but indicates broken policy)"
exit 2
fi
# Case 3: explicit allowlist — check if evil origin is NOT reflected
echo "PATCHED: ACAO='$ACAO' does not reflect untrusted origin"
exit 0
If you remember one thing.
Access-Control-Allow-Credentials: true — those are the only ones worth a same-week fix, (3) push a WAF rule that strips reflected ACAO headers as a stopgap while dev teams remove wildcards and adopt explicit allowlists, (4) verify session cookies are SameSite=Lax or Strict fleet-wide, (5) close the loop by re-running the verification script in CI so this doesn't regress. Do not page anyone at 2 AM for this.Sources
- Tenable WAS Plugin 98983 — Insecure CORS Configuration
- CWE-346: Origin Validation Error
- PortSwigger Web Security Academy — CORS
- OWASP HTML5 / CORS Security Cheat Sheet
- MDN — Access-Control-Allow-Credentials
- Chrome SameSite=Lax by default rollout
- Corsy CORS misconfiguration scanner (s0md3v)
- CORScanner (chenjj)
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.