exec() 与 SQL 组合构成远程代码执行(RCE)漏洞,危害远超普通 SQL 注入;修复关键是彻底避免 exec 处理用户输入、SQL 片段或数据库逻辑,改用白名单校验、ORM 表达式树或参数化查询等安全方案。

exec() 本身不直接导致 SQL 注入,但它会放大风险——只要它执行的字符串里拼了用户输入,就等于把 SQL 注入的入口从数据库层提前到了 Python 解释器层。真正危险的是:用 exec() 动态构造并执行含用户输入的 SQL 字符串。
所以修复重点不是“怎么安全地用 exec()”,而是彻底避免让它碰任何用户输入、SQL 片段或数据库操作逻辑。
为什么 exec + SQL 是双重危险组合
exec() 执行的是任意 Python 代码,一旦字符串里混入用户可控内容(比如 f"query = 'SELECT * FROM {table} WHERE id = {user_id}'"),攻击者就能:
- 注入
__import__调用,读取文件、执行系统命令 - 绕过所有 ORM 或参数化查询的保护机制
- 在数据库连接对象上直接调用
cursor.execute(),且跳过参数校验
这不是“SQL 注入漏洞”,而是“远程代码执行(RCE)漏洞”——危害等级远高于普通 SQL 注入。
立即学习“Python免费学习笔记(深入)”;
替换 exec 的安全等价方案
绝大多数声称“必须用 exec() 才能动态执行 SQL”的场景,其实都有更安全的替代路径:
- 动态表名/字段名 → 白名单校验 + 格式化:
if table_name not in ["users", "orders", "logs"]: raise ValueError("Invalid table"),再拼进 SQL - 动态 WHERE 条件 → 使用 SQLAlchemy 的
and_()/or_()构造表达式树,或 Django 的Q对象 - 批量执行相似查询 → 循环调用参数化查询,而不是拼一个大字符串再
exec() - 需要运行用户定义逻辑 → 改用
ast.literal_eval()(仅限字面量)或专用 DSL 解析器,而非exec()
如果真无法移除 exec,至少做三件事
极少数遗留系统或沙箱环境可能仍需保留 exec(),此时必须切断其与用户输入和数据库的连接:
- 剥离所有 I/O 和数据库相关模块:传入空的
globals和受限locals,显式删除__builtins__、open、os、subprocess、sqlite3等 - 禁止字符串拼接 SQL:检查
exec内容是否含"SELECT"、"INSERT"、.execute(等关键词,用正则预过滤 - 超时 + 资源限制:用
resource.setrlimit()限制 CPU 时间和内存,防止拒绝服务
注意:exec() 在生产环境处理用户输入,本质上是放弃安全控制权。白名单、沙箱、资源限制只是降低爆炸半径,不能消除根本风险。
最容易被忽略的陷阱:eval 和 exec 的混淆使用
很多开发者以为 eval() 比 exec() “安全一点”,但只要输入来自用户,两者都危险:
-
eval("1+2")安全;eval("__import__('os').system('id')")不安全 -
exec("x=1")看似无害,但若字符串是"import os; os.system('rm -rf /')"就直接失控 - Django 模板中的
{{ user_input|safe }}+ 自定义 filter 里用了exec,也会触发相同问题
真正安全的边界只有一条:用户输入绝不能成为代码字符串的一部分。哪怕只拼了一个表名、一个字段名、一个 LIMIT 数值,exec() 就已不可信。


















