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
run.js
漏洞分析▸
模板渲染过程中,include.js会拼接代码
https://github.com/node-swig/swig-templates/blob/313bed1faa42e310d9dca4cd05d384439d26ec63/lib/tags/include.js#L39-L52
拼接完的代码会给到out
变量
https://github.com/node-swig/swig-templates/blob/313bed1faa42e310d9dca4cd05d384439d26ec63/lib/parser.js#L891-L899
之后out
值为
关于为什么会生成这样的out
,主要是parser.js的checkMatch()
,
中间的逻辑很复杂,反正就是找属性、代码拼接,不好描述,这里不展开写了,整个call stack为:
最后生成的result
值为如下代码的字符串形式:
这就很明朗了,很明显_ctx
不存在Object
属性,会走到三目运算符中:
后面的逻辑,也就是:
很明显Object
是存在的,于是就最后就得到了Object.constructor
,也就是Function()
接着刚才的说,out
的值会被用来做一个匿名函数
在Swig.Swig.compile.compiled
处调用pre.tpl()
,这个tpl()
就是刚刚创建的匿名函数
跟进这个匿名函数,执行就会弹计算器:
虽然很长,但是前面不用看,我们直接跟到中间的call
方法,call
前面的一大套还是相同的逻辑,只是多套了两层三目运算,最后返回的是依然是Object.constructor
,call
方法来源于Object.constructor || efn
。其实根本不重要,因为不论是哪个函数,call
方法最终都是继承自Function.prototype
原型对象,然后call
的第一个参数就是上面分析的Object.constructor
,也就是Function()
,第二个参数就是函数体
于是乎顺理成章的通过Object.constructor
创建了如下匿名函数:
接着就走到了下一个()
对上述匿名函数做调用,成功逃逸沙箱,代码执行就发生了
已提issue: https://github.com/node-swig/swig-templates/issues/88
根据文档:
swig可以扩展模板,或包含模板,但对路径和后缀名没有做校验,因此可以实现任意文件读取
poc:
比较简单,就不详细分析了,就是直接取到路径然后读文件然后拼接到结果的_output
里