edwardfreemann/logsleuth
GitHub: edwardfreemann/logsleuth
一款基于 YAML 规则的日志检测 CLI 工具,无需 SIEM 即可对 SSH 和 Web 日志进行自动化威胁分析并生成分级报告。
Stars: 0 | Forks: 0
# logsleuth
[](https://github.com/edwardfreemann/logsleuth/actions/workflows/ci.yml)
[](https://www.python.org/)
[](LICENSE)
**针对 SSH 认证和 Web 访问日志运行 YAML 检测规则。获取一份分类报告,而不是满屏的 grep 输出。**
你只需指定原始日志,它就会将其解析为标准化事件,运行一系列检测规则(暴力破解、用户枚举、路径扫描、敏感文件探测),并按严重程度分组打印出附带证据行的检测结果。提供 JSON 输出以便于管道传输,提供 HTML 输出以便于直接发送给他人。
无需 agent、无需数据库、无需 SIEM 许可证。只需一个 Python 包、一个依赖(PyYAML),以及你只需 30 秒就能读懂的规则。
## 快速开始
开箱即用,可直接针对内置的示例日志(合成的,并植入模拟攻击)运行:
```
$ git clone https://github.com/edwardfreemann/logsleuth && cd logsleuth
$ pip install -e .
$ logsleuth run --logs samples/auth.log samples/access.log --rules rules/
```
上述确切命令的真实输出:
```
[logsleuth] samples/auth.log: 123 events (sshd)
[logsleuth] samples/access.log: 150 events (nginx)
logsleuth triage report
parsed 273 events from 2 file(s), ran 6 rules -> 8 finding(s)
== HIGH (4) ====================================================
[ssh-brute-force] SSH brute force
source_ip=203.0.113.66 count=16 window=2026-07-03 14:14:04 -> 14:16:40
Repeated failed SSH passwords from one source IP in a short window.
| Jul 3 14:14:04 web01 sshd[3441]: Failed password for dahl from 203.0.113.66 port 55911 ssh2
| Jul 3 14:14:17 web01 sshd[3444]: Failed password for dahl from 203.0.113.66 port 55416 ssh2
| Jul 3 14:14:24 web01 sshd[3447]: Failed password for dahl from 203.0.113.66 port 51695 ssh2
[ssh-user-enumeration] SSH user enumeration
source_ip=198.51.100.23 count=12 window=2026-07-03 14:35:16 -> 14:39:18
users: admin, ftpuser, git, guest, jenkins, minecraft, nagios, oracle, postgres, test, ubuntu, user1
Many "Invalid user" attempts from one source IP.
| Jul 3 14:35:16 web01 sshd[3477]: Invalid user oracle from 198.51.100.23 port 61713
[web-path-scanning] Path scanning / forced browsing
source_ip=192.0.2.146 count=26 window=2026-07-03 13:52:01 -> 13:53:02
paths: /.aws/credentials, /.git/config, /actuator/health, /backup.zip, /db.sql, ...
| 192.0.2.146 - - [03/Jul/2026:13:52:01 +0000] "GET /backup.zip HTTP/1.1" 404 153 "-" "Mozilla/5.0 (compatible; scanner/1.1)"
== MEDIUM (4) ==================================================
[web-sensitive-paths] Probe for sensitive files
source_ip=203.0.113.99 count=4 window=2026-07-03 14:28:48 -> 14:30:40
| 203.0.113.99 - - [03/Jul/2026:14:28:48 +0000] "GET /.env HTTP/1.1" 404 153 "-" "python-requests/2.31.0"
...
```
(为了 README 进行了截断;完整报告会显示每一条发现结果,并各自附带最多三行证据。终端输出会根据严重程度进行颜色编码。)
其他实用的调用方式:
```
$ logsleuth run --logs /var/log/auth.log --rules rules/ --json | jq '.findings[].group_key'
$ logsleuth run --logs samples/*.log --rules rules/ --html report.html
$ logsleuth run --logs /var/log/auth.log --rules rules/ --fail-on-findings # exit 2 if anything fires (cron/CI)
$ logsleuth rules --rules rules/ # list loaded rules and their thresholds
$ logsleuth parsers # list available log parsers
```
## 内置规则
| 规则 | 严重程度 | 触发条件 |
|---|---|---|
| `ssh-brute-force` | high | 5 分钟内来自同一 IP 的失败 SSH 登录次数 >= 8 |
| `ssh-user-enumeration` | high | 10 分钟内来自同一 IP 的不同**无效**用户名数量 >= 5 |
| `ssh-spray-many-users` | medium | 15 分钟内来自同一 IP 的不同用户名(任意失败)数量 >= 8 |
| `web-path-scanning` | high | 5 分钟内来自同一客户端的不同 404 路径数量 >= 12 |
| `web-error-spike` | medium | 2 分钟内来自同一客户端的 4xx/5xx 响应次数 >= 20 |
| `web-sensitive-paths` | medium | 任何针对 `/.env`、`/.git/`、`wp-login.php`、phpMyAdmin、shell 上传等的请求 |
## 规则格式
每个规则对应一个 YAML 文件。规则的所有功能都包含在此示例中:
```
id: ssh-brute-force
name: SSH brute force
severity: high # critical | high | medium | low | info
description: >-
Repeated failed SSH passwords from one source IP in a short window.
log_type: sshd # only run against events from this parser
match: # ALL conditions must hold (equals / regex / one_of)
- field: action
one_of: [failed_password, invalid_user]
threshold:
count: 8 # fire when >= 8 matching events...
window_minutes: 5 # ...land in any 5-minute sliding window
group_by: source_ip # evaluated separately per source IP
# distinct: user # optional: count DISTINCT users instead of raw events
```
有两点值得说明:
- **`distinct`** 改变了计数的对象。没有它时,8 次失败即视为暴力破解。加上 `distinct: user` 时,针对同一账户的 8 次失败会被忽略,而来自同一 IP 的 8 个不同用户名则会触发——这就是暴力破解规则与密码喷洒/枚举规则的区别,仅需一行 YAML 即可体现。
- **没有 `threshold`** 会使规则表现为特征签名风格:只要有一个事件匹配,它就会针对每个来源触发一次。这就是 `web-sensitive-paths` 的工作原理。
## 架构
```
log files ──> parsers (auto-detected) ──> normalized Events ──> rule engine ──> report
sshd, nginx/apache timestamp, source_ip, sliding-window text / json / html
user, action, status, thresholds with
path, method, raw group-by + distinct
```
- **Parser**(`src/logsleuth/parsers/`)将原始行转换为具有共享字段集的 `Event` 对象,因此一套规则语言就可以覆盖所有日志来源。一个 parser 大约 60 行代码:包含用于自动检测的 `sniff()` 启发式方法,以及一个 `parse_line()`。添加新来源(Windows 事件导出、journald 等)意味着只需增加一个新文件和一次 `register()` 调用。
- **规则引擎**(`src/logsleuth/rules.py`)根据规则过滤事件,对它们进行分组(通常按源 IP),并在按时间排序的组上运行双指针滑动窗口。它会报告满足阈值的最宽窗口,因此检测结果能展示攻击的完整范围,而不仅仅是前 N 次命中。
- **报告生成器**(`src/logsleuth/report.py`)以三种方式渲染相同的检测结果。JSON 输出稳定且支持管道传输;`--fail-on-findings` 提供了一个退出代码,你可以借此在 cron 中设置告警。
示例日志由 `scripts/make_samples.py`(设定了随机种子,具有确定性)生成,包含四种植入的攻击和逼真的良性干扰数据。测试不仅断言攻击行为会触发,**而且**断言良性的 IP 保持清白——因为如果检测结果把你自己的管理员标记为异常,那它还不如没有检测。
## 为什么开发这个工具
我是一名 Army SOC 分析师候选人。实际工作的大部分内容都是这样的循环:查看日志,判断哪些具有恶意,展示你的证据。用于教授这种循环的工具,要么是完整的 SIEM(在实验室中搭建显得笨重、昂贵且缓慢),要么是别人无法重用或测试的一次性 grep pipeline。
logsleuth 是我为自己实验室寻找的折中方案:将检测规则作为小巧易读的 YAML 文件,基于时间窗口使用真实的阈值,而不是简单的行匹配,并且生成的报告读起来就像一份分类记录。编写这些规则迫使我像一名检测工程师那样思考——到底什么区分了暴力破解和密码喷洒?(答案:你是统计事件总数,还是统计不同的用户名。)之所以存在误报测试,是因为 spray 规则的第一个版本曾把备份服务账号标记为了异常,这个教训让我铭记于心。
如果你正在学习蓝队(blue-team)工作,请 fork 它,破坏那些示例日志,并为一些新内容编写规则。这才是关键所在。
## 开发说明
```
$ python -m venv .venv && source .venv/bin/activate
$ pip install -e '.[dev]'
$ pytest -v
$ python scripts/make_samples.py # regenerate the sample logs
```
测试涵盖了两种 parser、滑动窗口/阈值逻辑(包括 distinct 计数和不触发的情况)、针对示例日志的内置规则,以及端到端的 CLI 测试。CI 会在 Python 3.9、3.11 和 3.13 上运行测试套件。
## 许可证
MIT。详见 [LICENSE](LICENSE)。
标签:Python, URL发现, 云计算, 安全检测, 安全运营, 恶意代码分类, 扫描框架, 无后门, 检测即代码, 红队行动, 规则引擎, 逆向工具