服务器盲注是通过页面响应时间、布尔逻辑或http状态码差异逐位推断数据的隐蔽攻击,字符串过滤对其无效,因其不依赖报错或回显,且攻击载荷如sleep()、条件表达式等常绕过正则黑名单,参数化查询才是根本防御手段。

字符串过滤对盲注完全无效,因为它不改变数据库行为
盲注不依赖报错或回显,而是靠响应时间、布尔逻辑或HTTP状态码差异来逐字推断数据。字符串过滤只在输入到达服务端前做简单替换或删除,但攻击者根本不需要触发报错——AND 1=1 和 AND 1=2 都是合法SQL,过滤函数不会删掉数字、等号或空格,更不会识别 SLEEP(5) 或 BENCHMARK(1000000,ENCODE('a','b')) 这类无害外观的延迟函数。
常见错误现象包括:前端用 str_replace("'", "", $input) 后,攻击者改用 1 AND (SELECT COUNT(*) FROM users) > 10;或用 %09(Tab)替代空格绕过空格过滤规则。
- 盲注本质是“让数据库自己说话”,不是拼出完整恶意语句,而是构造大量试探性条件
- 过滤函数无法感知上下文:数字型参数位置(如
id=1)根本不涉及引号,过滤单引号毫无意义 - MySQL 的
/*!50000SLEEP(5)*/内联注释会被多数过滤器当普通注释跳过,但数据库照常执行
为什么正则黑名单在盲注场景下形同虚设
盲注常用手法高度碎片化且动态变化,比如把 UNION 拆成 UNI/**/ON,把 SELECT 写成 SEL%09ECT,甚至用十六进制编码 0x73656c656374 表示 select。正则规则很难覆盖所有变体,而漏掉任意一种,攻击链就成立。
更关键的是,盲注不一定要用关键字:用 ORDER BY 触发列数探测、用 AND (SUBSTRING(@@version,1,1)='5') 推版本号,这些都不触发传统关键词匹配。
- WAF 或自定义正则通常只扫描一级请求参数,对嵌套 JSON 或 multipart body 中的字段视而不见
- PostgreSQL 支持
/*+ parallel(4) */提示语法,可被用作混淆,而 MySQL 黑名单规则对此无感知 - 部分框架自动解码 URL 编码,导致
%27→'在过滤后才出现,时序错位
参数化查询才是盲注的硬边界
盲注能成功,前提是用户输入被当作 SQL 代码的一部分执行。参数化查询(如 db.query("SELECT * FROM logs WHERE id = ?", [req.body.id]))从协议层把值传给数据库驱动,数据库引擎明确知道哪部分是数据、哪部分是结构,连 1 OR 1=1 都会被当做一个整数值处理,而非逻辑表达式。
注意:ORM 的 .filter(User.id == x) 安全,但一旦调用 .raw() 或拼接 ORDER BY 字段名,就退回盲注可利用状态。
- 数字型参数必须走绑定,不能用
WHERE id = " . intval($_GET['id'])—— intval 不防1.1或科学计数法1e2 - 动态排序字段(如
ORDER BY ?)不可参数化,必须用白名单:in_array($sort, ['created_at', 'name'], true) - MySQL 的
mysqli_multi_query()开启时,即使参数化也挡不住堆叠注入,需禁用该模式
容易被忽略的盲注入口点
盲注不止出现在 GET 参数里。Cookie 中的 session_id=abc123 若参与日志查询,Referer 头若被写入审计表,User-Agent 若用于统计报表——只要这些值未经参数化直接进 SQL,就是盲注温床。
最隐蔽的是“二次注入”:前端过滤后存入数据库的字符串,后期又被拼进另一条 SQL(比如后台导出功能读取用户备注字段再查关联数据),此时原始过滤早已失效。
- 所有非用户直输但可能含不可信内容的来源都要检查:HTTP 头、文件名、第三方回调参数、定时任务配置项
- PostgreSQL 的
pg_sleep()、Oracle 的DBMS_LOCK.SLEEP()延迟函数行为不同,防御策略得按实际 DB 类型校验 - 即使关闭错误回显,也要确保日志不记录原始 SQL——否则攻击者可通过日志泄露反推结构











