SQL注入漏洞的扫描工具在实际使用中经常出现“扫不到”或“误报一大堆”的情况,根本原因不是工具不行,而是使用方式没有贴合业务逻辑。上线前做SQL注入扫描,核心不是装一个工具跑一遍就完事,而是要解决三个具体问题:扫描目标怎么定、登录态和参数怎么喂给工具、扫描结果怎么筛掉噪音。下面直接拆开讲实操方法。

扫描目标先做资产收敛,别一上来就全网段乱扫

很多团队拿着AWVS、AppScan或者Xray对着域名就是一顿扫,结果扫出几千个URL,里面一半是静态资源,剩下一半有一堆重复参数。正确做法是先做流量收集。从测试环境或预发布环境的网关日志里导出最近48小时的请求,按路径去重,过滤掉.css、.js、.png、.woff等静态后缀,只保留带查询参数或者POST表单的请求。这样能把扫描目标从几千条压缩到几百条以内,扫描时间大幅缩短,误报率也会明显下降。

如果条件允许,直接接入API文档。Swagger、OpenAPI或者内部的接口管理平台导出的JSON,里面已经标明了参数名、参数类型和请求方法,把这些参数结构直接转换成扫描工具的输入,比爬虫盲扫精准得多。爬虫对前后端分离项目的表单识别很差,很多JSON接口根本爬不到,这是扫描漏报的最大来源。

登录态注入是扫描成败的分水岭

绝大多数业务系统的SQL注入点都在登录后的功能页面里,比如订单查询、用户列表筛选、报表导出参数。如果扫描器没有携带有效会话,扫到的全是登录页或者302跳转,根本进不到业务逻辑。实操中要先把登录流程走通,用浏览器开发者工具抓取登录成功后的Cookie,或者从接口响应里拿到Token,把这些凭证配置到扫描器的会话管理里。

对于使用JWT或者自定义Header鉴权的系统,很多扫描器默认只带Cookie,不会自动加Authorization头。这时候要手动在扫描配置里添加自定义Header,把Token值填进去。有些扫描器支持录制登录序列,比如先请求验证码接口、再提交用户名密码、最后从响应中提取Token,这种自动登录功能要优先用起来,能避免会话过期导致扫描中途失效。

还有一个容易忽略的点:验证码。如果登录接口有图形验证码或者短信验证码,扫描器没法自动绕过。解决办法是在测试环境临时关闭验证码校验,或者配置一个固定的万能验证码,只在测试库生效。上线前的扫描本来就应该在预发布环境做,不要直接扫生产。

参数标记决定扫描器能不能找到注入点

扫描器不是人,它不知道哪个参数会拼进SQL语句。如果只是给一个URL,扫描器会尝试对所有参数发payload,但很多参数是用于分页、排序或者前端逻辑判断的,注入测试效率很低。实操中要把参数分优先级。从代码层面看,凡是最终进入MyBatis的${}、JDBC的Statement拼接、或者Hibernate的原生SQL拼接的参数,都是高危参数,要重点标记。

如果拿不到代码,就从功能特征判断。搜索框、筛选条件、排序字段、ID类参数、导出接口的字段名参数,这些位置最容易出现拼接。把这些参数在扫描器里设置为“强制测试”,其他参数可以降低测试强度或者跳过。这样扫描时间能缩短一半以上,而且漏报率反而更低,因为扫描器把payload集中用在了真正危险的位置。

扫描器选择与payload调优

开源工具里sqlmap是绕不过去的,但sqlmap默认的检测级别对复杂登录态和JSON接口支持一般。用sqlmap扫JSON接口时,要把请求体保存成文件,用-r参数加载,并且手动指定参数位置。比如一个POST接口的请求体是{"page":1,"size":20,"keyword":"test"},要扫keyword参数,命令里要写清楚参数名,避免sqlmap把整个JSON当成一个字符串去测。

商业扫描器比如AWVS、Netsparker对登录态管理和爬虫支持更好,但payload相对固定,对国内常见框架的绕过能力不如sqlmap灵活。实际工作中建议组合使用:先用商业扫描器做全量覆盖,把发现的疑似注入点导出,再用sqlmap对单个参数做深度验证。这样既保证覆盖面,又能用sqlmap的tamper脚本处理WAF绕过和编码绕过。

tamper脚本的使用要有针对性。如果目标系统对空格有过滤,用space2comment;如果对单引号有转义,要看具体转义方式,有时候用双引号或者反斜杠能绕过。不要一上来就挂一堆tamper脚本,那样请求量暴增,扫描时间翻好几倍,而且容易把测试环境打挂。

扫描结果人工复核的四个关键动作

扫描器报出来的SQL注入点,至少有一半是误报。复核时先看响应差异。把扫描器发的payload复制出来,用浏览器或者curl手动发一次,对比正常请求和payload请求的响应。如果响应时间、页面内容、HTTP状态码有明显差异,再进一步验证。很多误报是因为扫描器把业务逻辑返回的“查询无结果”当成了注入特征。

第二个动作是看数据库报错信息。如果payload触发的是数据库语法错误,响应里会出现具体数据库类型的报错,比如MySQL的“You have an error in your SQL syntax”、Oracle的“ORA-”开头错误。这种基本可以确认注入存在。如果响应里只有通用错误提示或者空白页面,要结合时间延迟和布尔差异继续判断。

第三个动作是验证注入类型。确认存在注入后,要判断是字符型还是数字型,是报错注入、布尔盲注还是时间盲注。这决定了漏洞的严重程度和修复优先级。报错注入和联合查询注入可以直接脱库,属于高危;布尔盲注和时间盲注利用成本高,但也要修,只是排期可以靠后。

第四个动作是记录完整的复现步骤。把触发注入的完整HTTP请求保存下来,包括请求方法、URL、参数名、payload、响应特征。修复后要用同样的请求做回归测试,确认漏洞真正闭合,而不是只改了表面报错。

扫描报告输出与修复闭环

扫描完成后不要直接把工具导出的报告发给开发,那种报告开发看了也定位不到代码位置。要把扫描结果和代码关联起来。根据URL路径找到对应的Controller,再根据参数名定位到Service层和DAO层,在报告里写清楚“XX接口的XX参数存在字符型报错注入,代码位置在XX类的XX方法,原因是使用了Statement拼接”。这样开发才能快速修复。

修复方式上,参数化查询是首选。Java里用PreparedStatement,MyBatis里用#{}而不是${}。如果业务确实需要动态表名或列名,必须做白名单校验,把允许的表名字段名枚举出来,用户输入只能从白名单里选,不能直接拼进SQL。修复后要重新跑一遍扫描,确认漏洞消失,同时回归测试相关功能,避免修复引入新的业务逻辑问题。

上线前扫描的最终目标不是“扫完就完事”,而是把确认存在的注入漏洞在上线前全部修掉。扫描工具只是发现问题的入口,真正决定安全效果的是扫描配置的精准度、人工复核的严谨度,以及修复闭环的执行力。把这几个环节做实,SQL注入的上线前拦截率能提升到90%以上。