参数化查询是唯一可靠防线,因时间盲注不依赖错误回显或数据返回,只靠响应延迟判断逻辑真假,故所有用户输入必须通过预编译绑定(如PDO::prepare+bindValue、PreparedStatement、psycopg2占位符)隔离为纯数据,杜绝sleep()等函数进入SQL语句。

参数化查询是唯一可靠防线
时间盲注不依赖错误回显或数据返回,只靠响应延迟判断逻辑真假,所以任何拼接 SQL 的方式都可能被绕过。防御的核心不是“怎么检测延时”,而是让 sleep()、waitfor delay 这类函数根本无法执行——因为它们压根不会出现在最终语句里。
关键点在于:所有用户输入必须走预编译参数绑定路径,不能以字符串形式参与 SQL 构建。
- PHP 中必须用
PDO::prepare()+bindValue()或bindParam(),禁用mysql_query()、mysqli_query()直接拼接 - Java 中用
PreparedStatement,禁止用Statement+String.format()或+拼接 - Python 的
sqlite3.Cursor.execute(sql, params)、psycopg2的%s占位符,而非.format()或 f-string - MyBatis 中只允许用
#{},绝对不用${}——后者等同于字符串拼接,sleep(5)会原样进 SQL
MySQL 和 MSSQL 的延时函数差异要分清
攻击者常根据数据库类型选择不同延时函数,比如 MySQL 常用 sleep(5),MSSQL 则用 waitfor delay '0:0:5'。但防御时不需要识别这些函数——只要参数化到位,它们连语法解析阶段都过不了。
真正要注意的是开发环境和生产环境的一致性:
- 本地用 MySQL,上线却切到 MSSQL?若代码里混用
if(1=1, sleep(5), 0)这种 MySQL 特有写法,上线直接报错,反而暴露了技术栈,给攻击者提供线索 - ORM 如 Django ORM 或 Laravel Eloquent 默认安全,但一旦调用原生查询(如
raw()、DB::select()),就必须手动确保参数绑定 - 存储过程里如果用了动态 SQL(
EXEC(@sql)或CONCAT()拼接),同样会绕过应用层参数化,属于高危设计
WAF 和日志监控只能辅助,不能替代编码规范
很多团队把希望寄托在 WAF 规则上,比如拦截含 sleep、benchmark、waitfor 的请求。但这类规则极易被绕过:
-
sleep(5)可写成sleeP(5)、SLeeP(5),大小写混淆 - 用嵌套函数绕过关键词检测:
sleep(if(1=1,1,0))→ 实际仍执行延时 - 用十六进制或 URL 编码:
%73%6c%65%65%70(5)(sleep 的 hex) - 更隐蔽的:用
BENCHMARK(10000000,ENCODE('a','b'))替代sleep(),WAF 很难覆盖全
日志里记录慢查询(>5s)有用,但时间盲注常设为 sleep(1) 或 waitfor delay '0:0:1',刚好卡在业务正常响应边缘,很难区分是攻击还是真实慢查询。
最小权限原则必须落地到具体账号
即使参数化没写错,数据库账号权限过大也会放大风险。比如一个只查用户列表的接口,数据库连接用的是 root 或 sa 账号,那攻击者一旦通过其他漏洞(如反序列化、命令注入)拿到执行上下文,就能直接调用 sleep()。
实操建议:
- 为每个应用分配独立数据库账号,禁止共用
admin类账号 - 该账号只授予必要权限:
SELECTon specific tables,禁用EXECUTE、CREATE FUNCTION、SHOW DATABASES - MySQL 中禁用
FILE权限,防止通过load_file()读取配置;MSSQL 中禁用xp_cmdshell扩展存储过程 - 定期审计账号权限,尤其注意测试环境导出的账号是否误用于生产
最易被忽略的点:参数化能拦住 99% 的时间盲注,但拦不住开发者自己写的 eval()、exec()、os.system() 调用,或者用 system() 函数去执行 shell 命令时拼接了用户输入——这类漏洞和 SQL 注入无关,但同样会导致延时可控,且更难被 WAF 捕获。

















