← Back to Feed CACHED · 2026-07-14 09:48:16 · CACHE_KEY tenable:98983
tenable:98983 · CWE-346

Tenable WAS 98983 — Insecure Cross-Origin Resource Sharing

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

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.

"Insecure CORS is a real-but-contextual finding: it turns a victim's browser into an attacker's proxy — only if the target has session-bearing endpoints worth stealing."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Confirm the reflective/permissive CORS policy

Attacker probes the target endpoint with a crafted 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.
Conditions required:
  • Endpoint reachable by attacker (does not need auth)
  • Application returns CORS headers on the endpoint
Where this breaks in practice:
  • Modern frameworks default to strict-origin allowlists
  • WAFs and API gateways frequently strip or normalize CORS headers
Detection/coverage: Tenable WAS 98983 flags this directly; Burp Scanner, Netsparker/Invicti, and Acunetix all have signatures. Nuclei template cors-misconfig covers it.
STEP 02

Identify a state-changing or data-returning authenticated endpoint

The CORS bug only monetizes if there is an endpoint that (a) is served on the misconfigured origin, (b) accepts cookie/bearer auth, and (c) returns something worth stealing or performs an action worth invoking (/api/me, /api/transfer, /graphql, /admin/users). Attacker maps these via directory brute-force (ffuf, feroxbuster) or by reading published API docs / OpenAPI specs.
Conditions required:
  • Session-bearing endpoints exist on the vulnerable origin
  • Endpoints do not require additional CSRF token / custom header the attacker cannot forge cross-origin
Where this breaks in practice:
  • Well-designed APIs require a custom header like X-Requested-With that triggers a preflight and blocks bypass
  • Bearer tokens in Authorization header are NOT sent cross-origin automatically — only cookies are
Detection/coverage: Access-log correlation on Origin headers from unknown domains combined with 200-response Set-Cookie-authenticated calls.
STEP 03

Host attacker-controlled page and lure an authenticated victim

Attacker stands up 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.
Conditions required:
  • Victim has active session with target
  • Victim visits attacker page in same browser context
  • SameSite=Lax or None on session cookie (Strict blocks this entirely)
Where this breaks in practice:
  • Chrome/Edge/Firefox default SameSite=Lax since 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
Detection/coverage: EDR/CASB visibility of outbound fetch to newly-registered domains; browser isolation catches it.
STEP 04

Exfiltrate response body to attacker origin

The permissive 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.
Conditions required:
  • Response body contains sensitive data or actionable secrets
Where this breaks in practice:
  • Endpoints returning only opaque IDs / no sensitive payload yield little
  • Rate limiting and anomaly detection on server side may throttle bulk enumeration
Detection/coverage: SIEM rule: many endpoints hit in rapid sequence with Origin header from single external domain, followed by anomalous data volume.
03 · Intelligence Metadata

The supporting signals.

Finding typeTenable WAS plugin 98983 — misconfiguration signature, *not* a CVE. Maps to CWE-346 (Origin Validation Error).
Vendor severityMedium — 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 statusClass 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 toolingCorsy (s0md3v/Corsy), CORScanner (chenjj/CORScanner), Burp Suite CORS scanner, Nuclei cors-misconfig template
EPSS / KEVN/A — EPSS and KEV are keyed to CVE IDs; this Tenable plugin has none
Exposure populationHigh 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 radiusPer-user, per-endpoint — one victim's session, on one app, per phish. Not lateral, not fleet-scale.
Modern browser mitigationSameSite=Lax default (Chrome 80, Feb 2020) neuters most credentialed cross-site XHR; SameSite=Strict eliminates it
Fixed configurationExplicit domain allowlist; never combine ACAO: * or reflected origin with Access-Control-Allow-Credentials: true
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to MEDIUM (5.4/10)

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.

HIGH Severity bucket (MEDIUM)
HIGH Impact ceiling analysis
MEDIUM Per-instance exploitability — depends on which endpoints exist behind the misconfigured origin

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.

05 · Compensating Control

What to do — in priority order.

  1. Remove Access-Control-Allow-Credentials: true unless absolutely required — This single header change eliminates the high-impact variant. Without it, cross-origin JS cannot read responses that depend on cookies or Authorization headers. 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.
  2. Replace wildcard / reflected origins with an explicit allowlist — Never emit Access-Control-Allow-Origin: * when credentials are in play, and never blindly echo the requesting Origin. Maintain a static allowlist of trusted partner/subdomain origins in the reverse proxy (Nginx map $http_origin, Envoy cors_policy.allow_origin_string_match, or the framework's typed CORS middleware). Ship within 90 days.
  3. Set session cookies to SameSite=Strict (or at minimum Lax) with Secure and HttpOnly — Belt-and-braces defense: even if a rogue CORS policy slips through code review, SameSite=Strict prevents 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.
  4. 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.
  5. Add a WAF rule that strips or rejects reflected Origin responses — At F5, Cloudflare, Akamai, AWS WAF: inspect outbound response headers and block responses where ACAO echoes an origin not on the allowlist. This is a compensating control while the underlying app is being fixed.
What doesn't work
  • 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 ACAO header; 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.
06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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
07 · Bottom Line

If you remember one thing.

TL;DR
Treat this as a MEDIUM web-app hygiene finding, not a fire drill. Per the noisgate mitigation SLA for MEDIUM there is no mitigation deadline — you go straight to the noisgate remediation SLA window of ≤ 365 days, but realistically this is a one-line config change per affected endpoint and should ship in the next scheduled release train (2-4 weeks). Monday morning: (1) pull the full Tenable WAS report and dedupe 98983 findings by hostname + endpoint, (2) triage which findings return 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

  1. Tenable WAS Plugin 98983 — Insecure CORS Configuration
  2. CWE-346: Origin Validation Error
  3. PortSwigger Web Security Academy — CORS
  4. OWASP HTML5 / CORS Security Cheat Sheet
  5. MDN — Access-Control-Allow-Credentials
  6. Chrome SameSite=Lax by default rollout
  7. Corsy CORS misconfiguration scanner (s0md3v)
  8. CORScanner (chenjj)
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.