第三讲 扫描引擎原理:插件机制与扫描流程

说真的,我刚接触安全扫描那会儿,总觉得扫描工具就是个黑盒子——输入一个IP,出来一堆报告。直到我第一次去读Nmap的源码,才发现里面其实是个精密的插件工厂。

这一讲,我就带你把扫描引擎的底裤颜色看明白。不管是Nmap还是OpenVAS,核心逻辑就两件事:插件怎么挂上去的,扫描过程怎么串起来的。

3.1 插件机制:扫描工具的“乐高积木”

为什么需要插件?你想啊,安全检查的种类太多了——端口开放算一条、SSL证书过期算一条、SMB漏洞又是一个。如果全写在一个大程序里,改一个地方就要重新编译整个包。

所以主流工具都用了插件架构。说白了,就是定义好接口,每个人写自己的检测逻辑,然后通过配置文件“插”进去。

3.1.1 Nmap的NSE(Nmap Scripting Engine)

Nmap的扩展能力全在NSE里。我记得第一次写自己的NSE脚本,是用来检测某个内部系统的Redis未授权访问。整个脚本不到30行,注册一下就能跑。

NSE脚本的基本结构:

-- 脚本描述
description = [[
  检测目标是否运行了未授权的Redis服务
]]

-- 注册脚本到对应阶段
prerule = function() end
hostrule = function(host) return true end
action = function(host, port)
   -- 你的检测逻辑
   return "检测结果"
end

-- 分类信息
categories = {"safe", "default"}

这里有个关键:prerule、hostrule、portrule、postrule 这些阶段决定了脚本什么时候执行。我见过很多新手把检测逻辑一股脑塞进 action,却不配置 hostrule,结果每个端口都跑一遍,慢得要命。

我的建议:写NSE脚本时,先把 hostrule 或 portrule 写得严一点。比如只对开放了6379端口的IP执行Redis检测,能省下90%的时间。

3.1.2 OpenVAS的NASL(Nessus Attack Scripting Language)

OpenVAS继承了Nessus的NASL语法。说实话,NASL比NSE门槛高一些,但它对漏洞细节的表达更精确。

一个简单的NASL脚本框架:

# 脚本标识
script_id(12345);
script_name("检测Apache版本");
script_category(ACT_GATHER_INFO);

# 核心检测逻辑
port = 80;
banner = recv_line(socket:socket, length:1024);
if("Apache/2.4.49" >< banner) {
   security_message(port:port, data:"存在风险版本");
}

# 注册到扫描流程
script_add_preference(name:"超时时间", type:"entry", value:"5");

注意那个 script_category。OpenVAS把脚本分成好几类:ACT_GATHER_INFO、ACT_ATTACK、ACT_DENIAL 等等。不同类别在扫描流程里的执行顺序不一样。攻击类脚本要在信息收集之后才跑,这是基本规矩。

我曾经踩过的坑:写了一个DoS检测脚本,忘了设置 script_category,结果它在刚发现端口时就直接发了大量数据包,把客户的生产交换机打挂了。嗯,那天晚上我写报告写到凌晨三点。

3.2 扫描流程:从发现到报告的三阶段

不管用什么工具,扫描流程大致可以分成三个阶段。我习惯叫它“探测-枚举-验证”。

阶段 核心任务 插件类型 耗时占比
第一阶段 主机发现 + 端口探测 预扫描插件(probe) 30%
第二阶段 服务识别 + 信息收集 信息收集插件(gather) 40%
第三阶段 漏洞检测 + 结果验证 攻击/验证插件(attack) 30%

为什么是这个顺序?你想想看,如果连主机都还没发现,就直接去测漏洞,那不是瞎忙活吗?

3.2.1 第一阶段:探测与主机发现

这一步其实就是在问:“有人在家吗?”

Nmap用的是ICMP ping、TCP SYN到常用端口(比如80、443)、ARP请求这些方式。OpenVAS也类似,但它的预扫描插件更多,有时候会发送定制的探测包。

我记得有一次内网扫描,客户说所有主机都在线,结果Nmap只发现了一半。后来一查,是防火墙把ICMP全封了。换上 -Pn 参数,直接用TCP SYN扫端口,才把剩下的机器揪出来。

所以做DSPM扫描时,一定要清楚目标网络的过滤策略。不然后面的分析全是瞎子摸象。

