UscTrojansDodgers56/Elastic-eql-lab
GitHub: UscTrojansDodgers56/Elastic-eql-lab
在本地搭建自管 Elastic Stack 并编写 EQL 序列查询以关联 Linux 身份验证日志的安全检测实验项目。
Stars: 0 | Forks: 0
# Elastic + EQL 熟练度提升
在本地虚拟机上搭建自管 Elastic Stack,摄取真实的 Linux 身份验证日志,并编写 EQL 序列查询以关联多步骤身份验证行为。
**方法论:** 检测 - 分析 - 关联 - 强化 - 验证
## 坦诚声明
这是一个基于我自有硬件构建的个人自发实验环境。这不属于专业的 SOC 工作经验。凡是尽力而为或遇到已知限制的地方,都会在下面进行标注。在此构建过程中发生了四个真实的失败案例,并且全部记录在 [TROUBLESHOOTING.md](TROUBLESHOOTING.md) 中,因为排错过程是最有价值的部分。
## 为什么构建这个实验
我之前在 Microsoft Sentinel (Kusto) 和 Splunk (SPL) 中做过检测工作。我缺乏的是使用 Elastic 的实操经验,尤其是 EQL 序列,它能够以 Kusto 和 SPL 原生不支持的方式表达多步骤攻击行为。
目标是达到熟练:能够阅读 Elastic 查询、跟上分析师的交流思路,并准确谈论该技术栈。我特意将其范围设定得很小,作为一个周末的进阶练习,而不是一个完整的检测工程实验室。
## 环境
| 组件 | 详情 |
|---|---|
| 宿主机 | Windows 11 笔记本电脑,VMware Workstation |
| 虚拟机 | Ubuntu 25.10,12 GB 内存,2 vCPU |
| Elasticsearch | 8.19.19,自管,基础许可证 |
| Kibana | 8.19.19 |
| Elastic Agent | 8.19.19,独立模式(非 Fleet 管理) |
| 数据源 | 通过 System 集成获取的 `/var/log/auth.log` |
所有内容都在本地运行,零成本。没有云开销,没有付费层试用。自管基础许可证涵盖了 EQL 和 Security 应用程序。
## 检测:搭建技术栈
Elasticsearch 是一个 Java 应用程序,默认情况下,JVM 会为其堆分配大约一半的宿主机内存。在一台已经运行其他服务的 12 GB 虚拟机上,在索引单个文档之前,这就是一个问题。因此,第一个操作是关于内存的决策,而不是安装。
```
free -h
systemctl is-active splunk ollama
sudo systemctl stop ollama
```
我查看的是 **available** 列而不是 free 列,因为 available 反映了新进程实际可以声明的内存,包括可回收的缓存。由于有 9.6 GB 的可用内存,我明确限制了 Elasticsearch 的堆内存,而不是去调整虚拟机的大小:
```
echo -e "-Xms2g\n-Xmx2g" | sudo tee /etc/elasticsearch/jvm.options.d/heap.options
```
`-Xms` 是起始堆内存,`-Xmx` 是上限。将两者都设置为 2g 意味着它从一开始就受到限制并保持不变。运行中的服务总共稳定在 2.4 GB,包括堆和 JVM 开销。
安装和验证步骤位于 [SETUP.md](SETUP.md) 中。
现代 Elastic 默认启用了 TLS 和身份验证。Elasticsearch 和 Kibana 之间的注册令牌握手确实是第一小时最容易绊倒人的地方,我努力解决了这个问题,而不是为了节省时间而禁用安全性,因为这种摩擦代表了真实的生产环境。
## 分析:导入数据并验证映射
我使用带有 Elastic Agent 的 **System 集成** 并以独立模式运行,而不是使用 Fleet。Fleet 提供跨多个代理的集中管理;但对于单个虚拟机来说,它需要搭建并维护一个 Fleet Server,却没有任何收益。
关键细节在于 ECS。**ECS (Elastic Common Schema)** 是 Elastic 的标准化字段命名层,大致相当于 CIM 之于 Splunk。EQL 绝对依赖于它:如果文档中没有 `@timestamp` 和 `event.category`,每个 EQL 查询都会返回零命中,而且看起来完全就像是一个损坏的查询。System 集成会自动将身份验证事件映射到 ECS,这就是为什么相较于原始 Filebeat,它是正确的选择。
因此,在编写任何 EQL 之前,我直接验证了映射,而不是仅仅信任 UI:
```
curl -k -u elastic 'https://localhost:9200/_cat/indices/logs-*?v'
```
然后确认了真实 SSH 事件上的实际字段值:
```
"event": {
"action": "ssh_login",
"category": ["authentication"],
"outcome": "failure"
},
"source": { "ip": "127.0.0.1" },
"user": { "name": "sean" }
```
映射正确,`event.category`、`event.outcome`、`source.ip` 和 `user.name` 均已填充。这正是 EQL 序列所需的数据结构。
## 关联:EQL 序列
### 命名冲突,值得首先指出
**Kibana KQL 和 Kusto KQL 是毫不相关的语言,只是共用了同一个首字母缩写。** 我的背景是 Kusto (Microsoft Sentinel)。在 Elastic 体系中,“KQL” 指的是 Kibana Query Language:简单的 字段:值 过滤器,没有管道,没有聚合。混淆这两者是一个很容易犯且非常明显的错误。
| | Kusto KQL | Kibana KQL |
|---|---|---|
| 示例 | `SecurityEvent \| where EventID == 4625` | `event.category : "authentication"` |
| 聚合 | 是 | 否 |
| 运行于 | Sentinel Logs blade | Kibana Discover |
### EQL 实际运行的位置
不在 Discover 中。EQL 恰好在三个地方运行:Security 应用程序的 **Timelines, Correlation 标签页**;`eql` 类型的检测规则;或者 `_eql/search` API。将 EQL 粘贴到 Discover 中会产生看起来像语法错误的结果,但这其实是“用错了地方”的错误。
### 从 Kusto 和 SPL 的概念转变
Kusto 或 SPL 的暴力破解规则会进行**计数**:“一小时内来自同一来源的 10 次或更多失败。” 这是一个基于数量的阈值。
EQL 序列描述的是**顺序**:“这个事件,然后是那个事件,涉及同一个实体,发生在一个时间窗口内。”
### 练习 A:暴力破解随后取得成功
```
sequence by source.ip with maxspan=1h
[authentication where event.outcome == "failure"]
[authentication where event.outcome == "success"]
```
| 子句 | 功能 |
|---|---|
| `sequence by source.ip` | 连接键。按相同的操作者对事件进行分组。 |
| `with maxspan=1h` | 限定时间窗口。必须紧跟在 `sequence by` 之后,而不是放在查询末尾。 |
| `[authentication where ...]` | 每个带括号的块代表一个有序的阶段。 |
**结果:**在 `127.0.0.1` 上匹配到一个序列,22:44:42 UTC 的一次失败的 SSH 登录,随后在 22:44:48 UTC 是一次成功的登录。
这比数量阈值的信号更明确。它不会标记嘈杂的失败,而是标记那些随后发生了真实成功登录的失败。
为了在 Kusto 或 SPL 中表达相同的逻辑,你需要基于时间排序条件将表与自身进行自连接,或者运行两个查询并在第二遍中进行关联。EQL 用一条可读的语句即可完成。这正是值得掌握的强大能力。
所有查询都在 [queries/eql-queries.md](queries/eql-queries.md) 中。
## 强化:网络狩猎及其不足之处
练习 B 被定义为一次网络狩猎而不是检测,这意味着它需要包含一个假设、一个查询和一个结论。
**假设:**攻击者将密码猜测频率控制在数量阈值之下(例如每小时 10 次失败),从而对该规则不可见,但如果没有中间夹杂的成功登录,在更长的时间窗口内,它仍然会表现为针对同一账号的持续重复失败。
```
sequence by source.ip with maxspan=4h
[authentication where event.outcome == "failure"]
[authentication where event.outcome == "failure"]
[authentication where event.outcome == "failure"]
```
**结果:**匹配到 3 个序列。
**结论:排除。**所有匹配都是我自己的测试流量。三个序列中的两个将来自两个独立测试会话的失败拼接成了一个匹配,因为 EQL 只知道“该 IP 在四个小时内发生了三次失败”,这是事实,但并非攻击。
**这是非常有价值的发现。**单纯的“连续 N 次失败”序列是一个很弱的网络狩猎查询。它会对任何重复但无害的活动触发,包括密码输入错误。**EQL 序列编码的是顺序,而不是速率或密度。**严肃的“低频缓慢式”网络狩猎需要基于速率的信号,即不同时间桶中的失败次数,而不仅仅是一连串的事件。我宁愿诚实地记录下这个局限性,也不愿展示一个看起来能运行但其实有缺陷的查询。
## 验证:我的确认事项
| 标准 | 状态 |
|---|---|
| Elasticsearch 和 Kibana 运行中,独立启动/停止 | 已确认 |
| 正在摄取真实的身份验证数据,ECS 映射已验证 | 已通过 `_cat/indices` 和文档检查确认 |
| 在 Discover 中按 IP 和用户名进行 Kibana KQL 搜索 | 已确认 |
| 已编写并在 Elastic Security 中运行 EQL 序列 | 已确认,通过 API 和 Correlation 标签页 UI |
| Kibana KQL 与 Kusto KQL 的区别 | 已在上方记录 |
| Elastic 词汇可在交流中使用 | 见下文 |
## Elastic 到 Splunk 到 Sentinel 的映射
| Elastic | Splunk | Sentinel |
|---|---|---|
| Index | Index | Table |
| Document | Event | Row |
| Index pattern / data view | Index 或 sourcetype 范围 | Table 范围 |
| Elastic Agent | Universal Forwarder | Log Analytics agent / AMA |
| Discover | Search | Logs blade |
| ECS | CIM | 规范化的 Table schema |
| Detection rule | Correlation search | Analytics rule |
| Elastic Security | Enterprise Security | Sentinel workspace |
### 真正的区别,而不仅仅是重命名
1. **EQL 序列。**作为一流的语言特性支持有序的多阶段关联。
2. **映射模型。**Splunk 的 schema-on-read 容忍糟糕的字段提取。而 Elastic 的 EQL 会对糟糕的 ECS 映射进行隐式惩罚,结果是返回零数据且不报错。
3. **KQL 冲突。**在 Splunk 或 Sentinel 中没有类似的陷阱。
4. **设置摩擦。**默认即安全在开箱即用时更为繁重。
## 仓库内容
| 路径 | 内容 |
|---|---|
| `README.md` | 本说明文档 |
| `SETUP.md` | 包含完整安装和配置命令及其理由 |
| `TROUBLESHOOTING.md` | 构建期间遇到的四个真实失败案例及其诊断方法 |
| `queries/eql-queries.md` | 使用的所有 EQL、Kibana KQL 和 Query DSL 查询 |
| `screenshots/` | 编号的验证点截图 |
## 许可证
MIT。参见 [LICENSE](LICENSE)。
#ERSec
标签:Elastic Stack, EQL查询, 流量重放, 红队行动, 越狱测试