原型污染

来源:https://www.cnblogs.com/jaxu/archive/2023/11/10/17824226.html
-Advertisement-
Play Games

使用不可信的數據,通過調用不安全的遞歸函數來暴露預設原型 原型污染:基礎 什麼是原型污染? 原型污染是一種針對JavaScript運行時的註入攻擊。通過原型污染,攻擊者可以控制對象屬性的預設值,從而篡改應用程式的邏輯並可能導致服務被拒絕,甚至在某些極端情況下遠程執行代碼。 現在,你是不是滿腦子充滿了 ...


使用不可信的數據,通過調用不安全的遞歸函數來暴露預設原型

原型污染:基礎

什麼是原型污染?

  原型污染是一種針對JavaScript運行時的註入攻擊。通過原型污染,攻擊者可以控制對象屬性的預設值,從而篡改應用程式的邏輯並可能導致服務被拒絕,甚至在某些極端情況下遠程執行代碼。

  現在,你是不是滿腦子充滿了各種疑問。到底什麼是“在運行時改寫對象的屬性”?它如何影響應用程式的安全?而且,更重要的是,我如何保護我的代碼免受這種攻擊?

關於本文

  原型污染可以很複雜,所以本文將分三部分進行介紹。

  1. 使用原型污染危害易受攻擊的API。
  2. 瞭解更多有關JavaScript原型的知識以及原型污染是如何工作的。
  3. 如何修複和防止應用程式中的原型污染。

  事實上,原型污染漏洞在許多流行的JavaScript庫中都被髮現過並修複了,包括jQuerylodashexpressminimisthoek等等。在jQuery中發現原型污染時,當時有74%的網站都在使用jQuery,聽起來有多可怕!

原型污染攻擊示例

  讓我們來演示一下在真實場景中原型污染是如何進行攻擊的。假設一個名為startup.io的公司決定發佈一個API,允許用戶通過app來管理公司的數據。

  不幸的是,由於開發過程中時間緊任務急,startup.io的工程師們根本來不及考慮API的安全問題,以至於他們忽略了安全掃描報告中發現的所有安全漏洞。於是,隨著該API緊急發佈,其中包含了許多bug和安全漏洞——其中之一就是原型污染。

  對startup.io來說這顯然是一個壞消息,但對攻擊者來說卻再好不過了,他們可以通過這些bug和安全漏洞對API進行攻擊。讓我們來看看startup.io發佈的API中的兩個endpoints:

  • 通過HTTP POST請求https://api.startup.io/users/:userId,使用userId更新用戶的數據
  • 通過HTTP GET請求https://api.startup.io/users/:userId/role,獲取指定用戶當前分配的角色(admin或者user

易受攻擊的API

  讓我們嘗試通過篡改應用程式的邏輯來將我們提升到管理員的許可權。之後,我們再嘗試以拒絕服務的方式來搞垮整個API。

  所有的示例都假設我們已經獲得了應用授權,為了便於可讀,我們省略了所有的HTTP授權header。

  我們發送一個有效的請求,來看看那個HTTP POST的endpoint是如何工作的。我們從API提供的文檔中可以瞭解到,這個endpoint允許我們修改顯示在用戶個人資料頁面上的“about”部分的內容。我們打算將這部分內容修改成“Database sanitization expert”。

  複製並粘貼下麵的內容到終端然後執行:

curl -H "Content-Type: application/json" -X POST -d '{"about": "Database sanitization expert"}' https://api.startup.io/users/1337

  我們應該會收到一個JSON格式的響應,其中存儲了有關該用戶的數據,我們可以看到“about”的內容被更新了:

{ name: "Robert", surname: "Tables", about: "Database sanitization expert" }

  接下來,讓我們發送另一個請求,來看看那個HTTP GET的endpoint是如何工作的。

  複製並粘貼下麵的內容到終端然後執行:

curl -X GET https://api.startup.io/users/1337/role

  我們應該會得到一個JSON數據,其中包含該用戶預設分配的角色。

