JavaScript原型链及其污染
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
作者 | xluoxluo
来源 | urlify.cn/Mz22Ar
一.什么是原型链?
1.JavaScript中,我们如果要define一个类,需要以define构造函数(类)的方式来define:
function xluo() { //定义类xluo()
this.a = 10086 //this.a是xluo类的一个属性(方法)
}
new xluo() //实例对象
2.了解prototype
and __proto__
, 为了方便理解,通常我们说JavaScript里面'一切皆对象'。
(可以通过函数和对象的关系来了解原型链)
对象有__proto__
属性,函数(类)有prototype
属性;
对象由函数(类)生成;
生成实例对象时,实例对象的__proto__
属性指向函数(类)的prototype
属性,这也是JavaScript默认原型链指向逻辑
var xluo = {} //we actually use Object fuction to generate
xluo.__proto__ === Object.prototype
//true
var xluo = Object()
xluo.__proto__ === Object.prototype
//true
------------------------------------------------
//JavaScript一切皆对象,即函数也是对象的一种
function xluo(){} //function函数创建对象
xluo.__proto__ === Function.prototype
//true
typeof xluo
//"function"
Function.__proto__ === Function.prototype
//true
Object.__proto__ === Function.prototype
//true
var xluogood = new xluo()
xluogood.__proto__ === xluo.prototype
//true
3.原型链继承
对象的属性继承自generate它的函数的prototype。
xluogood中的xluo、Object的原型方法是通过原型链继承得到的。
继承的过程可以表示为xluogood.__proto__ = xluo.prototype
,
大多数情况下,__proto__
可以理解为“构造器的原型”,即__proto__
===constructor.prototype
,
但是通过 Object.create()创建的对象有可能不是。
即实例对象.__proto__ = 构造器(也就是构造函数).prototype
。
现在我们通过上面的一条原型链来进行分析:
xluogood为实例对象,它的构造器(构造函数)为xluo,以xluo为原型,
第一链为xluogood.__proto__ === xluo.prototype
;
xluo.prototype
为普通对象,构造器为Object,以Object为原型,
第二链为xluo.prototype.__proto__ === Object.prototype
;
Object.prototype以Null为原型,
第三链为Object.prototype.__proto__ === null
;
而这里也是该原型链的终点。
此时我们再看下面那张图是不是很容易理解呢。
4.通过prototype链实现继承
一般函数默认的prototype是系统自动生成的一个对象
当js引擎需要执行对象的属性或方法,会先查找对象本身是否存在该属性或方法,如果不存在则会在原型链上查找。
一般函数默认的prototype
是一个类型为"object"
的对象,它有两个属性:constructor
和 __proto__
。
其中constructor
属性指向这个函数自身,__proto__
属性指向Object.prototype
,这说明一般函数的prototype
属性是由Object
函数生成的。
通过学习prototype,我们可以发现:
每个构造函数(constructor)都有一个原型对象(prototype);
对象的__proto__
属性,都指向类(构造函数)的原型对象prototype
;
即JavaScript实际上使用prototype链实现继承机制。
function xluo(){}
typeof xluo.prototype
//"object"
xluo.prototype.constructor === xluo
//true
所以通过学习我们会产生一个疑惑,
如果是特殊函数Function,Object,
那么这种情况又是怎么样的呢?
typeof Object.prototype
//"oject"
可以看到Object
函数的prototype
属性也是一个类型为"object"
的对象,但和一般函数的默认prototype
属性不一样的是,它多了一大堆方法,这些方法都是JavaScript对象的系统默认方法。
再仔细看,Object函数的prototype
属性里没有__proto__
属性,我们试着把它的__proto__
属性打出来看看:
Object.prototype.__proto__
//null
Object.prototype.__proto__ === null
,提前到达了终点。
typeof Object.prototype === "object"
,说明它是一个Object对象,如果它由Object函数生成,于是按照我们上面的通用规则,就该是Object.prototype.__proto__ === Object.prototype
。
但这样的话Object.prototype.__proto__
属性指向了它自身,这样以__proto__
属性构成的原型链就没有终点。
所以在原型链的最顶端,JavaScript规定了Object.prototype.__proto__ === null
。
typeof Function.prototype //有别于其他函数的object
//"function"
Function.prototype.__proto__ === Object.prototype
//true
一个"function"
类型的对象,应该是由Function函数生成的,那它的prototype
属性应该指向Function.prototype
,也就是Function.prototype.__proto__ === Function.prototype
。
为了避免循环引用,所以JavaScript规定Function.prototype.__proto__ === Object.prototype
,这样既避免了出现循环引用,又让__proto__
构成的原型链指向了唯一的终点:Object.prototype.__proto__ === null
。
5.JavaScript原型链继承实践
function me() {
this.first_name = 'xu'
this.last_name = 'ruilong'
}
function you() {
this.first_name = 'nb'
}
you.prototype = new me()
you = new you() //通过you类建立对象you
you类继承了me类的last_name属性。
in fact,在调用you.last_name
的时候,JavaScript执行以下操作:
1.在实例对象you寻找last_name
2.如果找不到,则在you.__proto__
寻找last_name
3.如果还找不到,则在you.__proto__.__proto__
中寻找last_name
4.本题you.__proto__.__proto__.__proto__.__proto__ === null
6.JavaScript污染实践(一)
var xluo = {
age : 3
}
xluo.__proto__.age = 18
xluoxluo = {}
console.log(xluoxluo.age) //已污染
因为我们修改了xluo的原型,而xluo和xluoxluo同样是Object类的实例,所以事实上我们修改了Object,给Object增加了属性age,值为18。
总结:
在同一个应用中,If an attacker controls and modifies the prototype of an object, it will be able to affect all objects that come from the same class as the object. This attack method is prototype chain pollution.
ps:在JavaScript中访问一个对象的属性可以用a.b.c或者a["b"]["c"]
来访问。(也就是当过滤 . 的时候,我们可以考虑通过字符 [] 进行绕过)
由于对象是无序的,当使用第二种方式访问对象时,只能使用指明下标的方式去访问。因此我们可以通过a["__proto__"]
的方式去访问其原型对象。
7.JavaScript污染实践(二)
现在我们大概弄清了有关原型链污染的基础知识,那么现在构建一串代码
function merge(a,b){ //merge(a,b)意为合并a,b
for (flag in b){
if(flag in a && flag in b){
merge(a[flag],b[flag])
}
else{
a[flag] = b[flag]
}
}
}
xluo = {}
xluoxluo = JSON.parse('{"flag":1,"__proto__":{"flagflag":2}}')
merge(xluo,xluoxluo)
console.log(xluo.flag,xluo.flagflag)
xluogood = {}
console.log(xluogood.flagflag)
JSON.parse() 方法用于将一个 JSON 字符串转换为对象。
此时通过__proto__
污染Object。
7.JavaScript污染实践(三)ctf
以下是来自 hackit 2018 的一道题目
(该题目原文链接为https://www.smi1e.top/javascript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93)
const express = require('express')
var hbs = require('hbs');
var bodyParser = require('body-parser');
const md5 = require('md5');
var morganBody = require('morgan-body');
const app = express();
var user = []; //empty for now
var matrix = [];
for (var i = 0; i < 3; i++){
matrix[i] = [null , null, null];
}
function draw(mat) {
var count = 0;
for (var i = 0; i < 3; i++){
for (var j = 0; j < 3; j++){
if (matrix[i][j] !== null){
count += 1;
}
}
}
return count === 9;
}
app.use('/static', express.static('static'));
app.use(bodyParser.json());
app.set('view engine', 'html');
morganBody(app);
app.engine('html', require('hbs').__express);
app.get('/', (req, res) => {
for (var i = 0; i < 3; i++){
matrix[i] = [null , null, null];
}
res.render('index');
})
app.get('/admin', (req, res) => {
/*this is under development I guess ??*/
if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){
res.send('Hey admin your flag is flag{prototype_pollution_is_very_dangerous}');
}
else {
res.status(403).send('Forbidden');
}
}
)
app.post('/api', (req, res) => {
var client = req.body;
var winner = null;
if (client.row > 3 || client.col > 3){
client.row %= 3;
client.col %= 3;
}
matrix[client.row][client.col] = client.data;
console.log(matrix);
for(var i = 0; i < 3; i++){
if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){
if (matrix[i][0] === 'X') {
winner = 1;
}
else if(matrix[i][0] === 'O') {
winner = 2;
}
}
if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){
if (matrix[0][i] === 'X') {
winner = 1;
}
else if(matrix[0][i] === 'O') {
winner = 2;
}
}
}
if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){
winner = 1;
}
if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){
winner = 2;
}
if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){
winner = 1;
}
if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){
winner = 2;
}
if (draw(matrix) && winner === null){
res.send(JSON.stringify({winner: 0}))
}
else if (winner !== null) {
res.send(JSON.stringify({winner: winner}))
}
else {
res.send(JSON.stringify({winner: -1}))
}
})
app.listen(3000, () => {
console.log('app listening on port 3000!')
})
对于以上的代码,我们首先关注到if语句,这是一个很明显的判断语句。
我们可以发现访问admin获取flag需要user.admintoken
===req.query.querytoken
,
而user = []
,他是Array
的实例,继承自Array.prototype
。
api接口处有一处post操作,我们可以通过__proto__
对Array.prototype
进行赋值。
或者用python跑
reference:1.https://www.jianshu.com/p/3d756c5bba16
2.https://www.jianshu.com/p/686b61c4a43d
3.题目处链接
4.https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html#0x01-prototype__proto__
粉丝福利:实战springboot+CAS单点登录系统视频教程免费领取
???
?长按上方微信二维码 2 秒 即可获取资料
感谢点赞支持下哈