f-string拼接sql会直接执行恶意代码,因其在定义时立即求值生成完整sql字符串,使攻击者输入的单引号、分号或python表达式(如__import__('os').system())被直接解析执行,完全绕过参数隔离机制。

为什么 f-string 拼接 SQL 会直接执行恶意代码
f-string 在定义时就立即求值,数据库看到的不是“带占位符的语句”,而是一条完全拼好的、语法已固定的字符串。只要 user_input 含有单引号、分号或 SQL 关键字,整个查询逻辑就被重写。比如:user_input = "admin' --",经 f"SELECT * FROM users WHERE name = '{user_input}'" 后,实际执行的是 SELECT * FROM users WHERE name = 'admin' --',注释掉后续密码校验。
更危险的是,f-string 支持任意 Python 表达式:若 user_input 是 "__import__('os').system('rm -rf /')",f-string 定义那一刻系统命令就已触发——这和 SQL 注入无关,是纯粹的代码执行漏洞。
常见错误现象:
- 登录接口传入
admin' OR '1'='1直接绕过验证 - 分页参数
page=1; DROP TABLE users;导致删库 - 日志中出现
psycopg2.ProgrammingError: syntax error at or near "OR"却查不到源头
pymysql / psycopg2 必须用 %s 占位符,且只用于值
%s 不是 Python 的字符串格式化符号,而是数据库驱动层的参数绑定指令。它让数据库先编译语句骨架(如 DELETE FROM logs WHERE created_at ),再把参数当纯数据填入。结构与数据彻底分离,输入再恶意也变不成语法。
实操建议:
一款AI工具,主要用于Monitor and clean up invalid Codex authentication files in CPA. Check quota status, disable files returning 401 errors, and perform dual verification before deletion.,适合需要提升相关任务效率的用户。
- 所有用户可控变量(表单、API 参数、配置文件读取值)必须走
cursor.execute("WHERE id = %s", (user_id,)),(user_id,)必须是元组,单元素别漏逗号 -
%s只能出现在值的位置:WHERE 条件、INSERT VALUES、UPDATE SET 右侧;绝不能用于表名、列名、ORDER BY 字段 - 不要混用:禁止
f"SELECT * FROM {table} WHERE id = %s"—— 表名未校验,%s再安全也白搭 - MySQL 用
pymysql、PostgreSQL 用psycopg2,都只认%s;SQLite 用?
动态表名/字段名必须走白名单硬校验
数据库驱动不支持对标识符(表名、列名、排序方向)做参数化,因为它们影响语句解析结构。强行拼接等于开门揖盗。
正确做法是显式枚举 + 强制检查:
- 定义允许集合:
ALLOWED_TABLES = {"logs", "events", "metrics"} - 从配置或参数拿到
table_name后,立刻判断:if table_name not in ALLOWED_TABLES: raise ValueError("Invalid table name") - 确认合法后,再用
f"SELECT * FROM {table_name} WHERE status = %s"—— 此时{table_name}已是可信值,无注入风险 - 避免正则过滤、
strip()或转义:攻击者可用logs%00、logs`、logs/**/绕过
调试日志可用 f-string,但 SQL 日志必须脱敏
f-string 本身不是问题,问题是用在哪儿。日志输出不需要防注入,但需要可读性和性能:f"[SQL] DELETE FROM {table} WHERE ts 这类语句仅用于记录,不交给数据库执行。
但真实发给数据库的 SQL,日志里必须隐藏敏感值:
- 不要打
cursor.execute("SELECT * FROM users WHERE email = %s", ("attacker@example.com",))的完整语句 - 应记录为
[DEBUG] Executing: SELECT * FROM users WHERE email = %s (params: ['***']) - 尤其注意定时任务脚本:误把
cutoff_date打全量到日志,可能暴露业务时间窗口
最易被忽略的一点:ORM 的 .raw() 或 text() 查询,只要里面含变量拼接,就和手写字符串拼接无异——安全边界不在 ORM,而在是否用了参数绑定。










