SecMap - SSTI(mako)
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 语法
作为一门模板语言,肯定有自己的一套语法规则。
基础语法
mako 的基础语法规则一共 3 种:
- 变量取值:
${ }
,比如输入1+1
,2*2
,或者是字符串、调用对象的方法,都会渲染出执行的结果 - 控制结构:
%for ... : %endfor
、%if ... : ... %elif: ... % else: ... %endif
- Python 代码块:
<% ... %>
- 导入模块:在代码块的基础上加一个感叹号
<%! ... %>
- 定义函数:
<%def name="..." > ... </%def>
,调用:${...()}
- 注释:
##
(单行)、<%doc>
(多行) - 其他:
- 继承模板:
<%inherit ... />
- 包含模板:
<%include ... />
,引用:<%page ... />
- 还有很多,不列举了,见:资料 2,在实际的利用过程中用到的比较少,业务上可能比较多。
- 继承模板:
- 可以看到上面非常依赖
%
,如果非要用到%
,需要写成%%
对于常用的语法,看一个例子就懂了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from mako.template import Template
tp = Template('''## 这是一个注释
<%def name="my_range(n)" > <% return list(range(n))%> </%def>
<% c = 5 %>
% for i in my_range(c)+a:
%if i % 2:
${ i }
%endif
% endfor
''')
print(tp.render(a = [5, 6, 7, 8, 9]))
结果就是输出 1、3、5、7、9
另外,mako 还有一个值得一提的特殊语法:过滤器
过滤器
官方文档见资料 3
单个过滤器的使用和 jinja2 一样很像,都是用 |
来引用。如果要使用多个过滤器,mako 需要用 ,
来指定:${" <tag>some value</tag> " | h,trim}
要定义自己的过滤器也比较简单,不需要和 jinj2 一样操作 environment
,只需要定义一个函数即可使用:
1
2
3
4
5
6
7
8
9<%!
import myfilters
def myescape(text):
return "<TAG>" + text + "</TAG>"
%>
Here's some tagged text: ${"text" | myescape}
Here's some tagged text: ${"text" | myfilters.myescape}
非常优雅。
SSTI in mako
攻击思路
可以看到,mako 本身可以完美支持 Python 语句,所以利用 <% %>
、<%! %>
、${}
可以非常轻松地进行攻击,例如:
1
2
3
4
5
6
7
8
9
10
11
12<%!
import os
os.system("whoami")
%>
# 或者
<%__import__("os").system("whoami")%>
# 或者
${__import__("os").system("whoami")}
其中 ${ }
与 jinja2 的 {{ }}
比较类似,但由于 mako 直接支持 Python 语法,所以 ${ }
可以直接使用内置函数,例如 dir
。更不用说还有 <% %>
、<%! %>
了。
当然控制结构 %for ... : %endfor
、%if ... : ... %elif: ... % else: ... %endif
也是 ok 的。
所以 mako 的 SSTI 手法基本上兼容 jinja2 的 SSTI 手法,可以说思路灵活得多。
bypass 思路
“常规” 思路
目前我还没遇到过滤很严格的情况。我感觉大部分过滤技巧都可以参考 jinja2 的技巧(见资料 5)或者是 Python 沙箱逃逸(见资料 6)的技巧。为了避免有水字数的嫌疑,我就不赘述了
mako 有的特殊姿势
mako 引入了新的默认变量:
1
2
3
4
5
6
7
8
9
10
11
12
13In [57]: Template("${ locals() }").render()
Out[57]: """
{
'context': <mako.runtime.Context object at 0x7fd5e8af99d0>,
'pageargs': {},
'__M_caller': None,
'__M_locals': {
'pageargs': {}
},
'locals': <built-in function locals>,
'__M_writer': <built-in method append of collections.deque object at 0x7fd5c8013ac0>
}
"""
其中比较关键的有:
locals
:这个就是locals
,context
:见资料 4__M_writer
:与 print 类似,可以直接打印字符串pageargs
:render
里的参数会在这里面
如果在遇到无回显的场景,就可以用 __M_writer
、context.write
尝打印。
例如:
1
2
3
4
5
6
7
8
9from mako.template import Template
tp = Template('''
%for i in x:
"a"
%endfor
''')
print(tp.render())
其中 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
2
3%> /> _ + $ [ ' "
chr ord hex eval exce
...
所以 ${ }
、<% %>
、<%! %>
都不行,那么用控制结构来调用命令语句即可。这道题是没回显的,需要回显的话,可以用上文说到的办法来玩。这道题由于过滤的是 eval
,有需要的话我们就可以用修饰字符绕过字符过滤,ᵉᵥᵃˡ
:
当然,这个技巧也在 Python 沙箱逃逸中介绍过了。
dibber
从 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 参数来传递给模板,不要直接做拼接。
资料
- mako 官方文档
https://docs.makotemplates.org/en/latest/ - mako 标签
https://docs.makotemplates.org/en/latest/syntax.html#tags - mako 过滤器
https://docs.makotemplates.org/en/latest/filtering.html - mako context 文档
https://docs.makotemplates.org/en/latest/runtime.html#context - SSTI-jinja2
https://www.tr0y.wang/2022/04/13/SecMap-SSTI-jinja2/ - Python 沙箱逃逸经验总结
https://www.tr0y.wang/2019/05/06/Python沙箱逃逸经验总结/ - dibber
https://github.com/Macr0phag3/dibber - Python 反序列化
https://www.tr0y.wang/2022/02/03/SecMap-unserialize-python/
下期应该是 flask 相关的知识点
这段时间大家都好难啊...
又是疫情,又是股灾的...
还好快放假了,有了一些喘息的时间
提前祝各位五一快乐!!!