web前端安全-JavaScript 原型鏈污染


0x01 前言

最近看到一篇原型鏈污染的文章,自己在這里總結一下

 

0x02 javascript 原型鏈

js在ECS6之前沒有類的概念,之前的類都是用funtion來聲明的。如下

可以看到b在實例化為test對象以后,就可以輸出test類中的屬性a了。這是為什么呢?

原因在於js中的一個重要的概念:繼承。

而繼承的整個過程就稱為該類的原型鏈。

在javascript中,每個對象的都有一個指向他的原型(prototype)的內部鏈接,這個原型對象又有它自己的原型,直到null為止


function i(){ this.a = "test1"; this.b = "test2";} 

可以看到其父類為object,且里面還有許多函數,這就解釋了為什么許多變量可以調用某些方法。

在javascript中一切皆對象,因為所有的變量,函數,數組,對象 都始於object的原型即object.prototype。同時,在js中只有類才有prototype屬性,而對象卻沒有,對象有的是__proto__和類的prototype對應。且二者是等價的

當我們創建一個類時

原型鏈為

b -> a.prototype -> object.prototype->null

創建一個數組時

原型鏈為

c -> array.prototype -> object.prototype->null

創建一個函數時

原型鏈為

d -> function.prototype -> object.prototype->null

創建一個日期

原型鏈為

f -> Data.prototype -> object.prototype->null

所以,測試之后會發現:javascript 一切皆對象,一切皆始於 object.prototype

原型鏈變量的搜索

下面先看一個例子:

我們實例要先於在i中添加屬性,但是在j中也有了c屬性。這是為什么呢

答:

當要使用或輸出一個變量時:首先會在本層中搜索相應的變量,如果不存在的話,就會向上搜索,即在自己的父類中搜索,當父類中也沒有時,就會向祖父類搜索,直到指向null,如果此時還沒有搜索到,就會返回 undefined

所以上面的過程就很好解釋了,原型鏈為

j -> i.prototype -> object.prototype -> null

所以對象j調用c屬性時,本層並沒有,所以向上搜索,在上一層找到了我們添加的test3,所以可以輸出。

 

prototype 原型鏈污染

先看一個小例子:

mess.js

----

(function() { var secret = ["aaa","bbb"]; secret.forEach(); })(); 

attach.html

結果:

在mess.js中我們聲明了一個數組 secret,然后該數組調用了屬於 Array.protottypeforeach方法,如下

但是,在調用js文件之前,js代碼中將Array.prototype.foreach方法進行了重寫,而prototype鏈為secret -> Array.prototype ->object.prototype,secret中無 foreach 方法,所以就會向上檢索,就找到了Array.prototype 而其foreach方法已經被重寫過了,所以會執行輸出。

這就是原型鏈污染。很明顯,原型鏈污染就是:在我們想要利用的代碼之前的賦值語句如果可控的話,我們進行 ——__proto__ 賦值,之后就可以利用代碼了

 

如何應用?

在javascript中可以通過 test.a or test['a'] 對數組的元素進行訪問,如下:

同時對對象來說說也是一樣的

所以我們上述說的prototype也是一樣的

那就很明顯了,原型鏈污染一般會出現在對象、或數組的鍵名或屬性名可控,而且是賦值語句的情況下。

下面我們先看一道題:hackit 2018

