盲注型sql注入通过页面响应差异(如内容变化、状态码、响应时间)逐字推断数据,无需数据库报错;其隐蔽性强于报错注入,唯一根治方式是全程使用参数化查询彻底隔离输入与sql逻辑。

盲注型 SQL 注入不是“看不到错误就安全了”,而是攻击者根本不需要数据库报错——只要页面有细微响应差异(比如真假条件导致的加载快慢、内容有无、HTTP 状态码变化),就能逐字推断出数据。防护关键不在屏蔽错误,而在彻底切断输入与 SQL 逻辑的耦合。
盲注为什么比报错注入更难被发现
报错注入靠数据库吐出 MySQL syntax error 这类信息直接定位漏洞;盲注则完全静默:输入 ' AND 1=1 -- 和 ' AND 1=2 -- 都不报错,但前者返回正常页面,后者可能空白、超时或跳转——这些“副作用”就是攻击者的信号源。
- 布尔盲注靠页面内容/状态码差异判断真假,例如登录页输入
admin' AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a' --,成功则进主页,失败则提示“用户不存在” - 时间盲注靠
SLEEP(5)类函数制造可控延迟,输入admin' AND IF(1=1,SLEEP(5),0) --,响应耗时 >4s 就说明条件成立 - 带外盲注(如 DNS 外带)甚至不依赖 HTTP 响应,而是让数据库主动发起 DNS 请求,绕过所有前端过滤
参数化查询是唯一能根治盲注的手段
无论布尔、时间还是带外盲注,本质都是用户输入被当作了 SQL 代码执行。只要用 PreparedStatement(Java)、pg_query_params(PHP/PDO)、cursor.execute(sql, params)(Python/psycopg2)这类接口,数据库会严格区分“语句结构”和“参数值”,' OR 1=1 就只是个普通字符串,不会触发任何逻辑分支。
- MyBatis 中必须用
#{},禁用${}——后者是字符串拼接,等同于裸写 SQL - Node.js 的
mysql2库中,connection.execute('SELECT * FROM users WHERE id = ?', [req.query.id])才安全;connection.query(`SELECT * FROM users WHERE id = ${req.query.id}`)直接中招 - 即使你对输入做了正则过滤(比如只允许数字),遇到
id=1 AND SLEEP(5)这种纯数字+函数的 payload,过滤规则也会失效
别信“过滤单引号就能防盲注”这种说法
盲注根本不需要单引号。攻击者可以用 1 AND BENCHMARK(1000000,SHA1(1))(MySQL)、1 AND (SELECT COUNT(*) FROM information_schema.columns)>100(无引号布尔判断)、1 AND ASCII(SUBSTR((SELECT password FROM users LIMIT 1),1,1))>97(纯函数+数字比较)——所有这些都不含 '、"、--,但照样能跑。
- WAF 规则再强,也拦不住合法函数调用和数学表达式,因为它们本身就在 SQL 标准里
- 自定义转义(如把
'换成\')在多字节编码或宽字节场景下极易被绕过,且无法覆盖无引号场景 - 最小权限原则有用,但只是降低危害——盲注仍可读取当前账户能访问的所有表,删库跑路不一定需要
DROP权限
真正容易被忽略的点在于:很多团队以为加了 WAF 或做了输入长度限制就万事大吉,却没审计所有 DAO 层代码是否统一用了参数化接口。一个漏网的 string.format() 拼接,或者 ORM 框架里误用的原生 SQL 查询,就足以让整套防护失效。











