JavaScript原型鏈污染
前言
在瀏覽某個論壇的時候,第一次看到了JavaScript原型鏈污染漏洞。當(dāng)時非常的好奇,當(dāng)時我一直以為js作為一種前端語言,就算存在漏洞也是針對前端,不會危害到后端,因此我以為這種漏洞危害應(yīng)該不大??僧?dāng)我看到他的漏洞危害還有可以執(zhí)行任意命令的時候,發(fā)現(xiàn)可能我想到有點(diǎn)簡單了。js也是可以用來做后端語言的。這篇文章就來認(rèn)識一下這個漏洞。
JavaScript原型鏈?zhǔn)鞘裁矗?/h2>
既然漏洞名稱是JavaScript原型鏈污染,那么首先就要先明白JavaScript原型鏈?zhǔn)鞘裁础?/p>
正如我們所知,Javascrip的復(fù)雜類型都是對象類型(Object),而js不是一門完全面對對象編程的語言。那么對于對象編程來說要考慮對象的繼承。
js實(shí)現(xiàn)繼承的核心就是原型鏈。我理解的就是原型鏈的存在就是js中的繼承機(jī)制,保證函數(shù)或?qū)ο笾械姆椒?,屬性可以向下傳遞。
js使用了構(gòu)造函數(shù)來創(chuàng)建對象,如下,我們可以通過構(gòu)造函數(shù)來定義一個類:
// 構(gòu)造函數(shù)
function Person(name, age) {
this.name = name;
this.age = age;
}
// 生成實(shí)例
const p = new Person('zhangsan', 18);
1692167778_64dc6e62b659f0a4498b9.png!small?1692167779350
可以看到這個類除了我們定義的兩個屬性以外,還有一個prototype的屬性。prototype指向函數(shù)的原型對象,這是一個顯式原型屬性,只有函數(shù)才擁有該屬性。
prototype也擁有兩個屬性:
constructor:指向原型的構(gòu)造函數(shù)
prototype:指向了Object的原型
在prototype的屬性中有一個_proto_,那么這個_proto_與prototype又有什么關(guān)系?
1692167810_64dc6e826449231561148.png!small?1692167811088
原型prototype是類的一個屬性,而所有用類實(shí)例化的對象,都將擁有這個屬性中的所有內(nèi)容,包括變量和方法。比如上圖中的p對象,其天生就具有類Person的屬性和方法。
我們可以通過Person.prototype來訪問Person類的原型,但Person實(shí)例化出來的對象,是不能通過prototype訪問原型的。這時候,就該__proto__登場了。
也就是類可以用prototype來訪問類的原型,而實(shí)例化的對象可以用_proto_來訪問對象所在類的prototype屬性。
總結(jié):
其實(shí)我們只要明白一點(diǎn)就可以了,JavaScript原型鏈?zhǔn)莏s中實(shí)現(xiàn)繼承的核心,js的對象都會執(zhí)行其它的原型,最后指向的原型為null。最后關(guān)于原型鏈再來總結(jié)一下
1)js是通過原型鏈來實(shí)現(xiàn)繼承的。
2)所有類對象在實(shí)例化的時候?qū)碛衟rototype中的屬性和方法
3)類可以使用prototype來訪問類的原型對象,而實(shí)例化對象可以通過_proto_來訪問類的原型對象
let f = new Foo();
f.constructor === Foo;
f._proto_ === Foo.prototype
f._proto_ === Foo.prototype
Foo._proto_ === Function.prototype原型鏈污染
在了解了原型鏈的相關(guān)知識以后,可以來看看竟然什么是原型鏈污染漏洞。
上面說過實(shí)例化對象的__proto__指向了類的prototype。那么,如果我們修改了實(shí)例化對象__proto__中的值,是不是就可以修改類中的值呢?是否可以影響所有和這個對象來自同一個類、父祖類的對象?
其實(shí)這就是原型鏈污染的原理。我們通過修改實(shí)例化對象的__proto__中的值,污染了類本體,進(jìn)而影響所有和這個對象來自同一個類、父祖類的對象。
p神的博客上有一個這樣的例子,用來說明原型鏈污染:
1692167987_64dc6f332874455dd5d81.png!small?1692167987836
實(shí)際情況下利用分析
在實(shí)際的情況中,我們可能只能控制部分參數(shù),那么我們怎么才能為__proto__賦值呢?
要為__proto__賦值就要求__proto__作為變量傳進(jìn)去并且作為鍵名,這種情況一般出現(xiàn)在下面的三種場景中:
- 對象merge
- 對象clone(其實(shí)內(nèi)核就是將待操作的對象merge到一個空對象中)
- 路徑查找屬性然后修改屬性的時候
下面借用p神文章中的一個例子來看看具體操作,原文鏈接為:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
以對象merge為例,我們想象一個簡單的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],那么,這個key如果是__proto__,是不是就可以原型鏈污染呢?
let o1 = {}
let o2 = {a: 1, "__proto__": {b: 2}}
merge(o1, o2)
console.log(o1.a, o1.b)
o3 = {}
console.log(o3.b)結(jié)果是,合并雖然成功了,但原型鏈沒有被污染:
1692168050_64dc6f7280e79c362a397.png!small?1692168051194
這是因?yàn)?,我們用JavaScript創(chuàng)建o2的過程(let o2 = {a: 1, "__proto__": {b: 2}})中,__proto__已經(jīng)代表o2的原型了,此時遍歷o2的所有鍵名,你拿到的是[a, b],__proto__并不是一個key,自然也不會修改Object的原型。從下面的圖中也可以看出,在o1中,參數(shù)b并沒有出現(xiàn)在原型中。
1692168062_64dc6f7e3ea57daf7bf33.png!small?1692168062943
那么,如何讓__proto__被認(rèn)為是一個鍵名呢?
我們將代碼改成如下:
let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2)
console.log(o1.a, o1.b)
o3 = {}
console.log(o3.b)可見,新建的o3對象,也存在b屬性,說明Object已經(jīng)被污染
1692168090_64dc6f9ae493107bec1d4.png!small?1692168091483
這是因?yàn)?,JSON解析的情況下,__proto__會被認(rèn)為是一個真正的“鍵名”,而不代表“原型”,所以在遍歷o2的時候會存在這個鍵。
再來看看o1發(fā)現(xiàn),b屬性是定義在原型之中的。
1692168096_64dc6fa0980dab3967fd7.png!small?1692168097212
merge操作是最常見可能控制鍵名的操作,也最能被原型鏈攻擊,很多常見的庫都存在這個問題。
js原型鏈污染漏洞分析
接下來用一個cve漏洞來具體再看一下這個漏洞。
3.4.0版本之前的jQuery存在一個原型污染漏洞CVE-2019-11358,PoC如下。
//代碼如下,如果從前端接收一個json內(nèi)容,傳到后端。
//json內(nèi)容:JSON.parse('{"__proto__": {"z": 123}}')
const json1 = ajax();
jQuery.extend(true, {}, JSON.parse(json1));
console.log( "test" in {} ); // truejQuery.extend () 函數(shù)用于將一個或多個對象的內(nèi)容合并到目標(biāo)對象
$.extend( [deep ], target, object1 [, objectN ] )
參數(shù) | 描述 |
deep | 可選。 Boolean類型 指示是否深度合并對象,默認(rèn)為false。如果該值為true,且多個對象的某個同名屬性也都是對象,則該"屬性對象"的屬性也將進(jìn)行合并。 |
target | Object類型 目標(biāo)對象,其他對象的成員屬性將被附加到該對象上。 |
object1 | 可選。 Object類型 第一個被合并的對象。 |
objectN | 可選。 Object類型 第N個被合并的對象。 |
再來看看實(shí)際的代碼是如何去寫入的。
首先下載jQuery,這里下載的是3.3.0版本
https://github.com/jquery/jquery/tree/3.3.0
在src/core.js中文件中可以找到該extend函數(shù)。看過源碼,可以發(fā)現(xiàn)該函數(shù)的正好符合上面說的合并數(shù)據(jù)的概念,那么來看看它到底會不會被污染?
1692168161_64dc6fe1e3ff617f47974.png!small?1692168162655
我們來動態(tài)調(diào)試一下這個程序:
引入jQuery腳本,并設(shè)置斷點(diǎn),進(jìn)行調(diào)試
1692168167_64dc6fe7b978b4973c2cd.png!small?1692168168393
首先根據(jù)第一個參數(shù)判斷是否進(jìn)行深度拷貝,然后進(jìn)行第一次循環(huán),取得參數(shù)為__proto__
1692168172_64dc6fecc462985824a7e.png!small?1692168173522
第二次循環(huán),在__proto__中去參數(shù)進(jìn)行賦值
1692168177_64dc6ff18c0335618e4f2.png!small?1692168178185
此時再看原型已經(jīng)被污染了
1692168182_64dc6ff620c4ded5d0a06.png!small?1692168182716
總結(jié)
js原型鏈污染可以說原理并不是太難懂,關(guān)鍵是實(shí)際中如何去利用。關(guān)于這個漏洞也是看了很多大神的文章,它們的思路真的太厲害了,我還有很多需要學(xué)習(xí)的,跟大家一起共勉。
由于本人水平有限,文章中可能會出現(xiàn)一些錯誤,歡迎各位大佬指正,感激不盡。如果有什么好的想法也歡迎交流,謝謝大家了~~
參考鏈接
https://www.freebuf.com/articles/web/275619.html
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
https://xz.aliyun.com/t/7025
https://www.freebuf.com/articles/web/264966.html
本文作者:, 轉(zhuǎn)載請注明來自FreeBuf.COM



























