直接拼接字符串执行sql极其危险,因输入源被污染可致sql注入,引发误删、拖库或远程代码执行;f-string或.format()无法防止注入,应使用参数化查询。

直接拼接字符串执行 SQL 命令在运维脚本里极其危险——哪怕只是清理日志表、归档旧数据这类“只读”操作,一旦输入源(比如配置文件、API参数、命令行参数)被污染,就可能触发 SQL 注入,导致误删、拖库甚至远程代码执行。
为什么不能用 f-string 或 .format() 拼接 SQL?
运维脚本常需动态构造 SQL,比如按日期归档:"DELETE FROM logs WHERE created_at 。但攻击者只要控制 <code>date 变量(例如传入 "2023-01-01' OR '1'='1"),就能绕过条件、清空整张表。更隐蔽的是,某些数据库(如 PostgreSQL)支持语句块或函数调用,注入后可执行系统命令。
常见错误现象:
- 脚本在测试环境正常,上线后某次传入含单引号的主机名就报错
psycopg2.ProgrammingError: syntax error - 定时任务突然删掉不该删的分区,查日志发现
WHERE条件被篡改 - DBA 收到告警:连接数暴增,实际是注入语句触发了大量无效查询
用参数化查询替代字符串拼接
核心原则:所有用户可控、配置可变、环境读取的值,都必须走数据库驱动的参数占位符,绝不能进 SQL 字符串。
实操建议:
- PostgreSQL / MySQL(使用
psycopg2或pymysql):只用%s占位符,且仅用于值(WHERE、INSERT VALUES等),**不能用于表名、列名、排序字段** - SQLite(
sqlite3):统一用?占位符,它会自动转义并绑定类型 - 避免混用:不要在同一个查询里既用参数化又手动拼接,比如
f"SELECT * FROM {table_name} WHERE id = %s"—— 表名仍需白名单校验
示例(安全):
cursor.execute("DELETE FROM logs WHERE created_at <p>示例(危险):</p><pre class="brush:php;toolbar:false;">cursor.execute(f"DELETE FROM logs WHERE created_at <h3>表名/列名等标识符如何安全处理?</h3><p>参数化查询不支持动态表名。若脚本需根据环境切换表(如 <code>logs_prod</code> / <code>logs_staging</code>),必须走白名单校验 + 显式映射。</p><p>实操建议:</p>
- 定义允许的表名集合:
ALLOWED_TABLES = {"logs", "events", "metrics"} - 从配置或参数获取名称后,先检查是否在白名单:
if table_name not in ALLOWED_TABLES: raise ValueError("Invalid table name") - 用
str.format()或 f-string 拼接时,确保变量已通过白名单验证,且不含任何 SQL 元字符(如分号、反引号、括号) - 避免使用
sql.SQL()(psycopg2.sql)除非你完全理解其逃逸规则——它比字符串拼接更易误用
运维场景下的额外风险点
自动化脚本往往以高权限账号运行(如 DBA 角色),且常被 cron 或 CI/CD 调用,出问题影响面极大。
容易踩的坑:
- 脚本读取配置文件(如 YAML/INI)中的 SQL 片段,未做语法校验,导致恶意 SQL 被直接执行
- 用
subprocess.run(["psql", "-c", sql_cmd])调用 CLI 工具——此时参数化失效,必须对整个sql_cmd做严格过滤 - 日志中打印完整 SQL(含参数值),泄露敏感数据;应只记录模板语句,如
"DELETE FROM logs WHERE created_at - 忽略数据库返回的
rowcount,无法判断实际影响行数是否异常(比如预期删 100 行,结果删了 10 万行)
真正麻烦的不是写错一行 SQL,而是让脚本在无人值守时,把信任边界彻底交给了外部输入——而运维环境里,输入来源远比 Web 请求更难管控。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











