The guide attached to this article aims to collect all the techniques, from the basics to the advanced ones, to attack and protect Cross-Origin Resource Sharing (CORS).
What’s new?
In the last update (version 1.1), published on January 2020, two new technique to exploit CORS misconfiguration have been added:
- Using insecure protocol
- Using the browser cache
Another update concerned the special chars supported by the main browsers.
Some minor updates and typo corrections close the list of changes.
Who should read this guide?
This guide is directed to a large audience: web administrators, developers, penetration testers, bug bounty hunters, and more in general to security professionals.
Inside this guide the reader will find:
- A brief introduction to the Same Origin Policy and Cross-Origin Resource Sharing (CORS)
- Main techniques, from basic to advanced ones, to attack an application with CORS enabled
- General guidelines to implement CORS securely
What is CORS and the Same-Origin Policy?
The Same-Origin Policy (SOP) is the browser rule that stops a script loaded from one origin from reading responses that come back from a different origin. An origin is made of three parts: the scheme, the host and the port. If any of them differs, the browser treats it as a separate origin.
CORS exists because that rule is often too strict for real applications that legitimately talk to APIs or services on other domains. It loosens the SOP through a few HTTP response headers. The one that matters most is Access-Control-Allow-Origin, which names the origin allowed to read the response. If the server also sends Access-Control-Allow-Credentials: true, the cross-origin request can carry cookies or HTTP authentication. That combination has a catch: you cannot pair credentials with the Access-Control-Allow-Origin: * wildcard, so many servers end up echoing back whatever Origin the request sent. Whenever that header is built on the fly, the response should also carry Vary: Origin.
How is a CORS misconfiguration exploited?
Testing CORS comes down to three steps: find the handlers that return Access-Control-* headers, work out how the server decides which origins it trusts, then try to abuse that decision.
The most common mistake by far is a server that takes the Origin from the request, copies it straight into Access-Control-Allow-Origin, and sends Access-Control-Allow-Credentials: true next to it. When that happens, a page an attacker controls can fire an authenticated request inside the victim’s browser and read their private data straight back. Trusting the null origin is a close relative, since any page can produce it with a sandboxed iframe.
Validation, where it exists, is often weaker than it looks. Regular expressions cause a lot of the trouble: a check that only confirms the Origin ends with the target domain will happily accept nottarget.local, and controlling a subdomain, an allowed third party such as an S3 bucket, an http:// origin, or certain special characters in the hostname can all get through. Even with credentials off, reflected headers can still open the way to cache poisoning on the client or the server.
How do you configure CORS securely?
The safest CORS setup is the one you never turn on. If a resource does not need to be reached cross-origin, leave the SOP alone.
When you do need it, keep an explicit allow-list of origins and compare the incoming Origin against it exactly. A fixed list is far easier to get right than a regular expression. Skip the wildcard, and never reflect the Origin back without validating it first. Trust only HTTPS origins, so someone on the network cannot downgrade the request, and send Vary: Origin whenever the allow-origin header depends on the request.
Leave credentials off unless the feature genuinely needs them, and set Access-Control-Allow-Credentials to false explicitly when in doubt, because some frameworks assume true. Keep the list of allowed methods short and the preflight cache small (Access-Control-Max-Age of around half an hour is a sensible ceiling); for anything private, add Cache-Control: no-store. And only emit CORS headers when a real cross-origin request comes in, checking the defaults of whatever library you use, since several ship insecure out of the box. For a worked server-side example, see our guide to implementing secure CORS on Tomcat.
Be sure to develop secure apps. BeDefended.
Thanks for reading.
Download the full guide (PDF) for the complete walk-through, with request/response examples, the browser special-character compatibility table and the framework default-configuration matrix.