模板引擎是什么
// 安装EJS模块:npm install ejs
// 引入EJS模块
const ejs = require('ejs');
// 定义模板
const template = `
<h1>Hello, <%= name %>!</h1>
`;
// 渲染模板
const data = { name: 'John' };
const html = ejs.render(template, data);
console.log(html);
handlebars 模版使用
// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
pug 模版使用
// 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
总结:可以看到模版引擎其实都有各自的一些特定语 法规则,比如 pug 中可以通过 #{name} 来引用外部 环境的变量, ejs 则是 <%= name %>。通过这种方 式简化html代码的编写,同时实现模版重用。
模板引擎的工作原理
attrs[name] = attrs[value]
if(ast.block){
}
for(var i in node){
}
赋值操作未判断对应的属性是否为对象自身的属性,导致访问到原型链的 Object.prototype 的属性
判断某个属性是否存在,同样未判断是否为对象自身属性是否存在,若存在原型链污染,则可以进入if判断
JS的 for...in 循环会遍历对象的所有可枚举属性,包括原型链上的属性。例如:
let obj = { a: 1, b: 2 };
obj.__proto__.c = 3;
for (let i in obj) {
console.log(i); // a, b, c
}
AST树的生成本质上是影响生成的字符串,因此也可以导致XSS漏洞 代码执行的那一步才会导致RCE,这时候需要第一步通过原型链污染注入代码,进而影响生成的代码
pug template AST injection 示例
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); // <h1>It works<script>alert(origin)</script></h1>
(function anonymous(pug
) {
function template(locals) {var pug_html = "", pug_mixins = {}, pug_interp;var pug_debug_filename, pug_debug_line;try {;
var locals_for_with = (locals || {});
(function (msg) {
;pug_debug_line = 1;
pug_html = pug_html + "u003Ch1u003E";
;pug_debug_line = 1;
pug_html = pug_html + (pug.escape(null == (pug_interp = msg) ? "" : pug_interp)) + "u003Cscriptu003Ealert(origin)u003Cu002Fscriptu003Eu003Cu002Fh1u003E";
}.call(this, "msg" in locals_for_with ?
locals_for_with.msg :
typeof msg !== 'undefined' ? msg : undefined));
;} catch (err) {pug.rethrow(err, pug_debug_filename, pug_debug_line);};return pug_html;}
return template;
})
AST Injection原理分析(以pug为例)
语法树结构
{
"type":"Block",
"nodes":[
{
"type":"Tag",
"name":"h1",
"selfClosing":false,
"block":{
"type":"Block",
"nodes":[
{
"type":"Code",
"val":"msg",
"buffer":true,
"mustEscape":true,
"isInline":true,
"line":1,
"column":3
}
],
"line":1
},
"attrs":[
],
"attributeBlocks":[
],
"isInline":false,
"line":1,
"column":1
}
],
"line":0
}
语法树生成后,会调用 walkAst 执行语法树的解析过程,依次对每个节点的类型进行判断,即如下代码:
function walkAST(ast, before, after, options){
parents.unshift(ast);
switch (ast.type) {
case 'NamedBlock':
case 'Block':
ast.nodes = walkAndMergeNodes(ast.nodes);
break;
case 'Case':
case 'Filter':
case 'Mixin':
case 'Tag':
case 'InterpolatedTag':
case 'When':
case 'Code':
case 'While':
if (ast.block) { // 注意这里
ast.block = walkAST(ast.block, before, after, options);
}
break;
case 'Text':
break;
}
parents.shift();
}
语法树执行顺序
Block Tag Block Code ?
case 'Code':
case 'While':
if (ast.block) { // 注意这里
ast.block = walkAST(ast.block, before, after, options);
}
判断 ast.block 属性是否存在,此处的 ast 即当前ast语法树的节点 如果存在,继续递归解析 block
结合原型链污染
// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
0
// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
1
RCE
// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
2
// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
3
Attack example
(注,镜像已经上传,本地有docker环境可直接运行
// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
4// 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
5本地跑起来后运行在1337端口: 原型链污染
注意到这一行代码: // 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
6unflatten 这个库存在原型链污染 // 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
7AST Injection
注意到这一行代码: // 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
8结合原型链污染,可以实现RCE // 安装Handlebars模块:npm install handlebars
// 引入Handlebars模块
const handlebars = require('handlebars');
// 定义模板
const template = `
<h1>Hello, {{name}}!</h1>
`;
// 编译模板
const compiledTemplate = handlebars.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
9接下来实际分析一个模版引擎的漏洞挖掘案例: blade漏洞挖掘实例
除去上文介绍的三种模版引擎,blade 也是常用的一种,安装方法 // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
0编写一个示例: // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
1正常得到的语法树为: // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
2既然是挖掘AST Injection,此时我们假设随便给 Object.prototype 污染个属性 // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
3此时得到的语法树变成了如下,且代码运行报错: // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
4注意到出现在第9行处理代码的时候: // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
5出现了一个 undefined 的属性,这里是怎么来的呢? 以及,堆栈报错,可以作为辅助漏洞挖掘的信息 // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
6代码分析
Blade模版引擎的处理流程和其他模版引擎类似,如下过程:代码 ->词法分析 -> ast 树 -> code-gen
单纯从堆栈信息,只能看到处理语法树时候遇到的报错,具体在这里:// 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
7因为 attrs[i].text 显然是去访问一个 undefined 的属性,所以就会报错,这里无法获得更多信息了,只能动态调试代码去分析了。 调用栈分析
blade.compile 函数跟进,会调用到内部模块的 parse
跟进 parser.parse 函数文件位于:node_modules/blade/lib/parser/index.js 通过闭包导出了个函数: 跟进我们想要的函数,从函数命名上可以看出就是在利用堆栈去处理字符串,生成ast树,基本的编译原理了属于是 我们只关注 attributes 的处理,找到对应的函数, parse_attribute 这行代码: // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
8就是返回一个属性的对象,这个是符合正常逻辑的 但是此时还没有看到我们想搞明白的 undefined 属性是怎么出现的,不急 我们先从函数这个返回, 因为我们知道生成的这个属性肯定是要加到对应的ast树上的 果然,从 parse_attribute 返回,经过了一段处理代码之后,到达了这一处: // 安装Pug模块:npm install pug
// 引入Pug模块
const pug = require('pug');
// 定义模板
const template = `
h1 Hello, #{name}!
`;
// 编译模板
const compiledTemplate = pug.compile(template);
// 渲染模板
const data = { name: 'John' };
const html = compiledTemplate(data);
console.log(html);
9其中: attrs[name] = attrs[value]
if(ast.block){
}
for(var i in node){
}
0赋值完后,还有一个循环,刚好能够取出原型链中的属性,实现 ast injeciton 如下图:注意到 i 就是原型链中的属性 这样我们就明白了为啥导致AST树中出现 undefined ,我们只需要加入name 和 value 属性即可 attrs[name] = attrs[value]
if(ast.block){
}
for(var i in node){
}
1RCE
既然可以注入任意属性了,那我们就可以利用这一点,向 attributes 中写入代码 注意到在处理AST生成代码的过程中,会处理 code 的属性 Payload如下,这样就能实现AST Injection+ RCE了! attrs[name] = attrs[value]
if(ast.block){
}
for(var i in node){
}
2往期推荐
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...