4. 漏洞扫描与手工验证
面试中问到“你怎么做漏洞扫描”,如果只回答“用 Nessus 扫一遍,然后导出报告”,基本会被判定为工具操作员而非渗透测试工程师。真正拉开差距的,是能否讲清楚扫描器在什么条件下会骗你,以及你如何用手工手段把结论钉死。本节围绕扫描器原理与局限、误报与漏报的成因、手工验证思路、报告解读与追问应对四个层面展开。
一、扫描器到底在做什么
主流扫描器(Nessus、OpenVAS、AWVS、Xray、Nuclei 等)的工作模型可以拆成四步:资产发现、端口与服务识别、插件/模板匹配、结果判定。以一次典型的内网扫描为例,假设目标网段为 10.10.20.0/24,扫描器先做主机存活探测(ICMP、TCP SYN 到常见端口),再对存活主机做端口扫描,然后对开放端口做服务指纹识别(读取 banner、发送探针比对特征库),最后把识别到的服务版本与插件库中的检测逻辑匹配,命中则输出漏洞条目。
关键点在于:扫描器的判定本质是“特征匹配 + 响应推断”,不是“真实利用”。它看到 Apache 版本是 2.4.49,就推断存在 CVE-2021-41773 路径穿越;它发送一个带 ../ 的请求,看到返回 200 且响应体包含 root:,就判定漏洞存在。这个推断链条中任何一环出错,都会产生误报或漏报。
二、误报与漏报的典型成因
误报(False Positive)指扫描器报有漏洞但实际不存在。常见原因包括:
- 版本推断错误:目标做了版本伪装或反向代理,banner 显示旧版本但实际已打补丁。例如 Nginx 返回
Server: nginx/1.18.0,但后端实际是 1.24,扫描器仍按 1.18 的漏洞库匹配。 - 响应特征巧合:扫描器用“返回 200 且包含某关键字”判定,而目标恰好因为自定义错误页返回了类似内容。
- WAF/IPS 干扰:WAF 拦截了攻击请求并返回 200 的拦截页面,扫描器误以为攻击成功。
漏报(False Negative)指实际存在漏洞但扫描器未报。常见原因包括:
- 认证后漏洞:扫描器未登录,无法访问需要认证的功能点,如后台的文件上传、SQL 注入。
- 逻辑漏洞:越权、支付篡改、验证码绕过等业务逻辑问题,扫描器几乎无法覆盖。
- 非标准端口/协议:服务跑在 8443、9443 等非默认端口,或使用自定义协议,指纹识别失败导致插件不触发。
- 时间盲注与竞争条件:需要精确时间测量或并发触发的漏洞,扫描器默认配置下容易漏掉。
三、手工验证的标准思路
拿到扫描结果后,不要直接采信,按“复现—确认—定级”三步走。以一条“SQL 注入”告警为例,假设扫描器报告 http://10.10.20.15/product.php?id=1 存在注入。
第一步,复现请求。用 curl 或 Burp Repeater 发送原始请求,观察响应:
curl -i "http://10.10.20.15/product.php?id=1"
curl -i "http://10.10.20.15/product.php?id=1'"
curl -i "http://10.10.20.15/product.php?id=1 AND 1=1"
curl -i "http://10.10.20.15/product.php?id=1 AND 1=2"
判断依据:id=1 与 id=1' 返回内容是否不同;AND 1=1 与 AND 1=2 的响应体长度、状态码、页面元素是否有差异。如果 1=1 正常、1=2 异常,说明参数参与了 SQL 拼接,注入大概率成立。
第二步,确认可利用性。用 ORDER BY 探测列数,再用 UNION SELECT 回显:
id=1 ORDER BY 5-- -
id=1 UNION SELECT 1,2,3,4,5-- -
如果页面回显了 2 或 3 等数字,说明 UNION 注入可行,可进一步读取 database()、user()。若页面无回显,则转向时间盲注:
id=1 AND SLEEP(5)-- -
观察响应时间是否稳定延迟约 5 秒。注意要重复 2~3 次,排除网络抖动。若延迟稳定出现,可判定时间盲注成立。
第三步,定级与影响判断。能读取数据库版本、当前用户、库名,说明危害至少为中危;若能通过 LOAD_FILE 读文件或 INTO OUTFILE 写文件,则升级为高危。定级要结合业务数据敏感度,不能只看技术利用链。
四、扫描报告解读与面试追问应对
面试官常追问:“扫描器报了 200 个漏洞,你怎么处理?”回答要点是去重、分级、验证、复测。去重指同一漏洞在不同 URL 重复出现时合并;分级按 CVSS 与业务影响排序;验证即上述手工确认;复测是修复后重新扫描确认闭环。
另一个高频追问:“误报怎么向客户解释?”正确姿势是给出证据链:原始请求、响应对比、判断依据、结论。例如“扫描器报 CVE-2021-41773,我发送 GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd,返回 404,且目标 Apache 版本经 apache2 -v 确认为 2.4.51,已修复该漏洞,判定为误报”。
下面给出一份可直接用于面试或实战的手工验证记录表示例,字段按漏洞验证场景设计:
| 字段 | 示例内容 | 说明 |
|---|---|---|
| 漏洞名称 | SQL 注入 | 扫描器原始告警名称 |
| 目标 URL | http://10.10.20.15/product.php?id=1 | 含参数的完整地址 |
| 扫描器判定依据 | 响应差异 + 报错信息 | 记录扫描器为何报 |
| 验证请求 | id=1 AND 1=2-- - | 实际发送的 payload |
| 响应观察 | 页面空白,状态码 200,长度 0 | 与正常请求对比 |
| 结论 | 确认存在 | 确认/误报/待定 |
| 危害等级 | 高危 | 结合数据敏感度 |
| 修复建议 | 参数化查询 | 可落地建议 |
最后强调一点:面试中不要只说“我用工具扫”,要展示工具输出 → 人工质疑 → 手工验证 → 结论定级的完整思维链。扫描器是起点,手工验证才是渗透测试工程师的核心价值。能讲清楚误报漏报的成因、能现场写出验证 payload、能给出证据链,才是面试官想听到的答案。