整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          一文搞懂JavaScript原型及原型鏈(附代碼)

          avaScript 是一種基于原型的面向對象語言。雖然你經常會看到class關鍵字,但它的底層本質還是用作原型。

          在本文中,我們將了解 JavaScript 的原型性質,以及對象中的原型鏈。

          首先檢查以下代碼:

          const animals = {

          name: "animal",

          type: "object",

          }

          animals.hasOwnProperty("name")// true

          但是我們并沒有給animals定義方法hasOwnProperty,可它為什么可以調用該方法?在本文結束時,我們將了解其工作原理以及更多內容。

          一、原型的概念

          根據查閱得到的字面意思的解釋是:

          指一個詞語或一個類型意義的所有典型模型或原形象,是一個類型的典型特征,我們可以用下圖中的車輛示例很好地解釋這一點。

          原型“A”是創建其他版本(如“B”和“C”)的第一個版本。“A”包含車輛應具備的最基本功能,而“B”和“C”將包含更多的功能。

          這意味著,“B”和“C”是“A”的改進版本,但仍包含“A”的特征。“A”有四個輪胎,“B”也有四個輪胎,但是可以飛,“C”同樣有四個輪胎,但是可以在水上行駛。

          JavaScript 在原型的基礎上工作。在每個函數的聲明中,JavaScript 引擎將prototype屬性添加到該函數,這使該函數成為可以創建其他版本的原型。我們可以通過打印其屬性確認:

          function hello() {

          console.log("hello")

          }

          console.dir(hello)

          結果:

          如上圖所示,顯示了函數的屬性,hello函數中包括了prototype屬性, 以及另一個名為__proto__的屬性。本文稍后會詳細介紹。

          該prototype對象有兩個屬性:一個名為constructor以及另一個同樣名為__proto__的屬性。前者指向hello函數,后者指向Object。

          二、原型的好處

          說原型的好處之前我們先說一下構造函數,這是創建對象的一種方式,如下所示:

          function Hello() {

          console.log("hello")

          }

          const anotherVersion = new Hello()

          anotherVersion.type = "new"

          console.log(anotherVersion)

          Hello首字母大寫是一種約定,表示該函數可以用作構造對象,這個函數也被稱為構造函數。

          結果:

          結果現在向我們展示了這個anotherVersion對象是一個從Hello函數通過new關鍵字而變化來的。你可以通過這種方式去創建類似具有相同功能的對象,例如:

          function Obj(name) {

          this.name = name;

          this.printName = function () {

          console.log(this.name)

          }

          }

          const javascript = new Obj("javascript")

          const java = new Obj("java")

          console.log(javascript)// Obj {name: 'javascript', printName: f}

          console.log(java)// Obj {name: 'java', printName: f}

          構造函數中的this變量指向構造函數new出來的實例化對象(在上面的代碼中是javascript和java)。

          我們可以看到,雖然javascript和java具有不同的名稱值,但它們具有相同的功能代碼。

          使用原型的好處就是,你可以通過一個構造函數去創建很多具有相同功能的對象,并且這些對象都具有不同的名字。

          還記得上面hello函數有兩個屬性:prototype和__proto__。prototype還有兩個屬性:constructor和__proto__。使用構造函數創建對象時,使用了prototype屬性的constructor屬性,讓我們用下面的代碼檢查一下:

          function Obj(name) {

          this.name = name

          this.printName = function () {

          console.log(this.name)

          }

          }

          const javascript = new Obj("javascript")

          console.log(javascript)

          結果:

          從上圖中,你會看到__proto__屬性連接到我們的構造函數Obj。

          三、與原型共享功能

          現在我們知道函數的prototype屬性使該函數成為可用于創建其他對象的原型。

          如果該prototype屬性有其他屬性呢?我們知道,JavaScript 對象可以在任何時候添加新的屬性,讓我們來看看:

          function Obj(name) {

          this.name = name

          this.printName = function () {

          console.log(this.name)

          }

          }

          const javascript = new Obj("javascript")

          Obj.prototype.printType = function () {

          console.log(this.type)

          }

          console.log(javascript)

          結果:

          如上圖所示,__proto__屬性現在有一個printType方法,但對象javascript本身沒有printType方法。由上面所述結果我們可以知道,__proto__屬性連接我們的構造函數,由于javascript在默認情況下可以訪問__proto__屬性中的構造函數,因此它也可以訪問printType。因此,以下操作將正常工作:

          javascript.printType()// undefined

          javascript.type = "language"

          javascript.printType()// language

          JavaScript 是如何做到這一點的呢?首先它檢查對象是否存在該方法,如果不存在,它檢查__proto__屬性。

          四、原型鏈

          我們看最后一張圖片,你會注意到車輛B和C也有自己的原型,這意味著Obj用作原型的對象也繼承了另一個原型的一些特性,這稱為原型鏈。

          這說明,一個對象可以是原型的新版本,同時也是另一個對象的原型。因此,當你嘗試訪問對象上的屬性時,JavaScript引擎開始從對象自身中查找該屬性,如果沒有,它會繼續檢查__proto__,一直到沒有__proto__或者找到該屬性。如果找到最后,此屬性不存在時,返回undefined

          五、總 結

          回到第一個代碼塊:

          const animals = {

          name: "animal",

          type: "object",

          }

          animals.hasOwnProperty("name")// true

          到現在你應該清楚了,對吧?

          當你的animals在控制臺打印的時候,您會注意到它有一個__proto__指向Object的原型。并且,Object的原型具有hasOwnProperty屬性。animals繼承了該屬性,這使得它可以使用該屬性。

          Object在 JavaScript 中有一個所有對象都能繼承的原型。Function、String等構造函數也從Object繼承了屬性。

          這也就是為什么string.toLowerCase()也可以直接使用的原因。構造函數的原型對象String具有所有這些屬性,因此字符串可以使用它們。

          在瀏覽某個論壇的時候,第一次看到了JavaScript原型鏈污染漏洞。當時非常的好奇,當時我一直以為js作為一種前端語言,就算存在漏洞也是針對前端,不會危害到后端,因此我以為這種漏洞危害應該不大。可當我看到他的漏洞危害還有可以執行任意命令的時候,發現可能我想到有點簡單了。js也是可以用來做后端語言的。這篇文章就來認識一下這個漏洞。

          JavaScript原型鏈是什么?

          既然漏洞名稱是JavaScript原型鏈污染,那么首先就要先明白JavaScript原型鏈是什么。

          正如我們所知,Javascrip的復雜類型都是對象類型(Object),而js不是一門完全面對對象編程的語言。那么對于對象編程來說要考慮對象的繼承。

          js實現繼承的核心就是原型鏈我理解的就是原型鏈的存在就是js中的繼承機制,保證函數或對象中的方法,屬性可以向下傳遞。

          js使用了構造函數來創建對象,如下,我們可以通過構造函數來定義一個類:

          // 構造函數
          function Person(name, age) {
              this.name = name;
              this.age = age;
          }
          
          // 生成實例
          const p = new Person('zhangsan', 18);

          可以看到這個類除了我們定義的兩個屬性以外,還有一個prototype的屬性。prototype指向函數的原型對象,這是一個顯式原型屬性,只有函數才擁有該屬性。

          prototype也擁有兩個屬性:

          constructor:指向原型的構造函數

          prototype:指向了Object的原型

          在prototype的屬性中有一個_proto_,那么這個_proto_與prototype又有什么關系?

          原型prototype是類的一個屬性,而所有用類實例化的對象,都將擁有這個屬性中的所有內容,包括變量和方法。比如上圖中的p對象,其天生就具有類Person的屬性和方法。

          我們可以通過Person.prototype來訪問Person類的原型,但Person實例化出來的對象,是不能通過prototype訪問原型的。這時候,就該__proto__登場了。

          也就是類可以用prototype來訪問類的原型,而實例化的對象可以用_proto_來訪問對象所在類的prototype屬性。

          總結:

          其實我們只要明白一點就可以了,JavaScript原型鏈是js中實現繼承的核心,js的對象都會執行其它的原型,最后指向的原型為null。最后關于原型鏈再來總結一下

          1)js是通過原型鏈來實現繼承的。

          2)所有類對象在實例化的時候將會擁有prototype中的屬性和方法

          3)類可以使用prototype來訪問類的原型對象,而實例化對象可以通過_proto_來訪問類的原型對象

          let f = new Foo();
          f.constructor === Foo;
          f._proto_ === Foo.prototype
          f._proto_ === Foo.prototype
          Foo._proto_ === Function.prototype

          原型鏈污染

          在了解了原型鏈的相關知識以后,可以來看看竟然什么是原型鏈污染漏洞。

          上面說過實例化對象的__proto__指向了類的prototype。那么,如果我們修改了實例化對象__proto__中的值,是不是就可以修改類中的值呢?是否可以影響所有和這個對象來自同一個類、父祖類的對象?

          其實這就是原型鏈污染的原理。我們通過修改實例化對象的__proto__中的值,污染了類本體,進而影響所有和這個對象來自同一個類、父祖類的對象。

          p神的博客上有一個這樣的例子,用來說明原型鏈污染:

          實際情況下利用分析

          在實際的情況中,我們可能只能控制部分參數,那么我們怎么才能為__proto__賦值呢?

          要為__proto__賦值就要求__proto__作為變量傳進去并且作為鍵名,這種情況一般出現在下面的三種場景中:

          • 對象merge
          • 對象clone(其實內核就是將待操作的對象merge到一個空對象中)
          • 路徑查找屬性然后修改屬性的時候

          下面借用p神文章中的一個例子來看看具體操作,原文鏈接為:https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

          以對象merge為例,我們想象一個簡單的merge函數:

          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)

          結果是,合并雖然成功了,但原型鏈沒有被污染:

          這是因為,我們用JavaScript創建o2的過程(let o2 = {a: 1, "__proto__": {b: 2}})中,__proto__已經代表o2的原型了,此時遍歷o2的所有鍵名,你拿到的是[a, b],__proto__并不是一個key,自然也不會修改Object的原型。從下面的圖中也可以看出,在o1中,參數b并沒有出現在原型中。

          那么,如何讓__proto__被認為是一個鍵名呢?

          我們將代碼改成如下:

          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已經被污染

          這是因為,JSON解析的情況下,__proto__會被認為是一個真正的“鍵名”,而不代表“原型”,所以在遍歷o2的時候會存在這個鍵。

          再來看看o1發現,b屬性是定義在原型之中的。

          merge操作是最常見可能控制鍵名的操作,也最能被原型鏈攻擊,很多常見的庫都存在這個問題。

          js原型鏈污染漏洞分析

          接下來用一個cve漏洞來具體再看一下這個漏洞。

          3.4.0版本之前的jQuery存在一個原型污染漏洞CVE-2019-11358,PoC如下。

          //代碼如下,如果從前端接收一個json內容,傳到后端。
          //json內容:JSON.parse('{"__proto__": {"z": 123}}')
          
          const json1 = ajax();  
          jQuery.extend(true, {}, JSON.parse(json1));
          console.log( "test" in {} ); // true

          jQuery.extend () 函數用于將一個或多個對象的內容合并到目標對象

          $.extend( [deep ], target, object1 [, objectN ] )

          參數

          描述

          deep

          可選。 Boolean類型 指示是否深度合并對象,默認為false。如果該值為true,且多個對象的某個同名屬性也都是對象,則該"屬性對象"的屬性也將進行合并。

          target

          Object類型 目標對象,其他對象的成員屬性將被附加到該對象上。

          object1

          可選。 Object類型 第一個被合并的對象。

          objectN

          可選。 Object類型 第N個被合并的對象。

          再來看看實際的代碼是如何去寫入的。

          首先下載jQuery,這里下載的是3.3.0版本

          https://github.com/jquery/jquery/tree/3.3.0

          在src/core.js中文件中可以找到該extend函數。看過源碼,可以發現該函數的正好符合上面說的合并數據的概念,那么來看看它到底會不會被污染?

          我們來動態調試一下這個程序:

          引入jQuery腳本,并設置斷點,進行調試

          首先根據第一個參數判斷是否進行深度拷貝,然后進行第一次循環,取得參數為__proto__

          第二次循環,在__proto__中去參數進行賦值

          此時再看原型已經被污染了

          總結

          js原型鏈污染可以說原理并不是太難懂,關鍵是實際中如何去利用。關于這個漏洞也是看了很多大神的文章,它們的思路真的太厲害了,我還有很多需要學習的,跟大家一起共勉。

          由于本人水平有限,文章中可能會出現一些錯誤,歡迎各位大佬指正,感激不盡。如果有什么好的想法也歡迎交流,謝謝大家了~~

          參考鏈接

          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


          作者:Notadmin

          文章來源:FreeBuf

          avascript的原型鏈圖

          各種對象的__proto__和函數的prototype 都闡明了

          不能保證100% 正確(有問題直接注釋或者私信偶) 但是互聯網上原型鏈圖比這個全的偶沒見過(右上角的Number Date們需要改進)

          可以改進但是 不能再簡單了

          有點暈吧 大部分人都會暈

          所有網上 一堆 xxx.__proto__.__proto__.__proto__ 皆可由此圖搞定

          那些刁鉆古怪的問題退化為孔乙己的茴字問題

          死記硬背吧 先 要不改行

          90%的前端或者js程序員或者老師們對Javascript懂得不比這個多 嘿嘿

          給手機看的

          但是這個圖里的所有褐色單向箭頭鏈就是Javascript的原型鏈(顏色標注對理解js原型鏈很關鍵)

          原型鏈大部分時候是不可見的(__proto__在firefox nodejs中可見)那么圖退化為

          上二圖三特點

          1.所有對象都在原型鏈上

          2.除了null 每個對象都有且唯一的__proto__原型對象

          3.除了null, Object.prototype,其它對象的原型對象雖然不可通過.__proto__操作訪問 但是通過原型鏈上某個構造器(函數)的prototype屬性都可以訪問到

          js coder大多時候要面對的是

          優雅了吧 :-()

          所有javascript重度編碼都是操作上面這個圖的元素 但是你心里至少要有下圖

          如果連這圖都嫌棄不好記 最簡單的鐵三角 javascript 99%的幻化都由此來

          原型鏈的本質是嘛?以后分解 嘿嘿


          主站蜘蛛池模板: 国产免费一区二区三区不卡| 国产成人亚洲综合一区| 亚洲一区无码精品色| 国产suv精品一区二区6| 99久久精品日本一区二区免费| 亚洲午夜精品一区二区公牛电影院| 亚洲欧美日韩一区二区三区在线 | 无码日韩AV一区二区三区| 精品无码一区在线观看| 亚洲一区二区三区香蕉| 中文字幕在线视频一区| 日韩一区二区精品观看| 日韩免费视频一区| 亚洲精品伦理熟女国产一区二区| 91福利国产在线观看一区二区 | 国产伦精品一区二区三区视频猫咪 | 在线观看国产一区| 亚洲国产一区二区三区| 日韩一区二区三区免费播放| 国产成人一区二区动漫精品| 人妻精品无码一区二区三区 | 男人的天堂精品国产一区| 亚洲精品色播一区二区| 日本一区二区三区精品视频| 男人免费视频一区二区在线观看| 久久精品无码一区二区三区免费| 国产午夜精品片一区二区三区| 中文字幕一区二区三区人妻少妇| 性色av无码免费一区二区三区| 成人精品视频一区二区| 亚洲高清成人一区二区三区| 亚洲色欲一区二区三区在线观看| 中文字幕一区一区三区| 在线免费视频一区| 在线视频一区二区三区三区不卡| 亚洲精品国产suv一区88| 中文字幕一区日韩精品| 波多野结衣中文一区二区免费| 三上悠亚精品一区二区久久| 一区二区三区在线|欧| 国产凸凹视频一区二区|