之前已经写到一些原型链污染的原理和原型链原理
这里再补充一些
1.利用:如何判断是否存在原型链污染?
·字符串可以被解析为对象或方法。例如:Json.parse进行解析、shvl库使用点对属性操作。
·对象的键和值都可控。target[key]=value,其中value和key均可控制。
常见有用merge方法
这里就不再赘述了
实例1:在antCTF中,使用shvl库对键值进行操作
email: async function (req, res) {
let contents = {};
// 遍历请求参数中所有的 key
// 将键和值赋值为 contents(shvl 库的 set 函数)
Object.keys(req.body).forEach((key) => {
shvl.set(contents, key, req.body[key]);
});
contents.from = '"admin" <admin@8-bit.pub>';
try {
await send(contents);
return res.json({
message: "Success."
});
} catch (err) {
return res.status(500).json({
message: err.message
});
}
}
2.常见的原型链污染方法有哪些?
function.__proto__polluted
function.prototype.polluted
obj.__proto__.polluted.例如:shvl只禁用了__proto__
obj.constructor.polluted
前端的有extend
Lodash 模块原型链污染
lodash.defaultsDeep
lodash.merge
lodash.mergeWith
lodash.setWith
lodash.template
function person(fullName) {
this.fullName = fullName;
}
var person1 = new person("Satoshi");
// function:prototype, __prototype
person.prototype.sayHello = 1
console.log(person1.__proto__);
person.prototype.newConstant = 2
console.log(person1.__proto__);
// object: __prototype__, constructor
person1.__proto__.sayHi= 3
console.log(person1.__proto__);
person1.constructor.prototype.oldConstant = 4
console.log(person1.__proto__);
/*
person { sayHello: 1 }
person { sayHello: 1, newConstant: 2 }
person { sayHello: 1, newConstant: 2, sayHi: 3 }
person { sayHello: 1, newConstant: 2, sayHi: 3, oldConstant: 4 }
*/
3.原型链怎么挖掘?
1.寻找JavaScript中的危险关键字(危险函数)。如:
·模块:child_process
·函数:eval,spwn,exec,setTimeout,setInteval,Function
2.寻找调用关系和可控的参数,并且确定如何进行传参
先确定危险函数和调用方式,就是头部和尾部,再去找中间过程看具体的使用
3.充分利用危险函数和能控制的参数.(读取文件或者反弹shell)
`目标机器环境如果有bash可以反弹shell
`目标机器环境如果有Python等,可以反弹shell
`目标机器环境如果只有sh,将readflag执行写入到其他地方,再利用其他方式读取.
4.如何防御原型链污染?如何绕过防御?
如果系统中有键值的操作,并且健和值来自外部输入,可以考虑进行过滤:
`禁止操作constructor
`禁止操作prototype
`禁止操作__proto__
绕过防御:
`思考测试是否过滤完全,参考antCTF的8-bit-pub中的shvl库绕过
最后
补充一下之前没有提到的
prototype是[方法特有的](大概了解一下)
`方法:类似C++中的类.除了有属性__proto__,还有属性prototype,prototype指向该方法的原型对象.prototype指定其他对象之后,会包含所有原型对象的属性和方法
`对象:类似C++中的对象.对象只有属性__proto__指向该对象的构造函数的原型对象.对象有constructor里面包含该类的prototype.
// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function () {
this.a = 1;
this.b = 2;
}
/* 这么写也一样
function f() {
this.a = 1;
this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}
// 在f函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
// (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。
// 综上,整个原型链如下:
// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
console.log(o.a); // 1
// a是o的自身属性吗?是的,该属性的值为 1
console.log(o.b); // 2
// b是o的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c是o的自身属性吗?不是,需要看它的原型上有没有
// c是o.[[Prototype]]的属性吗?是的,该属性的值为 4
console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,需要看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,需要看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined
0x01__proto__属性有什么用?
每个'对象'都有__proto__属性,指向了创建该对象的构造函数的原型.其实这个属性指向了[[prototype]],但是[[prototype]]是内部属性,我们并不能访问到,所以使用__proto__来访问.
简单来说:用prototype无法直接访问,需要使用__proto__访问.prototype是一个指针属性.
注意:
`__proto__:指向原型对象的构造器.
`constructor:指向当前对象的构造器
__proto__箭头指向原型对象的构造器
constructor箭头指向原型对象的构造器
好了 开摆!