3.2.2 第二阶段:服务识别与指纹收集

确定了主机和开放端口后,就该看看“门后面是什么”了。

这个阶段插件会做几件事:

  • 抓取banner信息——很多协议在连接时会主动暴露版本号
  • 发送探测请求——比如向HTTP服务发一个GET,看返回头的Server字段
  • SSL/TLS握手分析——提取证书、支持的加密套件
  • 服务主动指纹比对——Nmap的 service scan 用的就是这种

这里有一个坑:有些服务会故意隐藏版本信息。比如改了Nginx源码,把 Server: nginx 改成 Server: Microsoft-IIS/10.0。如果检测插件只信banner,那后续的漏洞检测就全跑偏了。

关键点:好的扫描引擎不会只依赖banner。它会结合多种探测方式——端口响应特征、协议行为差异、甚至TLS握手时的随机数生成模式,来做综合判断。我见过最狠的插件,会去分析目标服务器处理HTTP请求时的响应时间差异来判断是Apache还是IIS。

3.2.3 第三阶段:漏洞检测与验证

这是最吃插件质量的一步。有了前面的信息,插件才知道该看什么。

举个例子:一个检测Log4j漏洞的插件,它会:

  1. 先确认目标是否运行了Java应用(从第二阶段收集的信息判断)
  2. 再检测是否存在特定的JNDI接口路径
  3. 发送精心构造的payload,看响应中是否有依赖库加载的特征
  4. 如果匹配,标记为“高危”

但问题来了——误报。我见过一个扫描器,把Tomcat的默认错误页面当成了漏洞证据,结果整个部门花了两天人工复测,发现是虚惊一场。

所以现在的扫描引擎会加一个“验证”步骤。比如检测到疑似漏洞后,再发一个无害的请求,确认那不是误报。OpenVAS的 ACT_MIXED_ATTACK 类型插件就是这样做的。

我的习惯:做DSPM扫描时,我会把第三阶段的检测速度调慢一点。宁可在验证上多花时间,也不要生成一堆需要人工过滤的假警报。特别是流量敏感的生产环境,宁可漏报一个低危,也不能因为误报把运维哥们搞崩溃。

3.3 整个流程的串联逻辑

上面三个阶段不是各跑各的。扫描引擎内部有一个调度器,负责控制插件的执行顺序和数据传递。

下图是我自己整理的一个简化版扫描流程结构:

扫描引擎插件调度流程 扫描引擎插件调度流程 阶段一:探测 主机发现 · 端口探测 插件类型:probe 阶段二:信息收集 服务识别 · 指纹收集 插件类型:gather 阶段三:检测 漏洞检测 · 验证 插件类型:attack 插件调度器 控制执行顺序 · 传递上下文数据 结果汇聚与去重 合并多个插件结果,去除重复 报告生成与分类 按资产、风险等级输出 说明:三个阶段的数据是逐步传递的,前一阶段结果为后一阶段提供目标信息 调度器负责控制插件的启动时机和上下文传递

从这张图你能看到,调度器起了承上启下的作用。它手里维护着一张“插件注册表”,里面记录了每个插件的依赖条件、执行阶段、输入输出。

举个例子:一个检测MySQL弱口令的插件,它会在注册时声明:“我依赖端口扫描结果,只对3306端口执行,并且在服务识别之后才启动。”调度器看到这个声明,就知道该在什么时候把这个插件叫醒。

3.4 实战中的插件管理建议

做DSPM扫描,不可能上来就跑全部插件。我个人的做法是:

  • 先走一遍精简版:只启用探测和基本信息收集插件,快速摸清资产底数。
  • 再按风险分级:对关键资产启用全量检测插件;对非核心资产只跑高危漏洞检测。
  • 最后做差异化验证:对扫描结果中的高危项,启动专门的验证插件再次确认。

这样分轮次执行,既保证了扫描深度,又不会一次性把目标网络压垮。

特别注意:不要在生产业务高峰期启动全量插件扫描。我曾经在客户的OA系统上跑全量检查,三千多个插件同时往一个Tomcat发请求,直接把应用搞挂了。一定要用调度器的“并发控制”能力,限制同时活跃的插件数量。

好了,这一讲的内容就是这些。扫描引擎的原理理解透了,你在后面做策略配置和结果分析时,心里就有底了。