ashishsaud2025/mini-ids
GitHub: ashishsaud2025/mini-ids
一个用 Python 实现的轻量级入侵检测引擎,通过 YAML 规则对离线或实时网络流量进行匹配并生成告警。
Stars: 0 | Forks: 0
# Mini-IDS
[](https://github.com/ashishsaud2025/mini-ids/actions/workflows/ci.yml)
**一个轻量级的、受 Snort 启发的入侵检测规则引擎,使用 Python 编写。**
## 功能简介
`mini-ids` 从 **pcap 文件**或**实时网络接口**读取数据包,并将它们与受 Snort 启发的小型、声明式 **YAML 规则语言**进行匹配。
当一个数据包(或一组突发数据包)匹配某条规则时,引擎会打印警报,并可选择将其追加到 JSON Lines 文件中以便日后分析。
```
# 回放 capture 文件
python -m src.cli --rules rules.yaml --pcap capture.pcap
# 同时将 alerts 写入 JSONL 文件
python -m src.cli --rules rules.yaml --pcap capture.pcap --log alerts.jsonl
# 实时 capture(需要 root/admin 权限 — 见下文)
sudo python -m src.cli --rules rules.yaml --live eth0
```
| 参数 | 含义 |
|-----------|----------------------------------------------------------------|
| `--rules` | YAML 规则文件的路径(必填) |
| `--pcap` | 待重放的 PCAP 文件(离线分析) |
| `--live` | 用于实时嗅探的接口名称,例如 `eth0`, `en0`, `Wi-Fi` |
| `--log` | 将警报以 JSON Lines 格式追加到此文件中(每行一个 JSON 对象)|
## 规则语法
规则存放在单个 `rules.yaml` 文件中。该文件是一个 YAML 规则文档**列表**;加载器也接受以 `---` 分隔的多个文档。
```
- name: "ICMP Ping Sweep"
protocol: icmp
condition: "count > 10 in 5s from same src"
severity: medium
- name: "Plaintext FTP Login"
protocol: tcp
port: 21
payload_contains: ["USER", "PASS"]
severity: high
- name: "Suspicious User-Agent"
protocol: tcp
port: 80
payload_regex: "curl|python-requests|Wget"
severity: low
```
### 字段说明
| 字段 | 必填 | 含义 | 示例 |
|--------------------|----------|---------------------------------------------------------------------------------------------|------------------------------------------|
| `name` | ✅ | 显示在警报中的规则名称 | `"ICMP Ping Sweep"` |
| `protocol` | ✅ | 要匹配的数据包协议:`tcp`、`udp` 或 `icmp` | `tcp` |
| `severity` | ✅ | 警报严重程度:`low`、`medium`、`high` | `high` |
| `port` | ❌ | 目标端口过滤器(验证范围 1–65535)。在任何条件计数*之前*应用。 | `21` |
| `payload_contains` | ❌ | 子字符串列表(不区分大小写);它们**全部**必须出现在 payload 中。 | `["USER", "PASS"]` |
| `payload_regex` | ❌ | 在 payload 中搜索的 Python 正则表达式。 | `"curl\|python-requests\|Wget"` |
| `condition` | ❌ | `"count > N in Ts from same src"` → **有状态**窗口;`"payload_len > N"` → **无状态**大小检查 | `"count > 10 in 5s from same src"` |
一条规则必须至少定义一个匹配条件(`condition`、`payload_contains` 或 `payload_regex`)。一条规则上的多个条件将通过 **AND**(与)逻辑组合,因此必须每个指定的字段都匹配成功,规则才会触发。
**有状态窗口**会为每个 `(规则, 源 IP)` 维护一个滑动的时间窗口,因此 `count > 10 in 5s from same src` 表示如果在任何 5 秒的时间跨度内,有超过 10 个匹配的数据包来自同一个源,就会触发。该窗口使用 `collections.deque` 实现。
## 规则示例 (`rules.yaml`)
代码库附带了 10 条规则,涵盖了规范中的每一种场景:
| 规则 | 类型 | 场景 |
|----------------------------------------|-------------|--------------------------------------------|
| ICMP Ping Sweep | 有状态 | 5秒内来自同一源 IP 的 >10 个 ICMP 事件 |
| TCP Port Scan | 有状态 | 5秒内来自同一源 IP 的 >100 个 TCP 事件 |
| UDP Port Scan | 有状态 | 5秒内来自同一源 IP 的 >50 个 UDP 事件 |
| 明文 FTP 登录 | 无状态 | 端口 21 上的 `USER`/`PASS` |
| Telnet 明文登录 | 无状态 | 端口 23 上的 `login:`/`Password:` |
| HTTP Basic Auth 凭据 | 无状态 | 端口 80 上的 `Authorization: Basic ` |
| 可疑的 User-Agent | 无状态 | `curl`、`python-requests` 或 `Wget` |
| 异常大的 DNS 响应 | 无状态 | UDP/53 payload > 512 字节(放大攻击) |
| SSH 暴力破解 | 有状态 | 60秒内来自同一源 IP 的 >10 个 TCP/22 事件 |
| 超大 HTTP 响应 | 无状态 | 端口 80 上 payload > 65536 字节 |
## 这与 Snort/Suricata 的映射关系(及区别)
这种语言*受* Snort 的 `alert` DSL *启发*,但经过了刻意的简化:
| Snort / Suricata 概念 | Mini-IDS 等价物 |
|---------------------------------------------------|---------------------------------------------------------|
| `alert tcp $HOME_NET any -> $EXTERNAL_NET 80` | `protocol: tcp`, `port: 80` (始终为 `any -> any`) |
| `content:"USER"; content:"PASS";` | `payload_contains: ["USER", "PASS"]` |
| `pcre:"/curl\|python-requests\|Wget/i";` | `payload_regex: "curl|python-requests|Wget"` |
| `threshold: type both, track by_src, count 10, seconds 5` | `condition: "count > 10 in 5s from same src"` |
| `dsize:>512;` / `byte_test:...` | `condition: "payload_len > 512"` |
| `msg:"..."; classtype:...; priority:...` | `name: ...`, `severity: ...` |
| `sid:... rev:...` (规则标识/版本控制) | — (改用规则名称) |
**我们进行简化的原因:**
- **使用 YAML 而非 Snort DSL。** Snort 那种面向行的 DSL 非常紧凑,但包含大量隐藏的语法(动作、方向操作符、规则选项、修饰符)。YAML 使结构变得清晰明了,并允许我们在处理任何数据包之前,对各个字段进行独立验证并提供清晰的错误信息。
- **没有 home-net/external-net 变量。** `$HOME_NET`/`$EXTERNAL_NET` 需要了解网络拓扑结构;mini-ids 只是简单地匹配 `src`/`dst` 并按源 IP 进行计数。
- **没有规则动作。** Snort 规则可以执行 `alert`、`drop`、`reject`、`log` 等操作,而 mini-ids *仅*生成警报。
- **使用子字符串列表代替 `content` + offset/distance 修饰符。** Snort 的 `offset:`/`depth:`/`distance:`/`within:` 用于微调内容必须匹配的*位置*。支持它们会使语言变得过于臃肿,但教育意义却微乎其微。
- **单一的窗口语法。** Snort 的阈值支持多种组合(`type both/limit/threshold`、`track by_src/by_dst`、`count`、`seconds`)。我们恰好只支持一种形式:`count > N in Ts from same src`。
- **仅限正则表达式,不支持类似 `U` (UTF-8) 或 `R` (相对) 的 `pcre` 修饰符。** 正则表达式直接使用 Python 的 `re` 进行编译。
结论是:Snort/Suricata 规则是一种极其丰富的语言,专为以线速部署包含 40,000 条规则的社区规则集而调优。而这个项目则是希望你能完全理解匹配流水线中的每一个微小细节。
## 引擎内部原理
```
pcap/live packets
│
▼
capture.py ─ normalizes Scapy packets → dicts
│ (timestamp, protocol, src/dst, ports, payload)
▼
engine.py ─ for each rule:
│ 1. protocol/port gate
│ 2. payload_contains / payload_regex (stateless AND)
│ 3. condition:
│ • "payload_len > N" → stateless size match
│ • "count > N in Ts..." → sliding-window counter (deque)
│
▼
cli.py ─ prints alert to stdout, optionally appends JSONL
```
- **无状态规则**使用协议、端口、子字符串、正则表达式或大小检查来匹配单个数据包。`payload_contains` 的匹配不区分大小写(原始字节 → 转换为小写后比较),而 `payload_regex` 则区分大小写(如果需要不区分大小写的正则表达式,请自行编写 `(?i)`)。
- **有状态规则**为每个 `(规则名称, 源 IP)` 维护一个包含事件时间戳的 `collections.deque`。每个匹配的数据包都会追加其(数据包的)时间戳;一旦过期条目落在窗口之外(`cutoff = now - T`),就会立即从左侧弹出。这使得平摊维护成本为 O(1),且内存限制在最后 `T` 秒内的事件数量,即无需进行全历史扫描。仅当计数值*跨越*阈值时才会触发警报,从而避免在进行中的扫描期间每个数据包都产生警报。
## 已知局限性
- **不支持 TCP 重组。** 仅按单个数据包检查 payload;跨越多个 TCP 分段的内容对此引擎是不可见的。
- **不支持加密 payload 检查。** TLS/QUIC payload 显示为密文,无法匹配子字符串/正则表达式规则(尽管有状态的连接速率规则仍适用于 TCP/UDP 层面)。
- **单进程,单线程。** 纯 Python 引擎无法跟上线速流量;`sniff()` + Python 匹配的速度远不及 Snort 优化过的 C/模式匹配流水线。
- **无抗规避能力。** 没有重叠分片处理,没有 IP/TCP 标志位标准化,没有 TTL 检查,也没有去碎片化。攻击者可以轻易地规避这些规则。
- **端口扫描检测属于启发式方法。** `count > N in Ts` 会产生误报(正常的突发流量)和漏报(超出窗口时间范围的慢速扫描)。
- **pcap 文件会在分析前完全加载到内存中**(`rdpcap`)。处理非常大的抓包文件是不切实际的;如果需要,请在自己的代码中流式传输该文件。
- **重启后状态不会持久化。** 长时间的实时捕获会在内存中累积窗口;每个窗口的内存是有界的,但是 `(规则, 源 IP)` 键的字典会随着看到的唯一源 IP 数量增加而不断增大。
## 为你自己的抓包编写规则
推荐的工作流程是*先观察,后编写规则*:
```
# 1. 查看 capture 实际包含的内容(protocols、ports、payloads)
python scripts/analyze_pcap.py pcap-files/my-capture.pcapng
# 2. 找出哪些 flows 包含感兴趣的 strings(credentials、HTTP、UAs)
python scripts/find_interesting.py pcap-files/my-capture.pcapng
# 3. 为你发现的内容编写 rules(参见 my_rules.yaml,这是一个针对真实 capture 编写的带注释
# 教程文件),然后回放:
python -m src.cli --rules my_rules.yaml --pcap pcap-files/my-capture.pcapng
```
请查看 `my_rules.yaml` 中带有完整注释的示例,该示例详细介绍了*为什么*要这样编写每条规则(包括经典的 `USER` 与 `User-Agent` 子字符串陷阱,以及通过调整阈值来消除 TLS 突发误报)。
## 演示抓包
一个小巧的辅助脚本会生成一个独立的 `test_traffic.pcap`(由 Scapy 构建的数据包:FTP 登录、可疑的 User-Agent、超大的 DNS 响应,以及包含 110 个数据包的端口扫描突发流量),用于端到端测试 CLI:
```
python scripts/make_test_pcap.py
python -m src.cli --rules rules.yaml --pcap test_traffic.pcap --log alerts.jsonl
```
预期会出现四个警报:**Plaintext FTP Login**、**Suspicious User-Agent**、**Unusually Large DNS Response**,以及当突发流量超过阈值时触发的一个 **TCP Port Scan** 有状态警报。
## 运行测试
```
pip install -r requirements.txt
pytest -q
```
测试**仅使用带有模拟时间戳的事件**(没有真实抓包,也没有 Scapy),因此测试套件可以在任何机器上快速且确定性地运行。
## 项目结构
```
src/
rule_loader.py # YAML parsing + strict schema validation (clear errors)
engine.py # stateless matching + deque-based sliding windows
capture.py # pcap replay / live sniff() abstraction, Scapy normalization
cli.py # argparse entry point, stdout + JSONL output
rules.yaml # 10 example rules (classic Snort-style)
my_rules.yaml # annotated tutorial — how to write rules for YOUR traffic
scripts/
make_test_pcap.py # generates demo test_traffic.pcap
analyze_pcap.py # protocol/port/payload summary of a capture
find_interesting.py # locates flows carrying interesting strings
smoke_test.py # end-to-end smoke test (loader → engine → JSONL)
tests/
test_rule_loader.py # validation errors, multi-doc YAML, malformed files
test_engine.py # stateless + stateful matching with fake timestamps
README.md
requirements.txt
```
## 免责声明
本项目是一个**用于教育演示的项目**。它**不是**生产级别的 IDS 软件,绝不能用于保护真实的网络。真正的 IDS 引擎(Snort、Suricata)是在防规避、协议解码器、性能工程和坚如磐石的工具支持方面经过数十年发展的成果。请利用本项目来理解其中的*理念*,然后在真正进行防御工作之前,去阅读 Snort 的官方文档。
标签:Python, 云计算, 无后门, 网络安全, 规则引擎, 逆向工具, 防御绕过, 隐私保护