JavaScript原型鏈及其污染
1.什么是原型鏈?
(1).JavaScript中,我們如果要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
2.原型鏈繼承
對象的屬性繼承自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
;
而這里也是該原型鏈的終點。
此時我們再看下面那張圖是不是很容易理解呢。
3.通過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
。
4.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
5.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__"]
的方式去訪問其原型對象。
6.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 <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接口處有一處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__