简介:这是一套基于Suricata的网络入侵检测系统毕业设计demo,面向计算机、网络安全、电子信息等专业学生,特别适合课程设计、期末大作业或毕设参考。资源包含完整可运行源码与项目说明文档,覆盖Suricata核心检测模块、流处理、应用层协议解析(如HTTP、SSL、SMTP、DCERPC等)以及Web前端展示页面,可帮助读者理解从抓包分析、规则匹配到告警展示的完整IDS实现思路。压缩包共2000个文件,总大小约195MB,其中以C源码(569个)、JavaScript(559个)、头文件(534个)居多,另有CSS样式、JSON配置、Markdown说明、Python脚本及少量Vue/Shell/YAML文件,目录组织清晰,便于定位与二次开发。目前已有446人学习下载,具有一定的参考热度。对于想快速搭建网络入侵检测原型系统或深入研读Suricata源码的读者来说,这是一份兼顾代码实践与文档讲解的实用资料。
1. 为什么把Suricata源码拆成网络入侵检测系统Demo
Suricata不是那种下载完跑一下规则就算完的工具。这个demo把源码目录里的detect-fast-pattern.c、stream-tcp.c、detect-http-uri.c、app-layer-htp.c等十几个文件直接摆出来,相当于把一条入侵检测链路的核心部分全部暴露在眼前:先由TCP流重组还原会话,再由应用层解析器抽出HTTP、SSL、SMTP等协议字段,最后规则引擎在特定字段上做精确匹配。它的定位是“能演、能改、能答辩”的最小系统,而不是完整的企业级态势感知平台。适合两类人:一类需要快速交付一个可运行的网络入侵检测系统demo;另一类想读Suricata源码,却不知道从哪几个文件入手的开发者。理解这几个文件的位置,整个IDS的骨架基本就清楚了。
2. Suricata规则匹配引擎:fast_pattern与HTTP字段检测源码走向
2.1 规则从文本到模式树的过程
Suricata的检测引擎不会逐条规则去遍历流量。加载规则时,它先做语法解析,把每条规则拆成content、http_uri、flow等条件,再交给多模式匹配器统一处理。常见的匹配器有Aho-Corasick(AC)和Hyperscan,前者适合小规则集,后者在大规则集下依赖SIMD指令,吞吐量能拉开一个数量级。
而detect-fast-pattern.c的作用,就是从一条规则的多个content中挑出一个作为“快速模式”(fast_pattern),先在整个数据包或事务流上做一次粗筛,只有快速模式命中的规则才会继续匹配其余条件。这个选择直接影响引擎的最终性能。在源码里它通常是在规则初始化阶段被调用的,结构大致如下:
// 示意:detect-fast-pattern.c 中模式筛选逻辑 static int FastPatternForSig(Signature *s) { Content *c = NULL; // 遍历规则中的所有content for (c = s->content_list; c != NULL; c = c->next) { if (c->flags & CONTENT_NEGATED) { // 取反的content不适合做快速pattern continue; } if (c->len < 3) { // 长度太短,丢弃,避免误命中带来的开销 continue; } // 挑选长度最长且可能区分度最高的content ... } }这里的核心逻辑是“区分度”。一个纯字母的“GET”在HTTP流量里几乎每包都有,拿它做快速模式会让匹配器每次都在命中的边缘挣扎;而像"/cmdshell.asp"、某个固定的User-Agent片段,长度更大且语义受限,命中概率低,更适合做第一层过滤。所以,当你发现自己的自定义规则匹配性能下降时,优先检查是不是快速模式选错了。
2.2 detect-http-uri与detect-http-host的分工
HTTP检测是Suricata实际使用中最频繁的场景之一。这一层的关键文件是detect-http-uri.c、detect-http-host.c和detect-http-server-body.c。它们本质上是不同的规则关键字注册入口:
// 示意:detect-http-host.c 中的关键字注册 void DetectHttpHostRegister(void) { sigmatch_table[DETECT_HTTP_HOST].name = "http_host"; sigmatch_table[DETECT_HTTP_HOST].Setup = DetectHttpHostSetup; sigmatch_table[DETECT_HTTP_HOST].Match = DetectHttpHostMatch; }规则里写http_host; content:"evil.example.com";时,引擎并不会直接拿这段字符串去扫描原始报文。DetectHttpHostMatch会先从应用层解析出的HTTP事务对象里取出规范化后的Host字段,再做字符串匹配。这样做的价值在于:URI可能经过URL编码,Host在HTTP/1.1里不区分大小写,直接在原始字节上匹配很容易被小技巧绕过,而规范化的字段可以规避这类问题。
2.3 一个容易忽略的差异:原始字节与规范化字段
很多初步接触Suricata的人会把http_uri和普通的content混用,但这两者的匹配对象完全不同。http_uri拿到的是解析器解码后的URI,例如%2e会变成.,大小写也会被统一处理。而content在没有rawbytes修饰时,同样会被引擎做不区分大小写的规范化匹配。如果你要精确匹配原始请求字节,需要显式使用rawbytes,但这样也会失去跨事务、跨包匹配的能力。
| 规则关键字 | 对应源码文件 | 匹配对象 |
|---|---|---|
| http_uri | detect-http-uri.c | 解码、去空白后的URI |
| http_host | detect-http-host.c | 转小写后的Host字段 |
| http_server_body | detect-http-server-body.c | 服务器响应体,按长度分块 |
| http_client_body | detect-http-client-body.c | 客户端请求体 |
| http_header | detect-http-header.c | 所有header按名称排序后的拼接串 |
排查时我习惯先看解析器到底输出了什么,再去反推规则写法。例如Suricata在调试日志里会记录HTTP事务的URI和Host,如果你看到的是解码后的值,就没必要在规则里写编码形态的匹配串。
3. TCP流重组与应用层解析:从stream-tcp.c到app-layer-htp.c
3.1 为什么IDS必须自己完成流重组
给IDS写一条“检测URI中包含/cmdline”的规则看起来简单,但真实的网络流量很少按顺序把完整URI放在一个包里。TCP把应用层数据切成若干个段,中间还可能插入乱序段或重传段。如果不做重组,一个被拆成两半的恶意特征会被当成两个独立的数据包,规则永远无法命中。
stream-tcp.c承担的就是这个职责。它维护每个TCP会话的发送方和接收方状态,包括序列号、窗口大小、ACK号以及大小不等的segment树。当新包到达时,引擎会把载荷插入segment树,并尝试把连续的数据拼成一个有序的字节流。这之后,应用层解析器才能拿到完整的数据块。实际源码里对应StreamTcpReassemble这类函数,处理的主要是TcpStream和StreamBuffer对象。
// 示意:stream-tcp.c 中的重组入口 void StreamTcpReassemble(TcpStream *stream, Packet *p) { // 1. 检查序列号是否在窗口内 // 2. 将载荷放入segment树 SegmentListInsert(stream, p); // 3. 尝试合并连续段,产出连续数据 SegmentTreeMerge(stream); }如果不做这个工作,规则引擎拿到的是零散数据块,很多规则在真实流量里都会漏报。这也是为什么Suricata的演示项目里stream-tcp.c被单独拿出来作为核心文件的原因。
3.2 app-layer框架如何把重组后的数据变成协议字段
TCP重组只是第一步。重组后的字节流还需要被翻译成“协议字段”,Suricata才能用http.uri、tls.fingerprint这类语义化条件去匹配。这个过程由app-layer-*.c完成。
以HTTP为例,app-layer-htp.c封装了HTP库,它把请求行、Header、Body拆开,并为同一个TCP连接上的多个请求/响应维护多个事务对象。app-layer-smtp.c解析邮件命令和MIME头,app-layer-ssl.c从TLS握手报文中抽取版本、SNI和证书指纹。每个解析器最终都会把提取到的字段挂到Flow上的某个协议状态结构里,供检测引擎查询。
// 示意:app-layer-htp.c 中一次事务解析 static int HTPParseRequest(HTTPState *state, const uint8_t *data, uint32_t len) { // 解析请求行,得到method, uri, version // 解析headers,生成HeaderList // 创建HttpTransaction并加入事务链表 DetectTransactionProcess(state, transaction); return 0; }注意“事务”这个粒度。同一个TCP连接上可能有多个HTTP请求,Suricata会把它们拆成多个事务,规则匹配的上下文也卡在事务级别。规则里写flow:to_server只是限定方向,具体匹配仍然是基于单个事务的。
3.3 本demo中其他应用层文件的位置
| 源码文件 | 对应协议 | 检测场景示例 |
|---|---|---|
| app-layer-htp.c | HTTP | web攻击、恶意URI |
| app-layer-smtp.c | SMTP | 钓鱼附件、垃圾邮件特征 |
| app-layer-ssl.c | TLS/SSL | 恶意TLS指纹、自签名证书 |
| app-layer-dcerpc.c | DCERPC | 远程调用类攻击 |
| app-layer-dnp3-objects.c | DNP3 | 工控协议中的非法读写指令 |
从实现顺序看,stream-tcp.c产生的连续字节流会先经过这些解析器,再按方向交给对应的detect文件。因此,如果你在测试中发现某条规则语法正确却不触发,不要只盯着规则本身,先检查对应的app-layer解析器是否在编译时被启用、配置里是否被禁用。比如app-layer-dnp3-objects.c如果没有被编译进去,所有DNP3相关规则直接无效。
4. 把Demo跑起来:Suricata安装、规则配置与流量验证
4.1 最小可运行的编译与启动流程
拿到源码包后,第一件事不是看代码,而是先把引擎编译出来。如果仓库里有现成的configure脚本,我一般按下面的参数配置:
./configure --prefix=/usr/local/suricata \ --enable-pcap \ --enable-hyperscan make -j$(nproc) sudo make install参数说明:--enable-pcap用来支持读取pcap离线文件,这对开发期调试至关重要;--enable-hyperscan会启用多模式匹配加速,但如果宿主机CPU较老,可以不加并退回AC算法;--prefix安装到独立目录,方便后续整体删除。编译时报libhtp not found时,先看源码目录下有没有libhtp子模块,有的话需要先单独编译并设置CFLAGS包含它的头文件。
编译安装完成后,写一个最小的规则集合demo.rules:
alert http any any -> any any \ (msg:"demo: suspicious uri"; \ flow:to_server; \ http.uri; content:"cmd.exe"; nocase; \ sid:20250101; rev:1;)规则说明:alert表示告警;http限定协议;flow:to_server表示只匹配客户端到服务器的方向;http.uri规定只匹配规范化后的URI;content:"cmd.exe"是匹配子串;nocase表示忽略大小写。sid是规则唯一ID,建议自己约定一个区段,避免和内置规则冲突。
4.2 离线验证与活体监听
在开发阶段,我优先使用pcap文件而不是网卡实时流量,原因是可重复、容易定位问题。假设已经抓好了test.pcap,执行:
suricata -c /usr/local/suricata/etc/suricata/suricata.yaml \ -S demo.rules -r test.pcap -l output cat output/fast.log-r指定读取pcap文件,-S指定自定义规则,-l是输出目录。fast.log是纯文本告警列表,每行包含时间来源IP目的IP端口和规则消息。如果规则命中,输出结果会直接显示在fast.log里,非常适合演示。
如果需要看完整的HTTP详情,可以开启eve.json:
outputs: - eve-log: enabled: yes types: - alert - http这样output/eve.json里每行是一条JSON事件,http类型的事件包含hostname、url、http_method等字段,粒度更细。现场答辩时用jq过滤eve.json比贴几十行fast.log更有说服力。
4.3 跑demo时最容易碰到的几个问题
| 现象 | 大概率原因 | 处理方向 |
|---|---|---|
| 启动报“default-log-dir” | 当前用户对日志目录无权限 | 换普通用户运行并调整目录权限 |
| 规则加载报invalid option | 规则关键字拼写错误或依赖未编译 | 用suricata -T -S demo.rules测试 |
| 规则全部未命中 | pcap里没有对应方向的HTTP请求 | 用tshark验证pcap确实包含同一条URI |
| HTTP字段匹配失败 | 应用层解析器被编译选项裁剪 | 检查configure时的--enable开关 |
针对最后一种情况,我的调试习惯是先用tshark -r test.pcap -Y "http" -T fields -e http.request.uri确认流量自带URI,再给Suricata加--set app-layer.protocols.http.enabled=yes。如果pcap里的URI很短或只有一个TCP握手包,做毕设演示时很容易翻车,所以演示前一定先用tshark确认。
4.4 调整Suricata的运行模式
默认情况下Suricata会按worker模式跑,多线程处理流量,但离线小pcap文件不需要那么多并行度。可以用:
suricata -c suricata.yaml -S demo.rules -r test.pcap \ --runmode=single -l output--runmode=single强制单线程模式,排查问题时日志输出更有顺序,也能避免多个线程同时写日志带来的错乱。等单线程确认无误后,再切换回默认的worker模式验证最终性能。
5. 二次开发实战:改造detect-http-server-body与扩展DN P3解析
5.1 新增一个检测关键字的流程
如果毕设要求不只是“调用现成规则”,而是“改一个检测模块”,最直接的做法是仿照detect-http-server-body.c写一个新关键字。源码注册流程通常包含三个部分:注册关键字名称、提供Setup解析函数、提供Match匹配函数。
// 示意:自定义detect_demo_body关键字 void DetectDemoBodyRegister(void) { sigmatch_table[DETECT_DEMO_BODY].name = "demo_body"; sigmatch_table[DETECT_DEMO_BODY].Setup = DetectDemoBodySetup; sigmatch_table[DETECT_DEMO_BODY].Match = DetectDemoBodyMatch; }Setup负责读取规则参数,例如content:"xxx"前的修饰符、长度限制等,把结果存到自定义的data结构里。Match会在匹配阶段被反复调用,它拿到的data参数是payload分块。注意HTTP响应体往往很大,Suricata把它切成多块传给Match,所以“匹配整个body”和“在某个body分块上匹配”是两回事。大部分demo单个块就能展示效果,但如果需要做文件特征匹配,必须在Match里自行拼接缓存。
5.2 编写一个可替换的测试规则
注册好demo_body关键字后,规则文件可以这样写:
alert http any any -> any any \ (msg:"demo body sensitive data"; \ flow:from_server; \ demo_body; content:"mutation"; nocase; \ sid:20250202; rev:1;)flow:from_server限定服务器到客户端方向,与响应体匹配对应。代码块里content:"mutation"是真正参与匹配的内容。这里有一个调试技巧:如果你不确定自己的Match函数是否被调用,可以先临时让Match直接返回1,再用上面的规则跑pcap,如果告警出现,说明链路通;如果不出现,问题在解析器或事务创建,而不是匹配函数本身。
| 二次开发位置 | 需要修改的源码 | 验证手段 |
|---|---|---|
| 新增关键字 | detect自定义.c + detect.h | 规则加载是否成功 |
| 修改http匹配 | detect-http-server-body.c | 用包含目标的响应体pcap |
| 扩展协议解析 | app-layer-xxx.c | 用tcpdump抓对应协议包 |
| 调整事务逻辑 | app-layer-htp.c | 对比eve.json中事务数量 |
5.3 针对DN P3和DCERPC的验证方法
demo里还包含了app-layer-dnp3-objects.c和app-layer-dcerpc.c,这类协议流量不像HTTP那么常见,验证起来需要额外准备数据。DNP3属于工控协议,样本pcap可以从公开的数据包库下载,也可以用Scapy构造简单的DNP3帧。
# 用scapy构造一个最小DNP3包,投喂给TSHARK确认字段 from scapy.all import * from scapy.contrib.dnp3 import DNP3, DNP3Header pkt = IP(src="10.0.0.1", dst="10.0.0.100") / \ TCP(sport=20000, dport=20000) / \ DNP3() / DNP3Header() wrpcap("dnp3.pcap", [pkt])这段代码使用scapy.contrib.dnp3生成DNP3数据链路层包,写入pcap后,Suricata离线模式就可以读到。注意构建时dport填20000是DNP3默认端口,如果用了别的端口,需要在suricata.yaml的app-layer.protocols.dnp3.ports里增加配置。测试时先用tcpdump -nr dnp3.pcap看抓包是否能解析出DNP3,再交给Suricata规则匹配。
DCERPC的验证类似,端口通常为135,也可以用Scapy构造,但DCERPC的RPC头部字段较复杂,推荐先抓一个真实样本。如果你在windows主机上执行Distributed File System相关命令并抓包,Suricata的dcerpc规则就很容易触发。
5.4 修改源码后的回归验证
每次改动只动一个层,然后用同一组pcap回归,避免多变量同时变化。
suricata -T -c suricata.yaml -S demo.rules suricata -r demo.pcap -S demo.rules -l out diff out/fast.log baseline.log-T只做配置和规则语法测试,不跑流量。第二次-r跑pcap,然后diff结果。如果新增了关键字,还要确认fast.log里没有意外漏掉旧的告警。这里我吃过亏:改了一个Match函数的返回值逻辑,导致某类规则全部失效,但因为只用了新规则做测试,没发现回归问题,直到最终答辩前跑全量基线才暴露问题。
6. 用stats与fast_pattern日志验证你的检测规则
6.1 查看fast_pattern实际选中的模式
规则不命中或性能异常时,先确认fast_pattern是否选对了内容。Suricata的-v调试模式会在日志里打印模式信息,但除非抓取引擎日志,否则直接看规则本身更高效。你可以在任意一条规则的content后面手动加fast_pattern;修饰符,强制让该content作为快速模式。
alert http any any -> any any \ (msg:"force fp"; \ flow:to_server; \ content:"/login"; http.uri; fast_pattern; \ sid:20250303; rev:1;)fast_pattern;放在对应content的末尾,引擎会优先使用该content做多模式匹配。这个技巧在调试时很有用:先用最稳定、最长的字符串作为fast_pattern,排除其他误选干扰,再逐渐交给引擎自动选择。
6.2 用统计信息判断规则是否进入工作流
运行完pcap后,Suricata的统计信息会写入stats.log或eve.json的stats事件中。关注以下几项:
| 指标 | 含义 |
|---|---|
| detect.engine.rules_total | 已加载规则总数 |
| detect.engine.rules_analyzed | 实际参与匹配的规则数 |
| app_layer.flow.http | HTTP流数量 |
| tcp.memuse | TCP流内存占用 |
如果rules_total正常但rules_analyzed为0,说明规则被过滤了,常见原因是规则里指定的协议或端口与已开启的解析器不匹配。而http事件数量为零时,应该返回验证pcap本身的HTTP解析是否生效,而不是继续调规则。
# 从eve.json过滤HTTP事件 jq 'select(.event_type=="http") | .http.url' out/eve.json当fast_pattern被手动指定后,你会看到fast.log里对应sid的命中次数与未加修饰符时不同。这不是说强制指定一定更好,而是让你观察“模式选择”对结果的影响。真正部署时,规则集的快速模式应该交给引擎统一调度,手工干预只保留给性能瓶颈最明显的那几条规则。
本文还有配套的精品资源,点击获取