SQL注入防护核心是禁用字符串拼接、全面采用参数化查询,LIKE需手动转义通配符,排序/表名等动态部分须用白名单校验,错误信息和响应行为须收敛。

搜索框是SQL注入最常被突破的入口,根本原因不是“没过滤单引号”,而是把 $_GET['q'] 或 request.GET.get('q') 直接拼进 SQL 字符串里。只要还在用字符串拼接构造 LIKE 查询,所有后续过滤都形同虚设。
必须用参数化查询替代字符串拼接
这是唯一能从根源上堵死注入的手段。参数化不是“加个转义函数”,而是让数据库引擎明确区分“语句结构”和“数据值”。
- PHP(PDO):必须用
prepare()+bindValue(),$stmt->execute(['%' . $q . '%'])是安全的;"WHERE title LIKE '%" . $q . "%'"是危险的,无论 $q 是否经过addslashes()都可能被绕过 - Python(Django):优先走 ORM 的
filter(title__icontains=q),它底层自动参数化;若必须写原生 SQL,要用cursor.execute("SELECT * FROM article WHERE title LIKE %s", ['%' + q + '%']),绝不能用.format()或f-string - C#(SQL Server):必须用
@keyword占位符,cmd.Parameters.Add(new SqlParameter("@keyword", q));"WHERE name LIKE '%" + q + "%'"或Parameters.AddWithValue()(类型推断不准)都存在风险
LIKE 子句里的 % 和 _ 必须手动转义
即使用了参数化,LIKE 本身会把 % 和 _ 当通配符解析——攻击者搜 admin% 可能匹配到 administrator,这不是注入,但属于业务逻辑失控,且可能被用于盲注探测。
- MySQL:在
LIKE后加ESCAPE '',并在 PHP/Python 中提前把$q里的%、_、\替换为\%、\_、\\ - PostgreSQL:用
ESCAPE E''',且确保连接字符集为 UTF-8,否则宽字节可能绕过转义 - 更稳妥的做法:业务允许时,放弃前缀模糊匹配(
"%" . $q . "%"),改用后缀匹配($q . "%")或全文索引(MATCH ... AGAINST),减少通配符暴露面
排序字段、表名、列名不能参数化,得靠白名单
ORDER BY、GROUP BY、动态表名这些地方,参数化完全无效——数据库不接受 ORDER BY ? 这种写法。这里不是“要不要参数化”的问题,而是“根本不能拼接”的问题。
- 排序方向只允许
ASC/DESC:$order = in_array($_GET['sort'], ['ASC', 'DESC']) ? $_GET['sort'] : 'ASC'; - 列名必须硬编码白名单:
$allowed_cols = ['title', 'author', 'created_at']; $col = in_array($_GET['by'], $allowed_cols) ? $_GET['by'] : 'title'; - 表名绝对禁止由用户输入决定;如需多表查询,应拆成多个固定 SQL,用路由或权限控制分支
错误响应和执行行为要收敛
就算代码层全做了参数化,如果数据库报错直接返回给前端(比如 MySQL Error 1064),或者响应时间随输入明显波动(基于时间的盲注),攻击者依然能反向推断出数据结构。
- 生产环境禁用详细数据库错误输出,统一返回“搜索失败,请稍后再试”
- 对高频失败搜索(如含
UNION、SLEEP、SELECT的关键词)做速率限制或临时封禁 IP - LIKE 查询加上
LIMIT,避免全表扫描拖慢响应,也降低盲注成功率
真正难的不是写对那几行 prepare(),而是把所有动态拼接点——哪怕只是 ORDER BY 后一个字段名——都拎出来,逐个确认是否可控、是否白名单、是否可被绕过。漏掉任意一个,前面所有参数化都等于白做。

















