function xluo() { //定義類xluo() this.a = 10086 //this.a是xluo類的一個屬性 } new xluo()
prototype
and __proto__
, JavaScript裏面'一切皆對象'。__proto__
屬性,函數有prototype
屬性;__proto__
屬性指向函數的prototype
屬性。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
對象的屬性時從generate他的函數的prototype那裏獲得的。javascript
xluogood爲什麼有xluo、Object的原型方法,其實就是經過原型鏈繼承。html
繼承的過程能夠表示爲xluogood.__proto__ = xluo.prototype
,java
即對象.__proto__ = 構造器.prototype
。express
xluogood爲普通對象,它的構造器爲xluo,以xluo爲原型,json
第一鏈爲xluogood.__proto__ === xluo.prototype
;api
xluo.prototype
(注意這邊不是xluo)爲json對象,即普通對象,構造器爲Object,以Object爲原型,app
第二鏈爲xluo.prototype.__proto__ === Object.prototype
;函數
Object.prototype以Null爲原型,post
第三鏈爲Object.prototype.__proto__ === null
;ui
此時咱們再看下面那張圖是否是很容易理解呢。
[]
通常函數默認的prototype是系統自動生成的一個對象
當js引擎執行對象的屬性或方法時,先查找對象自己是否存在該方法,若是不存在則會在原型鏈上查找。
__proto__
屬性,指向類的原型對象prototype
通常函數默認的prototype
是一個類型爲"object"
的對象,它有兩個屬性:constructor
和 __proto__
。
大多數狀況下,__proto__
能夠理解爲「構造器的原型」,即__proto__
===constructor.prototype
,可是經過 Object.create()建立的對象有可能不是。
其中constructor
屬性指向這個函數自身,__proto__
屬性指向Object.prototype
,這說明通常函數的prototype
屬性是由Object
函數生成的。
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
。
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
var xluo = { age : 3 } xluo.__proto__.age = 18 xluoxluo = {} console.log(xluoxluo.age) //已污染
由於咱們修改了xluo的原型,而xluo和xluoxluo一樣是Object類的實例,因此事實上咱們修改了Object,給Object增長了屬性age,值爲18。
因此,in the same app,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.
在JavaScript中訪問一個對象的屬性能夠用a.b.c或者a["b"]["c"]
來訪問。因爲對象是無序的,當使用第二種方式訪問對象時,只能使用指明下標的方式去訪問。所以咱們能夠經過a["__proto__"]
的方式去訪問其原型對象。
如今咱們大概弄清了有關原型鏈污染的基礎知識,那麼如今構建一串代碼
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。
如下是來自 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 <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!') })
對於以上的代碼,咱們首先關注到if語句,這是一個很明顯的判斷語句。
咱們能夠發現訪問admin獲取flag須要user.admintoken
===req.query.querytoken
,
而user = []
,他是Array
的實例,繼承自Array.prototype
。
api接口處有一處賦值操做,咱們能夠經過__proto__
對Array.prototype
進行賦值。