Python格式化字符串漏洞(Django为例)
字数 1264 2025-08-29 08:31:42
Python格式化字符串漏洞分析与防御指南
1. Python格式化字符串基础
Python提供了多种字符串格式化方法,了解这些方法是理解相关漏洞的基础。
1.1 传统%格式化方法
"My name is %s" % ('phithon',)
"My name is %(name)%" % {'name':'phithon'}
1.2 format()方法
"My name is {}".format('phithon')
"My name is {name}".format(name='phithon')
format()方法提供了更强大的功能:
"{username}".format(username='phithon') # 普通用法
"{username!r}".format(username='phithon') # 等同于repr(username)
"{number:0.2f}".format(number=0.5678) # 保留两位小数
"int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42) # 转换进制
"{user.username}".format(user=request.username) # 获取对象属性
"{arr[2]}".format(arr=[0,1,2,3,4]) # 获取数组键值
2. 格式化字符串漏洞原理
当攻击者能够控制格式化字符串的一部分时,可以利用Python格式化字符串的高级功能访问敏感数据。
2.1 基本漏洞示例
def view(request, *args, **kwargs):
template = 'Hello {user}, This is your email: ' + request.GET.get('email')
return HttpResponse(template.format(user=request.user))
攻击者可以构造如下URL获取用户密码:
http://example.com/?email={user.password}
2.2 任意用户密码泄露
def view(request, *args, **kwargs):
user = get_object_or_404(User, pk=request.GET.get('uid'))
template = 'This is {user}\'s email: ' + request.GET.get('email')
return HttpResponse(template.format(user=user))
攻击者可以获取任意用户的密码:
http://example.com/?uid=1&email={user.password}
3. Django环境下的高级利用
3.1 获取Django配置信息
通过Django admin应用的导入路径获取settings配置:
http://localhost:8000/?email={user.groups.model._meta.app_config.module.admin.settings.SECRET_KEY}
http://localhost:8000/?email={user.user_permissions.model._meta.app_config.module.admin.settings.SECRET_KEY}
3.2 利用链分析
user是Django的User对象user.groups或user.user_permissions指向相关模型- 通过
model._meta.app_config.module找到admin应用 - admin应用导入了settings模块
- 最终可以访问
SECRET_KEY等敏感配置
4. Jinja2沙盒绕过漏洞
4.1 漏洞背景
Jinja2在2.8.1版本之前,沙盒机制无法阻止格式化字符串漏洞的利用。
4.2 漏洞利用示例
from jinja2.sandbox import SandboxedEnvironment
env = SandboxedEnvironment()
class User(object):
def __init__(self, name):
self.name = name
t = env.from_string("{user.__class__.__init__.__globals__}".format(user=User('joe')))
t.render(user=User('joe'))
这会泄露当前环境的所有全局变量__globals__,如果导入了settings或其他敏感配置项,将导致信息泄露。
4.3 修复方案
升级到Jinja2 2.8.1或更高版本,这些版本会抛出SecurityError异常阻止此类利用。
5. f-字符串与代码执行
Python 3.6引入的f-字符串(PEP 498)带来了新的安全风险。
5.1 f-字符串基础
f"{__import__('os').system('id')}"
这类似于PHP中的"${@phpinfo()}",可以直接执行代码。
5.2 实际利用场景
错误使用eval解析JSON:
import json
data = json.loads('{"name": "phithon"}')
# 错误用法
name = eval('f"' + data.get('name') + '"')
攻击者可构造恶意输入执行代码:
{"name": "{__import__('os').system('id')}"}
6. 防御措施
6.1 基本防御原则
- 永远不要将用户输入直接作为格式化字符串
- 严格控制格式化字符串的内容
6.2 安全编码实践
安全做法:
# 安全做法1:固定模板
template = 'Hello {user}, This is your email: {email}'
template.format(user=request.user, email=escape(request.GET.get('email')))
# 安全做法2:使用位置参数而非拼接
template = 'Hello {}, This is your email: {}'.format(
request.user,
escape(request.GET.get('email'))
)
6.3 框架特定建议
Django:
- 使用模板系统而非手动格式化
- 严格限制模板变量访问范围
- 避免将用户对象直接传递给可能被控制的模板
Jinja2:
- 确保使用最新版本(>=2.8.1)
- 启用沙盒环境时检查所有可能的沙盒绕过
6.4 针对f-字符串的防御
- 避免在Python 3.6+中使用eval解析不可信输入
- 使用ast.literal_eval替代eval进行简单表达式求值
- 对用户输入进行严格过滤和转义
7. 总结
Python格式化字符串漏洞虽然不如缓冲区溢出等传统漏洞知名,但在Web应用中可以导致敏感信息泄露甚至代码执行。开发者应当:
- 了解格式化字符串的所有功能
- 避免将用户输入直接用于字符串格式化
- 使用框架提供的安全机制
- 及时更新依赖库
- 对用户输入进行严格的过滤和验证
通过遵循这些原则,可以有效预防格式化字符串相关的安全漏洞。