web front-end security prototype chain contamination -JavaScript

0x01 Foreword

Recently I read an article in the prototype chain of contamination, their own summarize here

 

0x02 javascript prototype chain

js no concept of class before ECS6, before classes are used to declare the funtion. as follows

Can be seen bin the example into test对象the future, they can output test class 属性aa. Why is this?

The reason is that an important concept in js: inheritance.

The inheritance of the whole process is called the prototype chain of the class.

在javascript中,每个对象的都有一个指向他的原型(prototype)的内部链接,这个原型对象又有它自己的原型,直到null为止


function i(){
    this.a = "test1"; this.b = "test2";} 

We can see its parent class object, and there is also a lot of functions, which explains why many variables can call some methods.

Everything in javascript object because all of the variables, functions, arrays, objects began with a prototype object that is object.prototype. Meanwhile, the only prototype js only class attribute, and the object has not, some objects __proto__and classes prototypecorrespond. And both are equivalent

When we create a class

Prototype chain

b -> a.prototype -> object.prototype->null

When you create an array

Prototype chain

c -> array.prototype -> object.prototype->null

When you create a function

Prototype chain

d -> function.prototype -> object.prototype->null

Creating a date

Prototype chain

f -> Data.prototype -> object.prototype->null

So, after the test you will find: javascript everything is an object, and all are started object.prototype

Prototype chain variable search

A look at the following example:

In the first instance we have to iadd the property, but jthe property has also been c. Why is this

answer:

当要使用或输出一个变量时:首先会在本层中搜索相应的变量,如果不存在的话,就会向上搜索,即在自己的父类中搜索,当父类中也没有时,就会向祖父类搜索,直到指向null,如果此时还没有搜索到,就会返回 undefined

Therefore, the above process is well explained, the prototype chain

j -> i.prototype -> object.prototype -> null

所以对象j调用c属性时,本层并没有,所以向上搜索,在上一层找到了我们添加的test3,所以可以输出。

 

prototype 原型链污染

先看一个小例子:

mess.js

----

(function()
{
    var secret = ["aaa","bbb"]; secret.forEach(); })(); 

attach.html

结果:

在mess.js中我们声明了一个数组 secret,然后该数组调用了属于 Array.protottypeforeach方法,如下

但是,在调用js文件之前,js代码中将Array.prototype.foreach方法进行了重写,而prototype链为secret -> Array.prototype ->object.prototype,secret中无 foreach 方法,所以就会向上检索,就找到了Array.prototype 而其foreach方法已经被重写过了,所以会执行输出。

这就是原型链污染。很明显,原型链污染就是:在我们想要利用的代码之前的赋值语句如果可控的话,我们进行 ——__proto__ 赋值,之后就可以利用代码了

 

如何应用?

在javascript中可以通过 test.a or test['a'] 对数组的元素进行访问,如下:

同时对对象来说说也是一样的

所以我们上述说的prototype也是一样的

那就很明显了,原型链污染一般会出现在对象、或数组的键名或属性名可控,而且是赋值语句的情况下。

下面我们先看一道题:hackit 2018

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

获取flag的条件是 传入的querytoken要和user数组本身的admintoken的MD5值相等,且二者都要存在。

由代码可知,全文没有对user.admintokn 进行赋值,所以理论上这个值时不存在的,但是下面有一句赋值语句:

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

data,row,col,都是我们post传入的值,都是可控的。所以可以构造原型链污染,下面我们先本地测试一下。

下面我们给出payload和结果

注:要使用json传值,不然会出现错误

下面再看另一道题:

'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 аdmin = JSON.parse(JSON.stringify(req.cookies)) if (admin.аdmin == 1) { res.send("hackim19{}"); } else { res.send("You are not authorized"); } }); app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`); 

先分析一下题目,获取flag的条件是admin.аdmin == 1而admin 本身是一个object,其admin 属性本身并不存在,而且还有一个敏感函数 merg


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 } 

merge 函数作用是进行对象的合并,其中涉及到了对象的赋值,且键值可控,这样就可以触发原形链污染了

下面我们本地测试一下

是undefined,为什么呢?下面我们看下

原来我们在创建字典的时候,__proto__,不是作为一个键名,而是已经作为__proto__给其父类进行赋值了,所以在test.__proto__中才有admin属性,但是我们是想让__proto__作为一个键值的.

那应该怎么办呢?可以使用 JSON.parse

JSON.parse 会把一个json字符串 转化为 javascript的object

这样就不会在创建类的时候直接给父类赋值了

而题目中也出现了JSON.parse


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

这样我们就可以愉快地进行原型链污染了

payload:

 

 

转自安全客

Guess you like

Origin www.cnblogs.com/-hack-/p/12076609.html