~/blog/cors-misconfiguration-single-findingPOSTED
by Shady Nathan Tawfik · August 1, 2026 · 5 min

One Finding, Maximum Impact: A Permissive Cross-Origin Policy

A civic content platform on a modern stack produced a single finding of the highest severity. It needed no version check and no exploit; the browser already did the work.

Interactive[blog-2.0]

TL;DR: One finding, maximum impact: a wildcard cross-origin policy on the public data interface. No version check, no exploit — the browser does the work. I show the exact request and the allowlist fix.

The scenario

I ran this as a black-box review: no credentials, only the publicly visible surface. Here is what I saw before I touched anything.

A municipal content platform on a supported content system behind a modern reverse proxy. The assessment was narrow by design, examining the public data interface and the cross-origin policy governing browser access to it.

I removed all identifying information. The target is described only by sector and technology class so the pattern can be reused.

The pattern

The pattern I recognised — and that I keep finding in similar estates:

Cross-origin policy is a browser mechanism, and that is what makes it both powerful and cheap. When a server answers a cross-origin request with a permissive policy, the browser enforces it: the requesting page may read the response. The server did nothing wrong by its own lights, no version is out of date, and no payload is required. A single HTML file on any domain is the whole attack.

The distinction that matters is between a wildcard and a reflected origin. A wildcard on a public, unauthenticated read endpoint is frequently a deliberate convenience: the data is public, so the developer reasons that anyone may fetch it. The reasoning is correct about the data and wrong about the browser. The endpoint is not merely readable, it is readable from a page the visitor is currently looking at, which turns it into a same-origin resource for any script that gets to run there.

This is why a single finding can outrank a long list of version notes. Version findings describe what an attacker could do after winning access. A permissive cross-origin policy decides, for every future injection on every page of the site, whether the attacker gets to read the response without needing a foothold at all. It converts every other bug on the estate from minor to major, which is why it belongs at the top of the report regardless of how little effort it took to find.

TL;DR: A civic content platform on a modern stack produced a single finding of the highest severity. It needed no version check and no exploit; the browser already did the work.

Findings

SeverityFindingEvidence
KRITISCHWildcard cross-origin policy on the public data interfaceAny page on any domain can read the response
MEDIUMWildcard policy applied to write-capable pathsPermissive header not scoped to the read endpoint
MEDIUMWildcard combined with credentials on one routeResponse readable with cookies attached
LOWNo vary header on the reflected originCache poisoning surface
LOWPreflight answered without a route allowlistMethod and header negotiation not constrained
INFOPlatform fully patchedNo exploitable vulnerability in the application itself

The attack path

Attacker page on any domainCross-origin fetch with credentialsTarget data interfaceWildcard policy returnedBrowser permits reading the responseData readable without any foothold

Reproduction in my lab

Every command below targets a lab container I control. Nothing here is aimed at a live system.

Check your own interface from a foreign origin

~/code/bash bash
# target: your own lab interface
# 1. what does the server say for a foreign origin
$ curl -s -o /dev/null -D - -H 'Origin: https://evil.example' \
  'https://example.org/api/public/items' | grep -i 'access-control'

# 2. does it reflect any origin, including a subdomain that does not exist
$ curl -s -o /dev/null -D - -H 'Origin: https://attacker-controlled.example.org' \
  'https://example.org/api/public/items' | grep -i 'access-control'

# 3. and can credentials be attached
$ curl -s -o /dev/null -D - -H 'Origin: https://evil.example' \
  --cookie 'session=whatever' 'https://example.org/api/private/items' | grep -i 'access-control'

Detection in your own estate

Sweep your own estate for reflective origin headers

~/code/bash bash
$ for p in /api/public/items /api/private/items /graphql /api/me; do
    printf '%s -> %s\n' "$p" \
      "$(curl -s -o /dev/null -D - -H 'Origin: https://probe.example' "https://example.org$p" \
         | grep -i 'access-control-allow-origin' | tr -d '\r')"
done

# and for the combination that actually matters
$ curl -s -o /dev/null -D - -X OPTIONS -H 'Origin: https://probe.example' \
  -H 'Access-Control-Request-Method: DELETE' -H 'Access-Control-Request-Headers: authorization' \
  'https://example.org/api/public/items' | grep -i 'access-control'

Remediation

Scope the policy to an allowlist, and never reflect blindly

~/code/nginx nginx
# an explicit allowlist, evaluated server side
map $http_origin $cors_origin {
    default                       "";
    "https://example.org"        $http_origin;
    "https://www.example.org"    $http_origin;
}

add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;

# credentials only where the endpoint is genuinely authenticated and the
# origin list is already restricted to hosts you control
# add_header Access-Control-Allow-Credentials "true" always;

Return the correct refusal instead of a permissive header

~/code/ts ts
// an allowlist beats a wildcard, always
const ALLOWED = new Set(['https://example.org', 'https://www.example.org']);

export function cors(req, res, next) {
  const origin = req.headers.origin;
  if (origin && ALLOWED.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
  }
  // no origin header at all when the request is not from an allowed origin
  next();
}

Once an attacker gains access, they can perform the following actions:

Wildcard cross-origin policy on the public data interface
Wildcard policy applied to write-capable paths
Wildcard combined with credentials on one route

Takeaways

  • A permissive cross-origin policy is a multiplier on every other bug on the estate.
  • Public data does not mean publicly readable from any page.
  • Never reflect an origin without an allowlist and without Vary.
  • One finding that changes the severity of everything else belongs at the top of the report.

For educational purposes only. Every reproducible command targets my own lab environment (localhost), never a live system. © 2026