const express = require('express') var hbs = require('hbs'); var bodyParser = require('body-parser'); const md5 = require('md5'); var morganBody = require('morgan-body'); const app = express(); var user = []; //empty for now var matrix = []; for (var i = 0; i < 3; i++){ matrix[i] = [null , null, null]; } function draw(mat) { var count = 0; for (var i = 0; i < 3; i++){ for (var j = 0; j < 3; j++){ if (matrix[i][j] !== null){ count += 1; } } } return count === 9; } app.use(express.static('public')); app.use(bodyParser.json()); app.set('view engine', 'html'); morganBody(app); app.engine('html', require('hbs').__express); app.get('/', (req, res) => { for (var i = 0; i < 3; i++){ matrix[i] = [null , null, null]; } res.render('index'); }) app.get('/admin', (req, res) => { /*this is under development I guess ??*/ console.log(user.admintoken); if(user.admintoken && req.query.querytoken && md5(user.admintoken) === req.query.querytoken){ res.send('Hey admin your flag is <b>flag{prototype_pollution_is_very_dangerous}</b>'); } else { res.status(403).send('Forbidden'); } } ) app.post('/api', (req, res) => { var client = req.body; var winner = null; if (client.row > 3 || client.col > 3){ client.row %= 3; client.col %= 3; } matrix[client.row][client.col] = client.data; for(var i = 0; i < 3; i++){ if (matrix[i][0] === matrix[i][1] && matrix[i][1] === matrix[i][2] ){ if (matrix[i][0] === 'X') { winner = 1; } else if(matrix[i][0] === 'O') { winner = 2; } } if (matrix[0][i] === matrix[1][i] && matrix[1][i] === matrix[2][i]){ if (matrix[0][i] === 'X') { winner = 1; } else if(matrix[0][i] === 'O') { winner = 2; } } } if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'X'){ winner = 1; } if (matrix[0][0] === matrix[1][1] && matrix[1][1] === matrix[2][2] && matrix[0][0] === 'O'){ winner = 2; } if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'X'){ winner = 1; } if (matrix[0][2] === matrix[1][1] && matrix[1][1] === matrix[2][0] && matrix[2][0] === 'O'){ winner = 2; } if (draw(matrix) && winner === null){ res.send(JSON.stringify({winner: 0})) } else if (winner !== null) { res.send(JSON.stringify({winner: winner})) } else { res.send(JSON.stringify({winner: -1})) } }) app.listen(3000, () => { console.log('app listening on port 3000!') }) 

獲取flag的條件是 傳入的querytoken要和user數組本身的admintoken的MD5值相等,且二者都要存在。

由代碼可知,全文沒有對user.admintokn 進行賦值,所以理論上這個值時不存在的,但是下面有一句賦值語句:

matrix[client.row][client.col] = client.data

data,row,col,都是我們post傳入的值,都是可控的。所以可以構造原型鏈污染,下面我們先本地測試一下。

下面我們給出payload和結果

注:要使用json傳值,不然會出現錯誤

下面再看另一道題:

'use strict'; const express = require('express'); const bodyParser = require('body-parser') const cookieParser = require('cookie-parser'); const path = require('path'); const isObject = obj => obj && obj.constructor && obj.constructor === Object; function merge(a, b) { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } function clone(a) { return merge({}, a); } // Constants const PORT = 8080; const HOST = '0.0.0.0'; const admin = {}; // App const app = express(); app.use(bodyParser.json()) app.use(cookieParser()); app.use('/', express.static(path.join(__dirname, 'views'))); app.post('/signup', (req, res) => { var body = JSON.parse(JSON.stringify(req.body)); var copybody = clone(body) if (copybody.name) { res.cookie('name', copybody.name).json({ "done": "cookie set" }); } else { res.json({ "error": "cookie not set" }) } }); app.get('/getFlag', (req, res) => { var аdmin = JSON.parse(JSON.stringify(req.cookies)) if (admin.аdmin == 1) { res.send("hackim19{}"); } else { res.send("You are not authorized"); } }); app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`); 

先分析一下題目,獲取flag的條件是admin.аdmin == 1而admin 本身是一個object,其admin 屬性本身並不存在,而且還有一個敏感函數 merg


function merge(a, b) { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } 

merge 函數作用是進行對象的合並,其中涉及到了對象的賦值,且鍵值可控,這樣就可以觸發原形鏈污染了

下面我們本地測試一下

是undefined,為什么呢?下面我們看下

原來我們在創建字典的時候,__proto__,不是作為一個鍵名,而是已經作為__proto__給其父類進行賦值了,所以在test.__proto__中才有admin屬性,但是我們是想讓__proto__作為一個鍵值的.

那應該怎么辦呢?可以使用 JSON.parse

JSON.parse 會把一個json字符串 轉化為 javascript的object

這樣就不會在創建類的時候直接給父類賦值了

而題目中也出現了JSON.parse


var body = JSON.parse(JSON.stringify(req.body)); 

這樣我們就可以愉快地進行原型鏈污染了

payload:

 

 

轉自安全客


免責聲明!

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



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