攻擊1:失敗的嘗試

  現在我們知道API是如何工作的了,讓我們看看能否修改用戶的角色並將其設置為admin。我們試著通過POST請求直接將role改成admin

  複製並粘貼下麵的內容到終端然後執行:

curl -H "Content-Type: application/json" -X POST -d '{"role": "admin"}' https://api.startup.io/users/1337 && curl -X GET https://api.startup.io/users/1337/role

  我們應該得到以下輸出:

{ role: "user" }

  顯然,這種簡單的嘗試未能奏效。role的值依然是user。不過,我們的嘗試並不止於此!

攻擊2:使用原型污染提升許可權

  通過前面的內容,我們發現原型污染可以允許我們改寫應用程式中在任何對象上定義的任何屬性的值。也許我們可以借用這個漏洞來更改角色?讓我們再試一次——但是這次我們在要設置的屬性前添加了神奇的__proto__首碼。

  複製並粘貼下麵的內容到終端然後執行:

curl -H "Content-Type: application/json" -X POST -d '{"about": {"__proto__": {"role": "admin"}}}' https://api.startup.io/users/1337 && curl -X GET https://api.startup.io/users/1337/role

  然後我們得到:

{ role: "admin" }

  哈哈!通過向後端發送這樣一個神奇的內容{"about": {"__proto__": {"role": "admin"}}},我們成功地將自己的許可權提升為管理員。

  不過,等一下,這個神奇的__proto__首碼到底是什麼?為什麼它在這裡能起作用?別擔心,接下來我們會詳細討論它。但先讓我們把這個有bug的API給整癱瘓掉。

攻擊3:搞垮整個API

  在前面的攻擊中,我們設法將role的值改成任何我們想要的內容。但是,JavaScript函數不是也作為屬性存儲在它們各自的對象上嗎?那麼我們可以使用相同的方式來改寫一個函數嗎?

  讓我們試一下看看!在JavaScript中哪個函數最有可能被其它程式調用?答案是toString函數!讓我們試著將該函數改寫成一段毫無意義的內容,改成一段程式員的笑話怎麼樣?

  複製並粘貼下麵的內容到終端然後執行:

curl -H "Content-Type: application/json" -X POST -d '{"about": {"__proto__": {"toString": "Two bytes meet. The first byte asks: Are you ill? The second byte replies: No, just feeling a bit off."}}}' https://api.startup.io/users/1337

  我們應該得到以下輸出:

500 Internal Server Error

  API掛掉了。看來我們完全可以改寫一個函數!

  究竟發生了什麼?稍後我們會深入研究這其中的代碼。現在,我們已經得知我們能夠改寫toString方法,就像前面我們對role屬性所做的那樣。當JavaScript運行時,toString()總是被當作一個函數來調用,但是當我們修改之後它就不再是一個函數了(現在它是一段笑話,是一個字元串),因此整個web伺服器掛了,返回500錯誤。

原型污染工作原理

JavaScript中的原型是什麼?

  為了便於理解我們上面的攻擊過程,我們需要首先解釋一下什麼是JavaScript原型。

  當我們在JavaScript中創建一個空對象時(例如,const obj = {}),此時所創建的對象已經具有了一些屬性和方法,例如toString方法。你是否想過這些屬性和方法來自於哪裡?答案就是原型。

  許多面向對象語言,例如Java,都基於類來創建對象。每個對象都屬於一個類,這些類按照父子層級的結構組織在一起。當我們在一個對象上調用toString方法時,底層運行庫將會在該對象所屬的類中查找toString方法的定義。如果沒有找到,則在父類中進行查找,一直查到最頂層的類。

  相反,JavaScript是一種基於原型的面向對象編程語言,每個對象都鏈接到一個“prototype”(原型)。當我們在對象上調用toString方法時,JavaScript首先查看該對象上是否定義了這個方法,如果沒有,則在對象的原型上進行查找。

普通的JavaScript對象

const a = {};
console.log(typeof a.__proto__);

// Output: object

來自原型上的屬性

const a = {};
a.__proto__.someFunction = function () {
  console.log("Hello from the prototype!")
};
a.someFunction();

