• 原型链污染漏洞(一)


    0x01 深入了解JavaScript

    对象与类

    JavaScript一切皆对象,所以先来了解了解对象

    创造一个最简单的js对象如:

    var obj = {};
    

    创建obj这个对象时,并没有赋予他任何属性或者方法,但是他会具有一些内置属性和方法,像__proto__,constructor,toString等.

    为了探究这些内置属性是怎么来的,接下来需要看一下JavaScript中类的一些机制

    JavaScript中的类从一个函数开始:

    1. 函数对象:
    function MyClass() {
        console.log("lonmar");
    }
    var inst = new MyClass();
    //以上代码创建了一个MyClass函数,同时MyClass也是一个类,可以像别的语言中那样为这个类实例化一个对象inst
    

    观察以上代码执行结果可以发现,在实例化inst的时候,MyClass()也同样执行了.

    这可以联想到构造函数,构造函数在的特性就是在new一个对象的时候执行.

    所以MyClass()函数MyClass这个类的关系就很明显了,前者是后者的构造函数

    通过constructor这个属性可以查看对象的构造函数

    下面了解下 __proto__prototype

    先抛出结论

    1. prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
    2. 一个对象的__proto__属性,指向这个对象所在的类的prototype属性
    3. 类在运行程序运行时是可以修改的

    再看实例:

    下面这段代码通过prototype属性,为Person类添加了getInfo()这个属性.

    其实例化对象也都具有这个属性

    function Person(name,age) {
        this.name = name;
        this.age = age;
        this.greed = function(){
    		console.log("hello,I am",this.name);
        }
        console.log("I am Person Class");
    }
    
    Person.prototype.getInfo = function(){
        return this.name + "," + this.age;
    }
    
    var lonmar=new Person('lonmar',10);//I am Person Class
    lonmar.greed();//hello,I am lonmar
    lonmar.getInfo();//'lonmar,10'                                                       
    

    下面其实是JavaScript中继承的一种写法,通过修改原型链来继承

    Student类继承了Person类,但也可以看出只是继承部分属性,如constructor就没有被继承

    然后通过prototype修改的属性也被继承了!

    function Student(){
        console.log("I am Student Class")
    }
    
    Student.prototype = new Person();//I am Person Class;erson { name: undefined, age: undefined, greed: [Function] }
    Student.prototype.age = 10;//10
    Student.prototype.name = "lonmar";//lonmar
    var stud = new Student();
    stud.getInfo();//'lonmar,10'
    

    通过下面的实例又可以看出,Student类的prototype实际上指向一个Person的实例化对象

    stud的__proto__也指向一个对象,并且stud.__proto__ =Student.prototype

    也可以依次向前查找__proto__属性,可以发现奇妙的关系.(最终是null的)

    最后,总结一下:

    1. JavaScript是一个神奇的语言,一切皆对象.
    2. 对象都有一个__proto__属性,指向它的类的``prototype`
    3. 类是通过函数来定义的,定义的这个函数又是这个类的constructor属性值
    4. 每个构造函数constructor 都有一个原型对象prototype
    5. JavaScript使用prototype链实现继承机制
    6. 子类是可以通过prototype链修改其父类属性,以及爷爷类的属性值的

    0x02 什么是原型链污染

    做一个简单的实验,其实也是对前面的一个总结

    // foo是一个简单的JavaScript对象
    let foo = {bar: 1}
    
    // foo.bar 此时为1
    console.log(foo.bar)
    
    // 修改foo的原型(即Object)
    foo.__proto__.bar = 2
    
    // 由于查找顺序的原因,foo.bar仍然是1
    console.log(foo.bar)
    
    // 此时再用Object创建一个空的zoo对象
    let zoo = {}
    
    // 查看zoo.bar
    console.log(zoo.bar)
    

    zoo.bar的结果是2;

    因为前面修改了foo的原型foo.__proto__.bar = 2,而foo是一个Object类的实例,所以实际上是修改了Object这个类,给这个类增加了一个属性bar,值为2。

    后来,又用Object类创建了一个zoo对象let zoo = {},zoo对象自然也有一个bar属性了。

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

    0x03 哪些情况下原型链会被污染

    1. 最显然的情况

    obj[a][b] = value
    obj[a][b][c] = value
    

    如果控制了a,b,c及value就可以进行原型链污染的攻击,

    可以控制a=__proto__

    2.利用某些API来进行攻击

    • Object recursive merge
    merge (target, source)
    	foreach property of source
    		if property exists and is an object on both the target and the source
    			merge(target[property], source[property])
    		else
    			target[property] = source[property]
    

    这种情况下,__proto__必须被视为key才能成功

    对于

    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]
            }
        }
    }
    
    //1. 
    let o1 = {}
    let o2 = {a: 1, "__proto__": {b: 2}}
    merge(o1, o2)
    console.log(o1.a, o1.b)
    
    o3 = {}
    console.log(o3.b)//undefined
    
    //2. 
    let o1 = {}
    let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
    merge(o1, o2)
    console.log(o1.a, o1.b)
    
    o3 = {}
    console.log(o3.b)//2
    

    1和2两种情况是不一样的.

    因为前面代码中 __proto__已经代表o2的原型了 ,没有被看成一个key

    后面的代码中经过JSON.parse解析,__proto__就代表了一个key

    详情可参考https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x04

    • Property definition by path
    theFunction(object, path, value)
    

    如果攻击者可以控制path,如path=__proto__.myValue.就可以进行污染

    • Object clone
    function clone(obj) {
    	return merge({}, obj);
    }
    

    3. 找出易受攻击的API

    在https://github.com/HoLyVieR/prototype-pollution-nsec18这个项目的paper里面,作者给了一个找易受攻击API的脚本.

    node xx.js library-name

    var process = require('process');
    
    //check是否收到污染
    function check() {
    	if ({}.test == "123" || {}.test == 123) {
    		delete Object.prototype.test;
    		return true;
    	}
    	return false;
    }
    
    function run(fnct, sig, name, totest) {
    	// Reinitialize to avoid issue if the previous function changed attributes.
    	BAD_JSON = JSON.parse('{"__proto__":{"test":123}}');
    
    	try {
    		fnct(totest);
    	} catch (e) {}
    
    	if (check()) {
    		console.log("Detected : " + name + " (" + sig + ")");
    	}
    }
    
    var BAD_JSON = {};//NULL OBJ
    var args = process.argv.slice(2);//node xx.js param1 param2 parma3获取所有参数 返回数组[param1,param2,param3]
    
    //忽略异常
    process.on('uncaughtException', function(err) { });
    
    var pattern = [{
    	fnct : function (totest) {
    		totest(BAD_JSON);
    	},
    	sig: "function (BAD_JSON)"
    },{
    	fnct : function (totest) {
    		totest(BAD_JSON, {});
    	},
    	sig: "function (BAD_JSON, {})"
    },{
    	fnct : function (totest) {
    		totest({}, BAD_JSON);
    	},
    	sig: "function ({}, BAD_JSON)"
    },{
    	fnct : function (totest) {
    		totest(BAD_JSON, BAD_JSON);
    	},
    	sig: "function (BAD_JSON, BAD_JSON)"
    },{
    	fnct : function (totest) {
    		totest({}, {}, BAD_JSON);
    	},
    	sig: "function ({}, {}, BAD_JSON)"
    },{
    	fnct : function (totest) {
    		totest({}, {}, {}, BAD_JSON);
    	},
    	sig: "function ({}, {}, {}, BAD_JSON)"
    },{
    	fnct : function (totest) {
    		totest({}, "__proto__.test", "123");
    	},
    	sig: "function ({}, BAD_PATH, VALUE)"
    },{
    	fnct : function (totest) {
    		totest({}, "__proto__[test]", "123");
    	},
    	sig: "function ({}, BAD_PATH, VALUE)"
    },{
    	fnct : function (totest) {
    		totest("__proto__.test", "123");
    	},
    	sig: "function (BAD_PATH, VALUE)"
    },{
    	fnct : function (totest) {
    		totest("__proto__[test]", "123");
    	},
    	sig: "function (BAD_PATH, VALUE)"
    },{
    	fnct : function (totest) {
    		totest({}, "__proto__", "test", "123");
    	},
    	sig: "function ({}, BAD_STRING, BAD_STRING, VALUE)"
    },{
    	fnct : function (totest) {
    		totest("__proto__", "test", "123");
    	},
    	sig: "function (BAD_STRING, BAD_STRING, VALUE)"
    }]
    
    if (args.length < 1) {
    	console.log("First argument must be the library name");
    	exit();
    }
    
    try {
    	var lib = require(args[0]);
    } catch (e) {
    	console.log("Missing library : " + args[0] );
    	exit();
    }
    
    var parsedObject = [];
    
    function exploreLib(lib, prefix, depth) {
    	if (depth == 0) return;
    	if (parsedObject.indexOf(lib) !== -1) return;
    
    	parsedObject.push(lib);
    
    	for (var k in lib) {
    		if (k == "abort") continue;
    		if (k == "__proto__") continue;
    		if (+k == k) continue;
    
    		console.log(k);
    	
    		if (lib.hasOwnProperty(k)) {
    			for (p in pattern) {
    				if (pattern.hasOwnProperty(p)) {
    					run(pattern[p].fnct, pattern[p].sig, prefix + "." + k, lib[k]);
    				}
    			}
    
    			exploreLib(lib[k], prefix + "." + k, depth - 1);
    		}
    	}
    
    	if (typeof lib == "function") {
    		for (p in pattern) {
    			if (pattern.hasOwnProperty(p)) {
    				run(pattern[p].fnct, pattern[p].sig, args[0], lib);
    			}
    		}
    	}
    }
    
    exploreLib(lib, args[0], 5);
    
    

    下面也是paper里面给出的几个library

    1. Merge function

    • hoek
      hoek.merge
      hoek.applyToDefaults
      Fixed in version 4.2.1
      Fixed in version 5.0.3

    • lodash
      lodash.defaultsDeep
      lodash.merge
      lodash.mergeWith
      lodash.set
      lodash.setWith
      Fixed in version 4.17.5

    • merge
      merge.recursive
      Not fixed. Package maintainer didn’t respond to the disclosure.

    • defaults-deep
      defaults-deep
      Fixed in version 0.2.4

    • merge-objects
      merge-objects
      Not fixed. Package maintainer didn’t respond to the disclosure.

    • assign-deep
      assign-deep
      Fixed in version 0.4.7

    • merge-deep
      Merge-deep
      Fixed in version 3.0.1

    • mixin-deep
      mixin-deep
      Fixed in version 1.3.1

    • deep-extend
      deep-extend
      Not fixed. Package maintainer didn’t respond to the disclosure.

    • merge-options
      merge-options
      Not fixed. Package maintainer didn’t respond to the disclosure.

    • deap
      deap.extend
      deap.merge
      deap
      Fixed in version 1.0.1

    • merge-recursive

      merge-recursive.recursive

      Not fixed. Package maintainer didn’t respond to the disclosure.

    2. Clone

    • deap
      deap.clone
      Fixed in version 1.0.1

    3. Property definition by path

    • lodash
      lodash.set
      lodash.setWith
    • pathval
      pathval.setPathValue
      pathval
    • dot-prop
      dot-prop.set
      dot-prop
    • object-path
      object-path.withInheritedProps.ensureExists
      object-path.withInheritedProps.set
      object-path.withInheritedProps.insert
      object-path.withInheritedProps.push
      object-path

    0x04 Attacking

    1. 拒绝服务

    JS中的Object默认带了一些属性,如toString和valueOf,利用原型链污染他们,可能导致整个程序停止运行

    toString()将Object转换为字符串格式返回,valueOf()返回数值或者bool值等

    {}__proto__.toString="123"

    {}__proto__.valueOf="123"

    eg: server.js

    var _ = require('lodash');
    var express = require('express');
    var app = express();
    var bodyParser = require('body-parser');
    
    app.use(bodyParser.json({ type: 'application/*+json' }))
    app.get('/', function (req, res) {
    res.send("Use the POST method !");
    });
    
    app.post('/', function (req, res) {
    _.merge({}, req.body);
    res.send(req.body);
    });
    
    app.listen(3000, function () {
    console.log('Example app listening on port 3000!')
    });
    

    payload{"__proto__":{"toString":"123","valueOf":"It works !"}}

    2. for循环污染

    就像下面的for循环,如果commands里面有,就可以执行恶意代码,污染等

    {
        “__proto__”:{“my malicious command”:”echo yay > /tmp/evil”}
    }
    
    var execSync = require('child_process').execSync;
    
    function runJobs() {
    	var commands = {
    	"script-1" : "/bin/bash /opt/my-script-1.sh",
    	"script-2" : "/bin/bash /opt/my-script-2.sh"
    	};
    
    	for (var scriptname in commands) {
    		console.log("Executing " + scriptname);
    		execSync(commands[scriptname]);
    	}
    }
    

    3. 属性注入

    注意到,如果污染了某个类的prototype,那么那些没有被显式定义的对象都会受到影响.

    NodeJS 的 http模块支持很多header同一个name.

    所以如果污染了如cookie等.会造成很有意思的攻击.可能导致所有用户公用一个session

    {“__proto__”:{“cookie”:”sess=fixedsessionid; garbage=”}}

    0x05 针对Prototype pollution的防御

    1. 原型冻结

    ECMAScript5标准中添加的一个特性.

    使用该特性后,对于对象属性的修改都将失败

    eg:

    Object.freeze() 冻结对象, 冻结的对象无法再更改.我们无法添加,编辑或删除其中的属性

    Object.freeze(Object.prototype);
    Object.freeze(Object);
    ({}).__proto__.test = 123
    ({}).test // this will be undefined
    

    2. Schema validation of JSON input

    NPM上的多个库(例如:avj)都为JSON数据提供了模式验证

    可以在json规则里添加additionalProperties=false

    3. 使用MAP代替Object

    MAP是EcmaScript 6标准中新增的

    需要使用key/value模式时,尽量用MAP

    1.js创建map对象
    var map = new Map();
    2.将键值对放入map对象
    map.set("key",value)
    map.set("key1",value1)
    map.set("key2",value2)
    3.根据key获取map值
    map.get(key)
    4.删除map指定对象
    delete map[key]
    或
    map.delete(key)
    5.循环遍历map
    map.forEach(function(key){
      console.log("key",key)  //输出的是map中的value值
    })
    

    4.Object.create(null)

    可以用JavaScript创建没有任何原型的对象 : Object.create(null),用Object.creat创建的对象没有__proto__constructor 以这种方式创建对象可以帮助减轻原型污染攻击

    var obj = Object.create(null);
    obj.__proto__ // undefined
    obj.constructor // undefined
    

    参考:

    1. https://github.com/HoLyVieR/prototype-pollution-nsec18
    2. https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
  • 相关阅读:
    Linux下Mysql自启动
    C++的Vector用法
    如何判断一个文本文件内容的编码格式 UTF-8 ? ANSI(GBK)
    windows自带记事本导致文本文件(UTF-8编码)开头三个字符乱码问题
    C/C++字符串查找函数
    C++ string 字符串查找匹配
    CentOS6.5升级autoconf版本 Autoconf version 2.64 or higher is required
    Linux命令之远程下载命令:wget
    Linux常用命令大全
    如何使用VisualStudio2013编写和调试c语言程序
  • 原文地址:https://www.cnblogs.com/l0nmar/p/13951739.html
Copyright © 2020-2023  润新知