Example of Prototype Chain Pollution

First introduce the prototype chain

What are JS prototypeand __proto__JS?

We can think of the prototype prototypeas Fooan attribute of the class, and all Fooobjects instantiated with the class will have all the contents of this attribute, including variables and methods.
We can Foo.prototypeaccess Foothe prototype of the class through , but Foothe instantiated object cannot access the prototype through the prototype. At this time, it's time __proto__to show up.

A foo object instantiated from the Foo class can foo.__proto__access the prototype of the Foo class through attributes, that is to say:

foo.__proto__ == Foo.prototype

in conclusion:

  1. prototypeprototypeIt is an attribute of a class, and all class objects will have the attributes and methods in it when they are instantiated
  2. An attribute of an object , pointing to an attribute __proto__of the class in which the object is locatedprototype

JavaScript Prototype Chain Inheritance

prototypeAll class objects will have properties and methods when they are instantiated . This feature is used to implement the inheritance mechanism in JavaScript.

for example:

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

function Son() {
    this.first_name = 'Melania'
}

Son.prototype = new Father()

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

The Son class inherits last_namethe properties of the Father class, and the final output is Name: Melania Trump.

To sum up, for the object son, when calling son.last_name, the JavaScript engine will actually perform the following operations:

  1. Find last_name in object son
  2. If not found, son.__proto__look for last_name in
  3. If still not found, then keep son.__proto__.__proto__looking for last_name in
  4. Search in turn until nullthe end is found. For example , Object.prototypethe__proto__null

Under what circumstances can the prototype chain be polluted

In practical applications, under what circumstances may there be situations where the prototype chain can be modified by an attacker?

Let's think about it, under what circumstances can we set __proto__the value? In fact, just look for the operation that can control the "key name" of the array (object):

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

Taking the object merge as an example, we imagine 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]
        }
    }
}

In the process of merging, there is an assignment operation target[key] = source[key], so if the key is true __proto__, it can pollute the prototype chain
Let's experiment with the following code:

let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)

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

The result is that although the merger is successful, the prototype chain is not polluted:
insert image description here
this is because, let o2 = {a: 1, "__proto__": {b: 2}}in the process of creating o2 with JavaScript ( ), __proto__it already represents the prototype of o2. Yes [a, b], __proto__it is not a key, and naturally it will not modify the prototype of Object.

So, how to make __proto__is considered a key name?

We change the code to the following:

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)

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__it will be considered a real "key name" instead of a "prototype", so when traversing o2 This key will exist when

The merge operation is the most common operation that may control the key name, and it is also the most vulnerable to prototype chain attacks. This problem exists in many common libraries.

example

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!')
})

First analyze the code.
The condition for obtaining the flag is that the incoming querytoken must be equal to the MD5 value of the admintoken in the user array itself, and both must exist.

It can be seen from the code that the full text does not assign a value to user.admintokn, so theoretically this value does not exist, but there is an assignment statement below:

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

data, row, col, are all the values ​​passed in by our post, and they are all controllable, so prototype chain pollution can be constructed.

Therefore, two variables can be defined.
One variable is used to pass in data, row, and col, so that the original admintoken can be polluted through the prototype chain to make the admintoken exist. The
other variable is used to make the admintoken match the md5 value in the passed-
in URL. Pay attention to use json when setting the value, otherwise __proto__ will not be recognized as an object but a character,

insert image description here
so you can use the prototype chain to pollute and successfully capture the flag

Guess you like

Origin blog.csdn.net/weixin_54986292/article/details/132068755