必须用参数化查询而非字符串拼接,因后者将校验推迟至运行时易被注入(如' OR 1=1 --),且数据库无法预编译优化;白名单校验仅限表名等动态部分,禁止f-string/.format填充SQL。

为什么拼接字符串执行SQL总在凌晨出问题
因为动态SQL把参数校验和语义解析推迟到了运行时,数据库没机会做预编译优化,攻击者只要混入一个 ' OR 1=1 -- 就能绕过所有业务层判断。
常见错误现象:mysql_query("SELECT * FROM users WHERE name = '" + user_input + "'") 这类写法在日志里看不出异常,但一旦 user_input 是 admin' --,整张表就裸奔了。
使用场景:老系统补丁、导出功能的条件拼接、管理后台的模糊搜索——这些地方最容易“图省事”用字符串拼接。
- 永远优先用参数化查询,哪怕多写两行代码
- 如果必须拼接(比如动态
ORDER BY字段),只允许白名单内的值,别信任何正则过滤 - 禁止把用户输入直接塞进
IN子句,改用WHERE id IN (?, ?, ?)配合占位符数量动态生成
Python里用sqlite3或psycopg2怎么写才不翻车
不是所有带 %s 或 {} 的写法都安全。错把格式化当参数化,是高频翻车点。
错误示范:cursor.execute(f"SELECT * FROM logs WHERE level = '{level}'") —— f-string 先拼完再进数据库,防火墙形同虚设。
正确姿势:
-
sqlite3只认?占位符:cursor.execute("SELECT * FROM users WHERE status = ?", (status,)) -
psycopg2接受%s,但必须是元组或字典传参:cursor.execute("SELECT * FROM orders WHERE user_id = %s", (user_id,)) - 绝对不用
.format()或 f-string 填充 SQL 字符串
Java的PreparedStatement为什么setString之后还被注入
根本原因不是 PreparedStatement 失效,而是开发者在它外面又包了一层字符串拼接。
典型错误:用 StringBuilder 拼出整个 SQL,再把拼好的字符串丢给 PreparedStatement——此时预编译对象拿到的已是“成品”,参数化早已失效。
性能影响:每次 SQL 字符串不同,数据库无法复用执行计划,连接池压力陡增。
- SQL 模板必须写死,只留
?占位符,连字段名、表名都不能动态插 - 需要动态表名?用白名单校验:
if (!Arrays.asList("orders", "users").contains(tableName)) throw new IllegalArgumentException(); - 批量插入别用循环执行多条
PreparedStatement,改用addBatch()+executeBatch()
重构旧代码时怎么识别隐藏的动态SQL
别信注释,也别靠 grep “+” 或 “&” —— 真正危险的是那些看起来“只是拼个变量”的地方。
容易被忽略的点:
-
LIKE查询中直接拼"%" + keyword + "%",得用setString()把完整模糊值传进去 - 存储过程调用里混着字符串拼接:
"CALL calc_balance('" + userId + "', '2024-01-01')" - ORM 的原生 SQL 方法,比如 MyBatis 的
${}(非#{})会直通拼接
复杂点在于:有些 SQL 构建逻辑分散在三个类里,中间还套了模板引擎。重构前先用日志把实际执行的 SQL 打出来,比对着看哪一行真正进了数据库。










