2. Snort架构与数据包处理流水线
Snort 的检测能力并不是由一个“万能程序”一次性完成的,而是由多个职责明确的模块按顺序协作完成。理解这条流水线,是后续编写规则、调优性能、排查漏报误报的基础。Snort 2.x 的经典架构可以概括为四个核心组件:解码器(Decoder)、预处理器(Preprocessor)、检测引擎(Detection Engine)和输出模块(Output Module)。数据包从网卡进入后,依次经过这些组件,最终形成告警或日志。
一、四个组件的职责划分
解码器负责把网卡抓到的原始字节流翻译成 Snort 内部的结构化数据。网卡交给 Snort 的是一串链路层帧,解码器按协议栈逐层解析:以太网头部给出源/目的 MAC 和上层协议类型;若是 IPv4,则解析版本、首部长度、TTL、协议号、源/目的 IP;若是 TCP/UDP,再解析端口、标志位、序列号等。解码器还会识别 VLAN 标签、IP 分片、TCP 选项等细节。它的输出是一个填充好的 Packet 结构体,后续模块都基于这个结构工作。解码器本身不做判断,只做“翻译”。
预处理器位于解码器和检测引擎之间,作用是“在规则匹配之前先把数据整理好”。典型职责包括:IP 分片重组(frag3)、TCP 流重组(stream5)、端口扫描检测(sfPortscan)、ARP 欺骗检测(arp_spoof)、协议规范化(如 HTTP 解码、Telnet 协商处理)等。预处理器解决的是“单个数据包看不出来的问题”——例如一次 HTTP 请求被拆到多个 TCP 段中,只有重组后才能被规则正确匹配。预处理器还可以提前丢弃明显异常的流量,减轻检测引擎负担。
检测引擎是 Snort 的核心。它把预处理后的数据与加载的规则集逐条比对。Snort 采用多模式匹配算法(如 Aho-Corasick)先快速筛选出可能命中的规则,再对候选规则做详细的条件判断(协议、端口、方向、内容、PCRE、字节测试等)。规则头决定“看哪些包”,规则选项决定“满足什么条件才算命中”。一旦某条规则的所有条件都满足,检测引擎就生成一个事件(event),交给输出模块。
输出模块负责把事件落地。它支持多种输出插件:alert_fast 写简短的告警文本,alert_full 输出包含包头信息的详细告警,unified2 生成二进制日志供 Barnyard2 或 Snorby 等工具消费,database 插件可直接写入数据库。输出模块还可以同时启用多个插件,实现“本地快速告警 + 集中化日志分析”的组合。
二、数据包从网卡到告警的完整流转
下面以一次典型的 TCP 攻击检测为例,说明数据包在流水线中的实际流转过程。假设攻击者向目标 Web 服务器发送一个包含 SQL 注入特征的 HTTP 请求,Snort 部署在旁路镜像端口上。
第一步:抓包。Snort 通过 libpcap(或 AFPACKET、PF_RING 等高性能抓包接口)从网卡获取数据帧。此时数据还是原始字节,Snort 尚未理解其含义。
第二步:解码。解码器按以太网 → IP → TCP → HTTP 的顺序逐层解析。它记录下源 IP、目的 IP、源端口、目的端口、TCP 标志位(如 PSH+ACK)、载荷起始位置和长度。如果该 HTTP 请求被拆成两个 TCP 段,解码器会分别产生两个 Packet 结构体。
第三步:预处理。stream5 预处理器发现这两个 TCP 段属于同一会话,按序列号将它们重组为完整的 HTTP 请求。HTTP 预处理器进一步对 URL 和头部做规范化(如处理 %20 编码、大小写折叠)。此时,原本分散在多个包中的攻击特征被还原到同一个数据缓冲区中。
第四步:规则匹配。检测引擎加载了包含 SQL 注入特征的规则,例如:
alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS \
(msg:"SQL Injection Attempt"; \
flow:to_server,established; \
content:"union"; nocase; \
content:"select"; nocase; \
classtype:web-application-attack; sid:1000001; rev:1;)
引擎先通过多模式匹配快速定位包含 "union" 的数据包,再检查同一缓冲区中是否同时出现 "select",并确认 flow 方向为 to_server、连接已建立。所有条件满足,规则命中。
第五步:事件输出。检测引擎生成一个事件,包含规则 sid、msg、时间戳、源/目的 IP 和端口、协议等信息。输出模块根据配置,将告警写入 /var/log/snort/alert(alert_fast 格式),同时通过 unified2 插件生成二进制日志,供后续 Barnyard2 解析入库。
三、各组件之间的边界与协作
理解组件边界有助于定位问题。如果告警中显示的载荷内容不完整,问题通常出在预处理器(流重组未生效或超时);如果规则明明写了却完全不触发,可能是解码器未正确识别协议(如非标准端口上的 HTTP 未被 HTTP 预处理器处理);如果告警重复出现多次,可能是输出模块配置了多个插件,或预处理器对同一会话产生了多次事件。