Johndenisnyagah/soc-triage
GitHub: Johndenisnyagah/soc-triage
多源安全日志分诊流水线,用确定性规则检测事件、用 LLM 解释和富化上下文,解决 SOC 团队告警过载和跨源关联难题。
Stars: 0 | Forks: 0
# SOC 告警分诊与上下文富化流水线
安全运营团队常常淹没在告警中。一个中等规模的环境每小时会产生数以万计的日志记录,涵盖 Linux 主机、云控制面和 Windows 域控制器,而分析师必须判断其中哪一小部分才是关键。
大部分时间都花在了机械性的工作上:阅读原始日志格式,在不同的来源之间交叉比对以检查同一个地址是否出现了两次,以及撰写事件报告。
这条流水线将上述过程自动化。它从多个来源摄取原始日志,将其标准化为统一的事件 schema,使用确定性规则检测安全事件,并利用大语言模型(LLM)解释其发现的内容 —— 包括 MITRE ATT&CK 映射、执行摘要和响应 playbook。
整个项目围绕着一个核心架构约束:**规则负责检测,AI 负责解释。** 模型绝不会创建、抑制或重新评估安全事件的评分。如果 LLM 不可用,流水线依然能够生成完整且正确的安全事件,并附带确定性的文本说明。无法复现和审计的检测算不上真正的检测。
本项目建立在 [LogLens AI](https://github.com/Johndenisnyagah/loglens_AI) 的基础之上,后者将同样的“规则检测/AI解释”架构应用于单一来源的 SSH 身份验证日志。在开发它的过程中,其结构性局限变得清晰:围绕单一日志格式构建的 schema 无法标准化 CloudTrail 记录,而局限于单个上传文件的安全事件无法关联出现在两处的攻击者。本项目正是为解决这两个问题而进行的重写。
## 状态
开发中。摄取和持久化层已完成并经过测试;检测和富化是接下来的步骤。
- [x] **摄取层** — 标准化事件 schema、基于置信度的 parser 注册表、sshd 和 CloudTrail parser
- [x] **持久化层** — 事件/实体模型、摄取 endpoint、全局去重
- [x] **检测** — 基于事件窗口的、与来源无关的规则
- [x] **关联** — 按照实体 key 和时间窗口进行事件分组
- [x] **MITRE ATT&CK 映射** — 静态规则映射和目录验证(LLM 提议路径尚未构建)
- [ ] **LLM 富化** — 带有置信度门控确定性回退机制的摘要生成
- [ ] **Windows 安全 parser**
- [ ] **执行报告和响应 playbook**
- [ ] **持续摄取 endpoint 和 worker**
## 架构
```
ingest → parse (registry) → normalize → detect (rules) → correlate (entity keys)
→ enrich (LLM: summary + ATT&CK, validated) → triage → incident → report
```
有两个设计选择承担了大部分的重量。
**Parser 选择基于置信度评分,而非顺序匹配。** 每个 parser 实现的是 `sniff(sample) -> float`,而不是返回布尔值的 `can_parse`。注册表会对前 200 行运行每个 parser 的 sniff 方法,并选择得分最高的一个;当得分低于置信度下限时,会回退到显式的“无法识别的格式”响应。如果使用 if/elif 链,添加一个 parser 可能会静默地捕获属于另一个 parser 的文件;评分机制使这种情况不可能发生,并提供了一个诚实的失败路径,而不是去盲目猜测。
**关联基于实体 key 运行,而非源字段。** 每个标准化事件都会为其涉及的对象生成带有命名空间的标识符 —— 例如 `ip:203.0.113.5`、`user:root`、`host:web01` —— 这些标识符存储在它们自己独立的索引表中。来自同一地址的一次失败 SSH 登录和一次失败的 CloudTrail 控制台登录会产生相同的 key,因此它们会被归入同一个安全事件中,而不是作为两个不相关的事件。如果将这些存储在 JSON 列中,会使跨源查找变成全表扫描;独立的表使其保持在单次索引查询的级别。
## 快速开始
```
git clone https://github.com/Johndenisnyagah/soc-triage.git
cd soc-triage/backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pytest -q
uvicorn app.main:app --reload
```
本地开发默认使用 SQLite。如需使用 Postgres,请设置 `DATABASE_URL`。
摄取一个示例文件:
```
curl -F "file=@../sample_logs/auth.log" http://localhost:8000/api/ingest
```
响应包含摄取 ID 和解析统计信息 —— 读取的行数、生成的事件数、跳过的行数以及跳过的重复项 —— 这样,对于部分格式错误的上传,系统会准确报告丢弃了什么,而不是静默失败。
```
{
"ingest_id": 1,
"filename": "auth.log",
"source_type": "syslog_sshd",
"detected_confidence": 0.86,
"events_persisted": 18,
"duplicates_skipped": 0,
"stats": { "lines_read": 30, "events_emitted": 18, "lines_skipped": 12, "errors": [] }
}
```
被跳过的 12 行是 CRON、systemd 和 sshd 会话行,sshd parser 将它们识别为 syslog,但并未将其建模为身份验证事件。第二次运行相同的命令会返回 `events_persisted: 0` 和 `duplicates_skipped: 18` —— 去重是全局性的,因此重新摄取文件是幂等的。
`sample_logs/cloudtrail.json` 覆盖了第二个 parser,并且有意与 `auth.log` 共享一个源 IP 和用户名:
```
curl -F "file=@../sample_logs/cloudtrail.json" http://localhost:8000/api/ingest
```
这两个文件共同生成了一个跨越两个数据源的 27 个事件的单一实体 key `ip:203.0.113.5` —— 包括一次 SSH 暴力破解突发、一次成功的 SSH 登录,随后是来自同一地址的 AWS 控制台登录和 IAM 变更。这正是检测和安全事件层正在构建的跨源关联基础。
## 设计决策
**规则负责检测,AI 负责解释。** LLM 负责编写摘要、提议 ATT&CK 技巧并选择 playbook。它绝不决定某事物是否构成安全事件。
**ATT&CK 技巧 ID 会根据真实目录进行验证。** 在映射关系已知的情况下,检测规则会静态映射到相关技巧。对于未映射的情况,模型可以提议某种技巧,但其返回的任何 ID 都会与 ATT&CK STIX 数据进行比对,一旦失败将被拒绝。一个看起来 plausible(似是而非)的技巧 ID 正是语言模型极度自信地产生的那种典型错误。
**Playbook 是检索出来的,而不是生成的。** 响应步骤来自以技巧 ID 为 key 的本地 YAML 库。模型负责选择和情境化;它不会凭空捏造事件响应程序。
**日志内容是不可信的输入。** User Agent、Windows 事件描述和 CloudTrail 字段都是攻击者可以控制的。传递给模型的证据是按事件进行上限控制的,并被包装为数据,同时系统 prompt 明确声明,绝不能执行在日志内容中发现的任何指令。
**Parser 绝不对畸形输入抛出异常。** 错误行会增加跳过计数器,并记录截断的摘要,每次运行上限为 100 个错误。仅仅因为一行格式错误的数据而中止一个 50 万行的摄取过程,是典型的流水线故障。
**去重是全局性且具有身份感知能力的。** 事件指纹排除了原始文本,因此空格和 JSON key 的顺序不会破坏去重,但它包含了一个特定于来源的自然标识符 —— 对于 sshd 是 pid 和 port,对于 CloudTrail 是 `eventID`。如果没有这个标识符,共享同一秒 syslog 时间戳的不同事件就会发生碰撞,导致一次暴力破解突发被折叠成单个事件。由于暴力破解突发正是检测层最需要看到的内容,这种碰撞会抹杀掉本项目致力于寻找的信号。
## 已知限制
- **去重仅对单写入者安全。** 摄取 endpoint 在 flush(刷盘)之前会通过批处理查询检查现有的 hash。对重叠数据进行两次并发的上传可能都会通过该检查,并在 unique index(唯一索引)上发生碰撞。持续摄取 worker 将需要使用 upsert,而不是先检查后插入。
- **上传大小上限限制的是进程内的数据。** `MAX_UPLOAD_BYTES` 限制的是驻留字符串,而不是服务器缓冲的内容 —— ASGI 层已经缓存了请求体。真正的上限应该设置在反向代理上。
- **摄取并非完全流式处理。** Parser 接收的是整个内容字符串。生成器接口避免了作为 ORM 对象进行第二次复制;它并没有避免持有源文本。完全的流式处理意味着 parser 需要接受行迭代器,并且对于 CloudTrail 的捆绑格式需要实现增量 JSON 解析。
- **Postgres 尚未验证。** 测试套件在 SQLite 上运行。全局 unique index 和时间窗口查询索引是这两个数据库引擎可能会产生分歧的地方。
## 技术栈
FastAPI, SQLAlchemy 2.0, Postgres(本地开发使用 SQLite),pytest。React 和 TypeScript 前端后续推出。
标签:告警分诊, 安全信息与事件管理, 安全规则引擎, 安全运营, 扫描框架, 搜索引擎爬取, 逆向工具