原型 JavaScript 是一门面向对象的语言,但是在 ES6 之前,JavaScript 中没有 class 语法。不过在它的构造函数(constructor
)就相当于类,通过构造函数,我们可以生成实例化的对象。
1 2 3 4 5 6 function Cat ( ) { this .color = 'orange' } var cat = new Cat()console .log(cat.color)
prototype JavaScript中的每个函数都有一个prototype
属性,它指向调用该构造函数而创建的实例对象的原型。
proto 同样的,在 JavaScript 中,每个实例对象也都有一个__proto__
属性用来指向实例对象的原型。 实例对象的 __proto__
与创建该实例对象的构造函数的 prototype
是相等的。
1 2 3 4 5 6 7 function Cat ( ) { this .color = 'orange' } var cat = new Cat()console .log(cat.__proto__ === Cat.prototype)
constructor 每个原型对象都有一个 constructor 属性,指向相关联的构造函数,所以构造函数和构造函数的 prototype 是可以相互指向的。实例对象也可以访问constructor 属性指向其构造函数。
原型链 1 2 3 4 5 6 7 8 9 10 function Cat ( ) { this .color = 'orange' } Cat.prototype.age = 4 var cat = new Cat()console .log(cat.color) console .log(cat.age)
在 JavaScript 中,如果想访问某个属性,首先会在实例对象(cat)的内部寻找,如果没找到,就会在该对象的原型(cat.__proto__
,即 Cat.prototype
)上找,我们知道,对象的原型也是对象,它也有原型,如果在对象的原型上也没有找到目标属性,则会在对象的原型的原型(Cat.prototype.__proto__
)上寻找,以此内推,直到找到这个属性或者到达了最顶层。在原型上一层一层寻找,这便就是原型链了。 实例对象原型的原型是Object.prototype
,而它的原型是null,null 没有原型,所以 Object.prototype
就是原型链的最顶端。
1 2 3 4 5 6 function Cat ( ) { this .color = 'orange' } console .log(Cat.prototype.__ptoto__ === Object .prototype) console .log(Object .prototype.__proto__)
JavaScript 中的所有对象都来自 Object,Object 位于原型链的最顶端,几乎所有 JavaScript 的实例对象都是基于 Object。
继承 JavaScript 的继承是基于原型链的,在原型链的任何位置设置属性,都能被对象访问到,原型的作用也是在此,它可以包含所有实例共享的属性和方法,就像该属性本来就在实例对象上一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function Cat ( ) { this .color = 'orange' this .age = 4 } Cat.prototype.getColor = function ( ) { console .log(this .color) } Object .prototype.getAge = function ( ) { console .log(this .age) } var cat = new Cat()cat.getColor() cat.getAge() var a = ['hello' , 'world' ]function f ( ) {}console .log(a.__proto__ === Array .prototype) console .log(f.__proto__ === Function .prototype)
原型链污染 在JavaScript中访问一个对象的属性可以用a.b.c或者a[“b”][“c”]来访问。由于对象是无序的,当使用第二种方式访问对象时,只能使用指明下标的方式去访问。因此我们可以通过a["__proto__"]
的方式去访问其原型对象。 在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
实例 hackit 2018 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 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 = []; 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 ) => { if (user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){ res.send('Hey admin your flag is <b>flag{prototype_pollution_is_very_dangerous}</b>' ); } 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!' ) })
访问admin获取flag需要user.admintoken
===req.query.querytoken
,而user = []
,他是Array
的实例,继承自Array.prototype
。而api接口处有一处赋值操作,我们可以通过__proto__
对Array.prototype
进行赋值。
Referer 深入理解JavaScript Prototype污染攻击 Code Breaking 挑战赛 Writeup 原型与原型链 JavaScript 原型链污染 JavaScript原型链污染