參考文檔
https://www.cnblogs.com/jofun/p/8727814.html
https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html
原型鏈繼承
函數(shù)對象和實例對象
javascript中沒有類的概念,但是,它的很多“面向?qū)ο蟆辈僮骱湍切┯蓄惖恼Z言差不多。這是通過“原型對象”和“原型鏈”實現(xiàn)的。
javascript中的對象分為函數(shù)對象(構(gòu)造函數(shù))和實例對象兩類。
以下是一個函數(shù)對象的例子:
function wow(){
this.a=1;
this.b=2;
}
使用console.log();作用于原型對象的時候,它會直接打印出它的代碼。
console.log(wow);
? wow(){
this.a=1;
this.b=2;
}
以下是一些實例對象的例子:
var a=["hidden_er","onequiz","sjh"];
//默認(rèn)構(gòu)造方式,由 Array() 構(gòu)造,構(gòu)造函數(shù)自帶。(數(shù)組)
let o=new f();
//一般構(gòu)造方式,由上面那個函數(shù)對wow()構(gòu)造,構(gòu)造函數(shù)自定。
object1={"a":1,"b":2};
//JSON構(gòu)造方式,解析成鍵值對的形式,允許嵌套。由Object()構(gòu)造。
使用console.log();作用于實例對象的時候,它會打印出一堆層次性結(jié)構(gòu)。
原型對象
原型對象是實例對象,而不是函數(shù)對象。
原型對象null是萬物之源,即js原型繼承鏈的源頭。null對象沒有任何屬性。
我們介紹或熟知的js對象聲明方式無法直接聲明原型對象;原型對象是js自己創(chuàng)建的,通過一些屬性和我們創(chuàng)建的對象相關(guān)聯(lián)。
關(guān)鍵屬性
prototype:指向其對應(yīng)的原型對象。只有函數(shù)對象才擁有這個屬性。
你每通過代碼聲明一個構(gòu)造函數(shù),js都會自動生成一個它的原型對象,并且在你的函數(shù)對象中添加該屬性。
_proto_屬性:指向它的構(gòu)造函數(shù)的原型對象。只有實例對象才擁有這個屬性。
constructor屬性:指向它的構(gòu)造函數(shù)本身。任何對象都有這個屬性(因為任何對象都有相應(yīng)的構(gòu)造函數(shù))。
需要注意的是,構(gòu)造函數(shù)是函數(shù)對象,它有prototype屬性;而prototype原型對象中的constructor又指向由它的構(gòu)造函數(shù)本身。這是個套娃。
[[prototype]]:我覺得這玩意就是_proto_
證據(jù):
函數(shù)對象Object
在上文中,我們已經(jīng)知道,原型對象的萬物之源是null。
但這實際上還不夠;實際上,null沒有直接作為任何構(gòu)造函數(shù)的原型對象。我們獲取null的最快途徑是Object.prototype._proto_ ;即,構(gòu)造函數(shù)Object的原型對象的構(gòu)造函數(shù)的原型對象 是null。
(構(gòu)造函數(shù)本身屬性我不會讀取,要通過其原型對象的constructor屬性讀取;而null沒有這玩意,所以 構(gòu)造函數(shù)Object的原型對象的構(gòu)造函數(shù) 這一層實際上也是沒有東西的。)
這里不需要再深入理解了;我們只需要記住,構(gòu)造函數(shù)的萬物之源是Object,找到了Object就相當(dāng)于找到了null。(實際上找null也沒用,我們以后最深的地方也就是找Object)
還需要說明的是,以JSON生成的對象直接繼承于Object,數(shù)組繼承于Array-->Object,函數(shù)繼承于Function-->Object。這些都是JS自帶的原型對象。
總結(jié)0
在之后的利用中,
如果初始對象是函數(shù)對象,那就用一次prototype,然后開始_proto_一直向上跑;
如果初始對象是實例對象,那就直接_proto_一直向上跑。
總結(jié)1**
圖中黑字部分是原型鏈的核心部分;其中,原型對象的源頭是原型對象null(也可以理解為Object原型對象),構(gòu)造函數(shù)(函數(shù)對象)的源頭是構(gòu)造函數(shù)Function。
藍色部分是最初的外延擴展部分。其中,JSON對象是最特殊的,它直接與Object原型對象和構(gòu)造函數(shù)Object相關(guān);而數(shù)組相關(guān)的內(nèi)容實際上只需要理解為右邊內(nèi)容的特例,即,JS已經(jīng)寫好了相應(yīng)的構(gòu)造函數(shù)(并生成了相應(yīng)原型對象),不需要我們自己去寫。自定義構(gòu)造函數(shù)的對象可以一直繼承,從而把原型鏈一直延長下去。
箭頭標(biāo)注了這些東西相互之間的溝通情況及溝通方式。實例對象(包括但不限于原型對象)僅有1、2兩種屬性,而構(gòu)造函數(shù)(函數(shù)對象)有1、2、3三種屬性。
關(guān)于prototype
以上的很多分析實際上我們都不需要記;只需要記住一點:用prototype就是為了防止每次new的時候都運行一次方法。
具體的解釋,就是:在有類的面向?qū)ο缶幊陶Z言中,對象的方法肯定寫在類里面,而不是寫在構(gòu)造方法里面。而JS沒有類的概念,表面看起來想生成對象只能使用構(gòu)造方法,于是只能把對象的方法寫在構(gòu)造函數(shù)里面。這就導(dǎo)致,每生成一個對象,對象方法就會被執(zhí)行一次。我們往往不想這樣,所以,我們可以把對象方法寫入原型對象中,來避免這種情況。
//不使用prototype,每生成一個對象,都必須調(diào)用方法show。
function Foo() {
this.bar = 1
this.show = function() {
console.log(this.bar)
}
}
//使用prototype,避免了上述的尷尬情況
function Foo() {
this.bar = 1
}
Foo.prototype.show = function show() {
console.log(this.bar)
}
注意
原型鏈繼承 只是JS對象繼承中的一種方法;實際上,還有很多種方法可以實現(xiàn)繼承,具體可參考 參考文檔。
原型鏈污染
JS對象的屬性檢索
JavaScript 對象有一個指向一個原型對象的鏈。當(dāng)試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。
看一些樣例:
//原型鏈繼承與修改
var Parent=function(){
this.parent="this is an attribute in Parent";
}
var Children=function(){
this.children="this is an attribute in Children";
}
Children.prototype=new Parent();//原型鏈繼承的關(guān)鍵語句,把Children對應(yīng)的原型對象接到了Parent對應(yīng)的原型對象的前面。
//使用上述兩個函數(shù)對象分別創(chuàng)建實例對象
var a=new Children();
var b=new Parent();
console.log(a.children);
//由于原型鏈繼承,子實例對象具有父親的屬性。
console.log(a.parent);
//修改子實例對象的值;對其他任何東西都沒有影響。
a.parent='???';
var c=new Children();
console.log(c.parent);
//修改父實例對象的值;對其他任何東西也沒有影響。
b.parent='???';
console.log(c.parent);
//使用原型鏈;此處修改就是原型對象了,而不是實例對象。會對所有以 該原型對象對應(yīng)的函數(shù)對象 生成的實例對象產(chǎn)生影響,??!包含已經(jīng)實例化了的??!
a.__proto__.parent='???';
console.log(c.parent);
//不過,它不能對子類原有的屬性產(chǎn)生影響;因為即使在父類原型對象里修改了該屬性,在子類構(gòu)造方法里,它也會被覆蓋為正確的值。
a.__proto__.children='?????';
console.log(c.children);
回顯:
this is an attribute in Children
this is an attribute in Parent
this is an attribute in Parent
this is an attribute in Parent
???
this is an attribute in Children
也就是說,我們只要能控制一個子實例對象的內(nèi)容(屬性),就可以控制所有其祖先原型對象,以及用它的祖先原型對象生成的實例對象。
這是一個比較高的權(quán)限;很容易產(chǎn)生漏洞。
一個遺留問題
var Parent=function(){
this.parent="this is an attribute in Parent";
}
var Children=function(){
this.children="this is an attribute in Children";
}
Children.prototype=new Parent();
var a=new Children();
console.log(a.children);
console.log(a.constructor);
console.log(a.__proto__.constructor);
console.log(a.__proto__.__proto__.constructor);
console.log(a.__proto__.constructor.__proto__.constructor);
console.log(a.__proto__.__proto__.__proto__.constructor);
回顯:
this is an attribute in Children
? (){
this.parent="this is an attribute in Parent";
}
? (){
this.parent="this is an attribute in Parent";
}
? (){
this.parent="this is an attribute in Parent";
}
? Function() { [native code] } //構(gòu)造函數(shù)Function
? Object() { [native code] } //構(gòu)造函數(shù)Object
我對于第二個回顯很不理解,其他幾個回顯也沒完全搞清楚。
實際污染樣例01--Merge()
function merge(target,source){
for (let key in source){
console.log('key is: ',key,' ',source[key]);
if (key in source && key in target){
merge(target[key],source[key])
}
else{
target[key]=source[key]
}
}
}
let object1={}
let object2=JSON.parse('{"a":1,"__proto__":{"b":2}}')
console.log(object2)
merge(object1,object2)
console.log(object1.a,object1.b)
object3={}
console.log(object3)
在實際環(huán)境中,我們能夠得到的權(quán)限往往比之前所述的要少。一般來說,我們最多有任意傳參(JSON格式對象)的權(quán)限,而不能像之前那樣直接對 對象相關(guān)屬性 進行命令執(zhí)行。
此處能夠進行原型鏈污染的核心原因在于JSON和JS本身解析方式的不同:我們傳入的參數(shù),在經(jīng)過JSON解析后,"__proto__"被認(rèn)為是一個沒有特殊意義的普通的鍵名(就跟"a"一樣);但是,在Merge函數(shù)中,key掃到"__proto__"時,執(zhí)行的target[key]=source[key]
命令就變成了object1.__proto__=object2.__proto__
,導(dǎo)致Object原型函數(shù)被修改,進而影響了所有實例對象。
本文摘自 :https://www.cnblogs.com/