定期渗透测试是防止sql注入漏洞在上线后滋生的必要节奏,因其能发现静态扫描和ci/cd无法识别的运行时风险,如动态拼接、sdk退化、nginx重写绕过、orm误用及底层权限变更等非典型入口。

定期渗透测试不是补漏,而是防止SQL注入漏洞在代码上线后悄然滋生的必要节奏。
为什么静态扫描和CI/CD流水线根本拦不住SQL注入
静态扫描工具(如SonarQube、Checkmarx)只能看到当前提交的代码快照,但SQL注入点常在运行时动态生成:
-
mysql_query("SELECT * FROM users WHERE name = '" + $_GET['name'] + "'")这类拼接在静态分析里可能被当作“普通字符串拼接”,尤其当变量经过多层函数传递后 - 第三方SDK升级可能把
queryBuilder->where()退化成裸字符串拼接,而Git diff里根本看不出变化 - Nginx重写规则把
/api/v1/user?id=1转成/user.php?id=1,绕过原有WAF防护层,静态扫描完全不感知 - ORM中误用
.extra(where=[f"name LIKE '%{xxx}%'"])或存储过程里写EXEC(@sql),这些都不是语法错误,而是逻辑风险
必须人工复测的三个非典型入口
自动化工具常忽略这些地方,但它们恰恰是真实漏洞高发区:
-
ORDER BY参数:传入id ASC, (SELECT password FROM users LIMIT 1)可能直接触发子查询 -
LIMIT和OFFSET:数值型参数若未强制转为整数,?limit=10; SELECT @@version--就可能执行 -
include字段(GraphQL或REST API):前端传{"include": "profile, (SELECT load_file('/etc/passwd'))"},后端若直接拼进SQL就崩了
测试时别只扫 ' OR 1=1--,要手工构造带上下文的payload,比如在排序字段后加子查询、在分页参数里塞时间盲注语句。
盲注验证必须排除干扰因素
当目标关闭错误回显、过滤关键字、又无字段回显时,AND SLEEP(5) 是否真生效,不能只看单次响应时间:
- 先用正常请求跑10次,记录平均耗时(比如
120ms),作为基线 - 再对同一payload发起至少30次请求,观察是否稳定高出
400ms+ - 必须确认没被CDN缓存、WAF限速、数据库连接池排队干扰——可对比
curl -w "%{time_total}"和服务端日志里的实际处理时间 - 同一payload在测试环境延时成功,生产环境可能因
wait_timeout设置过短而被截断,得实测
权限变更会让旧漏洞“复活”
数据库账号权限、插件启用状态、RDS参数组更新这些底层变动,不会触发任何代码扫描告警,却直接影响注入利用链:
- 半年前
information_schema被禁用,你以为盲注失效;运维给账号加了FILE权限后,LOAD_FILE()立刻能读取/etc/passwd - PostgreSQL开启
dblink扩展,原本只能读users表的注入点,突然能跨库查询 - MySQL启用
local_infile,配合INTO OUTFILE就能写Webshell - 每次渗透测试必须复测历史漏洞点,并检查
SHOW GRANTS FOR CURRENT_USER、SELECT plugin FROM mysql.plugins等底层状态
真正卡住SQL注入的,从来不是某次报告里的“已修复”,而是每次发版前强制跑一遍 sqlmap -u "https://api.example.com/user?id=1" --batch --level=3 --risk=2,再人工过一遍所有 WHERE、ORDER BY、LIMIT 的拼接逻辑——否则漏洞就在你改完代码的下一秒,静静躺在Git commit里等待上线。