// Output: Hello from the prototype!

共用預設的原型

const a = {};
const b = new Object();
console.log(a.__proto__ === b.__proto__);

// Output: true

在共用的原型上設置屬性

const a = {};
const b = new Object();
a.__proto__.x = 1337;
console.log(b.x);

// Output: 1337

有關原型污染的解釋

  總之,如果我們修改了被多個對象共用的原型,那麼所有對象都會受到影響!這些對象甚至不需要處於同一作用域或其它相關的範圍中。記住,絕大多數對象預設都共用同一個原型——所以如果我們修改了其中一個對象的原型,其它的對象也會被改變!

  如果有人惡意修改(或者污染)了被多個對象共用的原型怎麼辦?事實上,這就是前面我們對start.io公司的API所做的操作。記住,我們發送給伺服器的內容是:

{"about": {"__proto__":"{"role": "admin"}}}
{"about": {"__proto__": {"toString": "Two bytes meet. The first byte asks: Are you ill? The second byte replies: No, just feeling a bit off."}}}

  通過發送一個特定的HTTP POST請求,我們污染了預設共用原型上的roletoString屬性。要瞭解這種攻擊是如何工作的,我們可以看下GET和POST的HTTP請求處理程式代碼:

一種原型污染攻擊,黑客向後端伺服器發送惡意數據,然後通過一個不安全的合併函數將該數據與對象進行合併操作

 1 async function updateUser(userId, requestBody) {
 2   const userData = await db.loadUserData(userId);
 3   merge(userData, requestBody);
 4 
 5   log("Saving userData " + userData.toString());
 6   await db.saveUserData(userId, userData);
 7   return userData;
 8 }
 9 
10 async function getRole(userId) {
11   const userPermissions = await db.loadUserPermissions(userId);
12 
13   let role = "user";
14   if (userPermissions.role) {
15     role = userPermissions.role;
16   }
17 
18   return { role };
19 }
20 
21 /**
22  * Sets or updates all attributes of the source object on the target object.
23  *
24  * For example if `target` is {a: 1, b: 2} and `source` is {a: 3, c: 4},
25  * after calling this function `target` becomes {a: 3, b: 2, c: 4}.
26  */
27 function merge(target, source) {
28   for (const attr in source) {
29     if (
30       typeof target[attr] === "object" &&
31       typeof source[attr] === "object"
32     ) {
33       merge(target[attr], source[attr])
34     } else {
35       target[attr] = source[attr]
36     }
37   }
38 }
  1. 第1行,updateUser方法用來處理HTTP POST請求。參數requestBody的值是我們要發送給伺服器的數據。
  2. 第2行,從資料庫獲取指定user的數據。
  3. 第3行,merge函數將requestBody對象的所有屬性合併到userData對象中。這裡就是原型被污染的地方,我們將深入探討一下merge函數。
  4. merge函數的定義在第27行。target是userData,source是我們通過HTTP Post請求發送給伺服器的數據:
    target: { "about": "Database sanitization expert" ...}
    source: { "about": { "__proto__": { "role": "admin" } } }
  5. 第28行,遍歷source的所有屬性,第一個屬性是about
    attr: "about"
  6. 第33行,about屬性在target和source中都存在,於是我們通過遞歸調用merge函數。
  7. 代碼回到第27行,此時target和source的值為:
    target: "Database sanitization expert"
    source: { "__proto__": { "role": "admin" } }
  8. 第28行,遍歷source的所有屬性,此時第一個屬性是__proto__
    attr: "__proto__"
  9. 第33行,屬性__proto__在target和source中都存在,所以我們再次通過遞歸調用merge函數。這一步是問題的關鍵所在,因為target.__proto__是被大多數對象預設共用的原型!
  10. 代碼再一次回到第27行,此時target和source的值為:
    target: 預設原型
    source: { "role": "admin" }
  11. 第28行,遍歷source的所有屬性,第一個屬性是role
    attr: "role"
  12. 由於屬性role沒有在target中定義,所以代碼會走到第35行,通過語句target[attr] = target.role{"role": "admin"}設置給target。啊哈!我們成功地通過一段惡意代碼污染了全局原型。現在,對於所有共用預設原型的對象來說,屬性role的值即為"admin"。
  13. 代碼回到第3行,現在預設原型已經被role="admin"污染了。
  14. 然後,第10行,我們通過HTTP GET請求查詢我們分配給用戶的的角色。
  15. 第11行,對象userPermissions用來接收從資料庫返回的值,在本例中它是一個空對象({},因為指定的userId根本不存在),並且共用了預設原型。
  16. 第15行,由於userPermissions是一個空對象,所以它沒有role屬性。正常情況下,role預設為"user"。但是,由於我們通過role屬性污染了原型,userPermissions.role等同於userPermissions.__prop__.role,即"admin"。
  17. 第18行,{role: "admin" }被作為HTTP GET請求的返回值。

減少原型污染 

方案1:在通過遞歸設置對象的屬性時使用安全的開源庫 

  在startup.io公司的案例中,merge函數的作用是將一個對象的所有屬性更新到另一個對象中。正如我們在上面的代碼分析過程中所看到的那樣,merge函數以遞歸的方式將第二個參數的所有屬性合併到第一個參數中——甚至包含那些不可信的內容,如__proto__。 

  並不是只有合併兩個對象的功能才會使源代碼可能受到原型污染攻擊——任何其它以遞歸調用的方式對嵌套的屬性進行設置的函數都有可能受到攻擊。在JavaScript生態中,其它常見的例子包括:深拷貝(如lodash中的cloneDeep方法),設置嵌套的屬性(如lodash中的set方法),或者以遞歸的方式“壓縮”屬性的值來創建一個新對象(如lodash中的zipObjectDeep方法)。

  在以遞歸的方式設置嵌套的屬性時,需要始終確保將那些不可信的內容排除在外。不要自己實現!即使最優秀的程式員也會很容易犯錯。我們應該使用如lodash這樣的開源庫,它非常受歡迎,並且擁有出色的社區支持和及時的安全更新。 

一個避免原型污染的例子,黑客嘗試發送一條惡意數據來攻擊伺服器,在使用安全的合併函數後,阻止了對原型的影響

1 import safeMerge from 'lodash.merge';
2 
3 async function updateUser(userId, requestBody) {
4   const userData = await db.loadUserData(userId);
5   safeMerge(userData, requestBody);
6 
7   await db.saveUserData(userId, userData);
8   return userData;
9 }

  想知道哪些庫是可信的,可以使用Snyk Advisor,它提供了給定package的受歡迎程度、社區支持和安全性等信息。除此之外,也可以使用漏洞掃面工具來檢查你要使用的開源庫,如Snyk,它會告訴你使用的庫中所有發現的安全漏洞,並幫助你如何輕鬆地解決這些問題。

  事實上我們很難做到完全避免原型污染攻擊。在實現遞歸合併函數的過程中,lodash的開發人員確保值為__prop__的鍵不會從一個對象複製到另一個對象。不幸的是,後來發現原型污染也可以通過其它的屬性產生,例如constructor.prototype(查看這篇文章以瞭解lodash的開發人員如何修複這個問題)。因此我們得到的教訓是,要正確處理用戶的輸入是非常困難的,我們應該儘可能地使用那些經過實踐檢驗過的庫來幫助我們完成工作。

方案2:創建沒有prototype的對象:Object.create(null)

  另一種避免原型污染的方法是在創建新對象時考慮使用object.create()方法,而不是通過對象字面量{}或者構造函數new Object()來創建。這樣,我們可以通過傳遞給Object.create()的第一個參數直接設置所創建對象的prototype。如果參數的值為null,那麼所創建的對象就沒有prototype,因此就不會產生原型污染。

 1 async function updateUser(userId, requestBody) {
 2   const userData = await db.loadUserData(userId);
 3 
 4   const saveToDatabase = Object.create(null);
 5   merge(saveToDatabase, userData);
 6   merge(saveToDatabase, requestBody);
 7 
 8   await db.saveUserData(userId, saveToDatabase);
 9   return saveToDatabase;
10 }

方案3:阻止對prototype的任何修改:使用Object.freeze()

  JavaScript提供了Object.freeze()方法,我們可以使用它來阻止對對象屬性的任何修改。由於prototype也是一個object,所以我們可以freeze它。我們可以使用Object.freeze(Object.prototype)來凍結預設原型,這樣可以防止對象的預設原型受到污染。

  或者,你也可以安裝nopp包,它會自動凍結所有常見的對象原型。

 1 // call once in ‘main.js’ or similar
 2 Object.freeze(Object.prototype);
 3 
 4 async function updateUser(userId, requestBody) {
 5   const userData = await db.loadUserData(userId);
 6   merge(userData, requestBody);
 7 
 8   await db.saveUserData(userId, userData);
 9   return userData;
10 }

如何減少原型污染?

  當需要通過遞歸在對象上設置嵌套屬性時,使用流行的開源庫可以減少代碼庫中的原型污染漏洞。使用Snyk Advisor檢查你要使用的庫,並確保通過Snyk掃描後沒有安全漏洞。為了進一步強化代碼,可以使用Object.create(null)方法來避免使用prototype,或者使用Object.freeze(Object.prototype)來阻止對共用原型的任何修改。

 

  更多有關原型污染的內容:

  最後,如果你想對原型污染有更深入的瞭解,請閱讀由Olivier Arteau撰寫的有關原型污染的詳細報告。他在很多常見的JavaScript庫中發現並披露了許多原型污染漏洞。

 

原文地址:Prototype Pollution


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言: 繼上篇:Taurus .Net Core 微服務開源框架:Admin 插件【4-3】 - 配置管理 - Mvc【Plugin-MicroService 微服務】 本篇繼續介紹下一個內容: 系統配置節點:Mvc - Plugin - CORS 跨域界面: 界面如下: 跨域功能相關配置說明如下: ...
  • 目錄 Welcome to YARP - 1.認識YARP並搭建反向代理服務 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 - 配置提供者(Configuration Providers) 2.3 - 配置過濾器(Confi ...
  • 前言 由於業務需要,需要多台雲伺服器,但是公有雲的帶寬價格不菲,所以不可能給所有的雲伺服器都配上公網IP,一方面是成本的問題,另一方面也是公網安全的問題。 所以通過其它的方式使用無公網的雲伺服器來來實現對外資源的訪問。 一、操作步驟 至少需要有一臺具有公網IP的雲伺服器! 1、開啟ECS的路由轉發功 ...
  • 近幾年IT界軟硬體“國產化”搞得很密集,給很多公司帶來了商機。但是有些公司拿國外的代碼改改換個皮膚,就是“自主知識產權”的國產軟體,光明正大賣錢,這個有點...,還經常有醜聞露出,譬如某星瀏覽器、C某-IDE... 話不多說,最近有個項目需要國產化改造,業主方推薦了國產資料庫 瀚高資料庫,我們原來用 ...
  • 本文分享自華為雲社區《淺析KV存儲之長尾時延問題,華為雲 GeminiDB Redis 探尋行業更優解決方案!》,作者:華為雲資料庫GaussDB NoSQL團隊。 目前,KV存儲的廣泛使用極大程度上源於快速訪問的業務需求,而這種業務通常對時延敏感度高,在較好的平均性能下,還需要解決特定場景下的性能 ...
  • 在之前的實時湖倉系列文章中,我們已經介紹了實時湖倉對於當前企業數字化轉型的重要性,實時湖倉的功能架構設計,以及實時計算和數據湖結合的應用場景。 在本篇文章中,將介紹袋鼠雲數棧在構建實時湖倉系統上的探索與落地實踐,及未來規劃。 數棧為什麼選擇實時湖倉 數棧作為一個數據開發平臺,在未引入實時湖倉之前提供 ...
  • 資料庫底層實現博大精深,本文所述,根據線上場景進行了一些研究和探討,希望能為相關場景提供一些啟示。文章中難免會有不足之處,希望讀者能給予寶貴的意見和建議 ...
  • 前言 近期接到一個關於谷歌EDLA認證的需求,我負責的是谷歌原生桌面佈局的修改,通過研究源碼,將涉及到了一些修改思路發出來,大家可以參考一下有沒有對你有用的信息。主要修改內容有: 1、搜索欄、底部導航欄未居中 2、中部應用未按要求排布,詳情請參考摹客 3、在原生Google桌面未添加中性S-writ ...
一周排行
    -Advertisement-
    Play Games
  • 最近做項目過程中,使用到了海康相機,官方只提供了C/C++的SDK,沒有搜尋到一個合適的封裝了的C#庫,故自己動手,簡單的封裝了一下,方便大家也方便自己使用和二次開發 ...
  • 前言 MediatR 是 .NET 下的一個實現消息傳遞的庫,輕量級、簡潔高效,用於實現進程內的消息傳遞機制。它基於中介者設計模式,支持請求/響應、命令、查詢、通知和事件等多種消息傳遞模式。通過泛型支持,MediatR 可以智能地調度不同類型的消息,非常適合用於領域事件處理。 在本文中,將通過一個簡 ...
  • 前言 今天給大家推薦一個超實用的開源項目《.NET 7 + Vue 許可權管理系統 小白快速上手》,DncZeus的願景就是做一個.NET 領域小白也能上手的簡易、通用的後臺許可權管理模板系統基礎框架。 不管你是技術小白還是技術大佬或者是不懂前端Vue 的新手,這個項目可以快速上手讓我們從0到1,搭建自 ...
  • 第1章:WPF概述 本章目標 瞭解Windows圖形演化 瞭解WPF高級API 瞭解解析度無關性概念 瞭解WPF體繫結構 瞭解WPF 4.5 WPF概述 ​ 歡迎使用 Windows Presentation Foundation (WPF) 桌面指南,這是一個與解析度無關的 UI 框架,使用基於矢 ...
  • 在日常開發中,並不是所有的功能都是用戶可見的,還在一些背後默默支持的程式,這些程式通常以服務的形式出現,統稱為輔助角色服務。今天以一個簡單的小例子,簡述基於.NET開發輔助角色服務的相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 第3章:佈局 本章目標 理解佈局的原則 理解佈局的過程 理解佈局的容器 掌握各類佈局容器的運用 理解 WPF 中的佈局 WPF 佈局原則 ​ WPF 視窗只能包含單個元素。為在WPF 視窗中放置多個元素並創建更貼近實用的用戶男面,需要在視窗上放置一個容器,然後在這個容器中添加其他元素。造成這一限制的 ...
  • 前言 在平時項目開發中,定時任務調度是一項重要的功能,廣泛應用於後臺作業、計劃任務和自動化腳本等模塊。 FreeScheduler 是一款輕量級且功能強大的定時任務調度庫,它支持臨時的延時任務和重覆迴圈任務(可持久化),能夠按秒、每天/每周/每月固定時間或自定義間隔執行(CRON 表達式)。 此外 ...
  • 目錄Blazor 組件基礎路由導航參數組件參數路由參數生命周期事件狀態更改組件事件 Blazor 組件 基礎 新建一個項目命名為 MyComponents ,項目模板的交互類型選 Auto ,其它保持預設選項: 客戶端組件 (Auto/WebAssembly): 最終解決方案裡面會有兩個項目:伺服器 ...
  • 先看一下效果吧: isChecked = false 的時候的效果 isChecked = true 的時候的效果 然後我們來實現一下這個效果吧 第一步:創建一個空的wpf項目; 第二步:在項目裡面添加一個checkbox <Grid> <CheckBox HorizontalAlignment=" ...
  • 在編寫上位機軟體時,需要經常處理命令拼接與其他設備進行通信,通常對不同的命令封裝成不同的方法,擴展稍許麻煩。 本次擬以特性方式實現,以兼顧維護性與擴展性。 思想: 一種命令對應一個類,其類中的各個屬性對應各個命令段,通過特性的方式,實現其在這包數據命令中的位置、大端或小端及其轉換為對應的目標類型; ...