SecMap 系列之 SSTI(mako),继续冲鸭!预祝各位五一快乐!
SSTI(Server-Side Template Injection)服务端模板注入。
上一篇我们介绍了 jinja2 的 SSTI,SSTI 具体的定义就不啰嗦了。
mako 的一些设计和使用方式与 jinja2 是非常相似的,截止目前(2022),主流的模板语言就是 jinja2 和 mako。所以这篇就开门见山地来介绍下 mako 的 SSTI。
注:本文基本上都是 py3.x 的环境。
mako 是 Pylons 的默认模板语言,它们之间的关系与 jinja2 和 flask 的关系类似。
首先还是先了解下语法规则。
依旧推荐官方文档,见资料 1
作为一门模板语言,肯定有自己的一套语法规则。
mako 的基础语法规则一共 3 种:
${ },比如输入 1+1,2*2,或者是字符串、调用对象的方法,都会渲染出执行的结果%for ... : %endfor、%if ... : ... %elif: ... % else: ... %endif<% ... %><%! ... %><%def name="..." > ... </%def>,调用:${...()}##(单行)、<%doc>(多行)<%inherit ... /><%include ... />,引用:<%page ... />%,如果非要用到 %,需要写成 %%对于常用的语法,看一个例子就懂了:
1 | |
结果就是输出 1、3、5、7、9
另外,mako 还有一个值得一提的特殊语法:过滤器
官方文档见资料 3
单个过滤器的使用和 jinja2 一样很像,都是用 | 来引用。如果要使用多个过滤器,mako 需要用 , 来指定:${" <tag>some value</tag> " | h,trim}
要定义自己的过滤器也比较简单,不需要和 jinj2 一样操作 environment,只需要定义一个函数即可使用:
1 | |
非常优雅。
可以看到,mako 本身可以完美支持 Python 语句,所以利用 <% %>、<%! %>、${} 可以非常轻松地进行攻击,例如:
1 | |
其中 ${ } 与 jinja2 的 {{ }} 比较类似,但由于 mako 直接支持 Python 语法,所以 ${ } 可以直接使用内置函数,例如 dir。更不用说还有 <% %>、<%! %> 了。
当然控制结构 %for ... : %endfor、%if ... : ... %elif: ... % else: ... %endif 也是 ok 的。
所以 mako 的 SSTI 手法基本上兼容 jinja2 的 SSTI 手法,可以说思路灵活得多。
目前我还没遇到过滤很严格的情况。我感觉大部分过滤技巧都可以参考 jinja2 的技巧(见资料 5)或者是 Python 沙箱逃逸(见资料 6)的技巧。为了避免有水字数的嫌疑,我就不赘述了
mako 引入了新的默认变量:
1 | |
其中比较关键的有:
locals:这个就是 locals,context:见资料 4__M_writer:与 print 类似,可以直接打印字符串pageargs:render 里的参数会在这里面如果在遇到无回显的场景,就可以用 __M_writer、context.write 尝打印。
例如:
1 | |
其中 x 是注入点。
那么我们就可以用 str(__M_writer(str(__import__("os").system("id")))) 来实现回显。当然,盲注或者弹 shell 也是 ok 的。
还有一种类型的利用 context.kwargs 来获取上下文环境中传递的值。例如一个 web 接口有用到 mako,且有一个参数 name,那么可以直接在模板中使用这个变量名,这个时候通常需要 eval 下。
mako 的 CTF 很少,我只见过一道,就是今年 2022-susctf 的 HTML practice。
这道题首先需要 fuzz 出模板类型,这一步只能靠经验了。
得出是 mako 之后,还可以得到黑名单:
1 | |
所以 ${ }、<% %>、<%! %> 都不行,那么用控制结构来调用命令语句即可。这道题是没回显的,需要回显的话,可以用上文说到的办法来玩。这道题由于过滤的是 eval,有需要的话我们就可以用修饰字符绕过字符过滤,ᵉᵥᵃˡ:

当然,这个技巧也在 Python 沙箱逃逸中介绍过了。
从 Python 沙箱逃逸,到 Python 反序列化(见资料 8),到 jinja2 的 SSTI,再到 mako 的 SSTI,可以发现我们常常需要去搜索可以利用的攻击链。假设给定一个对象 [],如何通过 mro 搜到 os 模块呢?
我以前的做法就是用 dir 来找疑似高危的模块,然后进一步分析是否有引入 os 模块。这样效率太低了。所以我写了一个自动搜索的工具,叫 dibber:

目前已经可以支持 原始的 Python 代码、jinja2、mako 这三种形式的搜索,例如 mako 的 context,深度设定为 4,就可以得到以下结果:

如果遇到其他模板,或者是想搜索其他模块、函数,也可自行添加插件。感兴趣的橘友们可以试试,见资料 7
相比于 jinja2 来说,使用 mako 肯定更爽,因为可以随意在模板中插入 Python。但是,攻击者也很爽。
并且对比于 jinja2 来说,jinja2 有沙箱模式,mako 没有,所以在安全性上来说,mako 用起来更加危险。所以还是不要让模板对用户可控了吧。如果非要这样的话,可以用 render 参数来传递给模板,不要直接做拼接。
下期应该是 flask 相关的知识点 这段时间大家都好难啊...
又是疫情,又是股灾的...
还好快放假了,有了一些喘息的时间
提前祝各位五一快乐!!!