Elasticsearch集群取证
当我们成功仿真出 Elasticsearch 集群节点后,往往需要先把集群恢复到可用状态,才能通过 REST API 快速提取索引、用户、日志等关键信息。Elasticsearch 的几乎所有元数据都能通过 API 查询,因此恢复集群运行 > 直接翻数据目录。
Elasticsearch 基础知识
什么是 Elasticsearch
Elasticsearch 是基于 Apache Lucene 的分布式搜索引擎,由多个节点(Node)组成集群(Cluster),数据以索引(Index) 组织,索引被切分为分片(Shard),分片分散存储在各数据节点上,每个分片分为主分片(Primary)与副本分片(Replica)。节点按角色分工:
master—— 管理集群状态(节点加入/移除、索引创建、分片分配),master-eligible 节点间选举出 active masterdata—— 存储分片数据、执行检索/写入(可细分data_hot/data_warm/data_cold/data_frozen)ingest—— 执行 Ingest Pipeline 数据预处理coordinating—— 不配置任何角色即纯协调节点,转发/汇聚请求voting_only—— 仅参与 master 选举投票,不做 masterremote_cluster_client—— 跨集群搜索/复制的默认客户端角色
集群状态(Cluster State)由 active master 维护并持久化到所有 master-eligible 节点的数据目录 _state/ 中;分片数据以 Lucene 段文件形式落盘。
取证要点速览
| 类别 | 关键位置 |
|---|---|
| REST API | http://<IP>:9200(默认),_cat/_cluster/_security 系列接口 |
| 节点间通信 | TCP 9300(transport 层) |
| 主配置文件 | /etc/elasticsearch/elasticsearch.yml |
| 配置目录 | /etc/elasticsearch/(jvm.options、log4j2.properties、certs、users 等) |
| 程序目录 | /usr/share/elasticsearch/(bin、lib、plugins、modules) |
| 数据目录 | /var/lib/elasticsearch/(默认,path.data 指定) |
| 日志目录 | /var/log/elasticsearch/(<cluster_name>.log、slowlog、audit、deprecation、gc) |
| 内置工具 | /usr/share/elasticsearch/bin/elasticsearch-*(users、keystore、certutil、reset-password…) |
| 安全文件(native realm) | /etc/elasticsearch/users、/etc/elasticsearch/users_roles、/etc/elasticsearch/role_mapping.yml |
| 安全存储 | /etc/elasticsearch/elasticsearch.keystore(加密敏感配置) |
| 集群状态持久化 | <path.data>/nodes/0/_state/(cluster state、索引元数据) |
| 快照数据 | path.repo 指定的共享文件系统目录(fs 仓库) |
| systemd 单元 | systemctl status elasticsearch |
离线取证技巧:无法启动集群怎么办
若集群起不来(证书损坏、quorum 不满足、数据目录缺失),数据与元数据仍可离线提取:
# 集群元数据(索引 mapping、settings、模板)
ls /var/lib/elasticsearch/nodes/0/_state/ # cluster state / meta state 文件(.st 二进制 + manifest)
# 索引数据按 UUID 组织
ls /var/lib/elasticsearch/nodes/0/indices/ # 每个子目录名即索引 UUID
cat /var/lib/elasticsearch/nodes/0/indices/<UUID>/_state/state-*.st # 含索引名(strings 可见)
注意:Lucene 段文件是二进制格式,离线直接解析代价高,优先想办法把节点拉起来用 API 导出。单节点场景可用 discovery.type: single-node 绕过选举问题。
仿真环境网络配置
启动网卡
仿真出的节点默认网卡可能未启用:
ip link set ens33 up
dhclient ens33
修改网络绑定
Elasticsearch 默认只监听 127.0.0.1,集群仿真需改为对外可达地址。编辑各节点 /etc/elasticsearch/elasticsearch.yml:
# 对外监听地址(仿真机 IP 或 0.0.0.0)
network.host: 192.168.170.50
http.port: 9200
transport.port: 9300
# 集群名必须与现场一致,否则节点不会加入原集群
cluster.name: <原集群名>
# 节点名与角色(保持与原配置一致)
node.name: node-1
node.roles: [ master, data, ingest ]
集群发现配置
# master-eligible 节点互相发现(填写各 master 节点 IP)
discovery.seed_hosts: ["192.168.170.50", "192.168.170.51", "192.168.170.52"]
# 全新集群首次引导(仅在所有 master 节点全新启动时使用一次,之后删除或注释)
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
注意:cluster.initial_master_nodes 只在集群首次启动(数据目录为空)时有效。仿真恢复已有数据的节点时该配置会被忽略,切勿在已初始化的集群上反复修改,否则可能产生脑裂。只有单节点数据恢复可用 discovery.type: single-node 快速拉起。
启动服务并验证
systemctl start elasticsearch
systemctl status elasticsearch
journalctl -u elasticsearch -e --no-pager # 启动报错全在这里
curl http://127.0.0.1:9200/_cluster/health?pretty # 未开安全时
curl -k https://127.0.0.1:9200/_cluster/health?pretty -u elastic:<密码> # 开了 xpack 安全时
时钟偏差
Elasticsearch 的 master 选举、快照时间戳、ILM 生命周期策略、审计日志都对时钟敏感,仿真节点时间不一致会导致集群反复选主或分片恢复异常,需先统一时间:
#1. 关闭自动同步(防止 chrony/NTP 与手动设置打架)
timedatectl set-ntp false
systemctl stop chrony && systemctl disable chrony
#2.手动统一时间(在每台节点上执行,时间保持一致)
timedatectl set-timezone Asia/Shanghai
timedatectl set-time "2024-01-01 12:00:00"
hwclock --systohc # 同步写入硬件时钟 RTC
#3.确认时间一致(逐台执行比对)
date
timedatectl
#4.重启服务(时间一致后重启恢复集群)
systemctl restart elasticsearch
系统取证
基本系统信息
版本号
curl -s http://127.0.0.1:9200/ | grep version # 在线:ES 版本、Lucene 版本、构建哈希
cat /usr/share/elasticsearch/lib/elasticsearch-*.jar # 离线:jar 文件名即版本
dpkg -l | grep elasticsearch # deb 包版本(deb 安装时)
rpm -qa | grep elasticsearch # rpm 包版本(rpm 安装时)
_nodes 接口还能看到每个节点精确的版本与 JVM 信息:
curl -s http://127.0.0.1:9200/_nodes?pretty | grep -E '"name"|"version"'
主机内核版本
uname -a
主机名与节点名
hostnamectl
cat /etc/hostname
cat /etc/hosts # 集群各节点的 IP/主机名映射常记录在此
grep node.name /etc/elasticsearch/elasticsearch.yml
时间服务器地址
cat /etc/chrony/chrony.conf # server/pool 行即时间源(7 及以后多为 chrony)
cat /etc/ntp.conf # 老系统可能是 ntpd
chronyc sources # 在线状态查看
timedatectl # 时区与时间同步状态
集群信息
集群健康与状态
curl -s http://127.0.0.1:9200/_cluster/health?pretty
关注字段:
cluster_name—— 集群名status——green(全部分片就位)/yellow(副本缺失,数据可读)/red(主分片缺失,数据已丢)number_of_nodes/number_of_data_nodes—— 节点数active_primary_shards/active_shards—— 在线分片数unassigned_shards—— 未分配分片数(>0 说明有分片恢复失败)
节点列表与角色
curl -s http://127.0.0.1:9200/_cat/nodes?v&h=name,ip,node.role,master,heap.percent,ram.percent,disk.used_percent
curl -s http://127.0.0.1:9200/_nodes?pretty # 每个节点的完整信息:角色、JVM、插件、处理器、绑定 IP
curl -s http://127.0.0.1:9200/_cat/master?v # 当前 active master
node.role 列含义:m=master-eligible,d=data,i=ingest,l=machine learning,r=remote_cluster_client,v=voting_only,-=协调节点。
集群设置与元数据
curl -s http://127.0.0.1:9200/_cluster/settings?include_defaults=true&pretty # 生效的全部集群级配置
curl -s http://127.0.0.1:9200/_cluster/state/metadata?pretty # 索引/模板/ILM 元数据总览
curl -s http://127.0.0.1:9200/_cat/plugins?v # 各节点安装的插件(含第三方插件)
curl -s http://127.0.0.1:9200/_cat/tasks?v # 当前正在执行的集群任务
主配置文件
cat /etc/elasticsearch/elasticsearch.yml
取证关注点:
cluster.name、node.name、node.roles—— 集群拓扑与角色network.host、discovery.seed_hosts—— 集群成员 IPpath.data、path.logs—— 数据与日志实际位置(可能被改到非默认路径)path.repo—— 快照仓库允许路径xpack.security.*—— 安全功能开关、TLS 证书路径、审计开关- 异常的配置项(如指向陌生 IP 的
network.host、被关闭的安全功能)往往是攻击者改动的痕迹
索引与数据取证
索引清单
curl -s http://127.0.0.1:9200/_cat/indices?v&h=index,health,status,pri,rep,docs.count,store.size,creation.date.string
docs.count—— 文档数(yellow/red索引该值可能不完整)creation.date.string—— 索引创建时间,可用于还原数据写入时间线.开头的是系统索引(.security用户数据、.kibana*、.tasks、.ds-*数据流后端索引)
curl -s http://127.0.0.1:9200/<索引名>/_mapping?pretty # 索引字段结构
curl -s http://127.0.0.1:9200/<索引名>/_settings?pretty # 分片数、副本数、slowlog 阈值、ILM 策略
curl -s http://127.0.0.1:9200/<索引名>/_count # 文档计数
curl -s http://127.0.0.1:9200/_cat/aliases?v # 别名(数据流/读写别名)
curl -s http://127.0.0.1:9200/_index_template?pretty # 索引模板
分片分配状态
curl -s http://127.0.0.1:9200/_cat/shards?v&h=index,shard,prirep,state,docs,store,node
关注 UNASSIGNED 与 RELOCATING 的分片:
curl -s http://127.0.0.1:9200/_cat/allocation?v # 各节点磁盘水位
curl -s http://127.0.0.1:9200/_cat/recovery?v # 正在进行的分片恢复
curl -s -XPOST 'http://127.0.0.1:9200/_cluster/allocation/explain?pretty' -H 'Content-Type: application/json' -d '{
"index": "<索引名>", "shard": 0, "primary": true
}' # 分片未分配的具体原因(磁盘水位/副本不足/数据损坏)
磁盘水位(默认 cluster.routing.allocation.disk.watermark.*)触发时分片会被锁定,elasticsearch.yml 或 _cluster/settings 中可查到。
导出索引数据
方法一:elasticdump(推荐,直观)
需要 Node.js 环境,导出为 JSON 便于离线分析:
npm install elasticdump -g
elasticdump --input=http://127.0.0.1:9200/<索引名> --output=/导出目录/<索引名>.mapping.json --type=mapping # 先导 mapping
elasticdump --input=http://127.0.0.1:9200/<索引名> --output=/导出目录/<索引名>.data.json --type=data --limit=5000 # 再导数据
# 开了安全认证:
elasticdump --input=http://elastic:<密码>@127.0.0.1:9200/<索引名> --output=/导出目录/<索引名>.data.json --type=data
大索引用 --limit 分片导出避免 OOM;导出文件为 NDJSON,可直接 grep/jq 分析。
方法二:scroll 原生导出
不装任何工具,纯 curl 完成:
# 第一批(scroll=1m 表示快照游标存活 1 分钟)
curl -s 'http://127.0.0.1:9200/<索引名>/_search?scroll=1m&pretty' -H 'Content-Type: application/json' -d '{
"size": 5000,
"query": { "match_all": {} }
}' > /导出目录/batch_1.json
# 后续批次(用上一批返回的 _scroll_id)
curl -s 'http://127.0.0.1:9200/_search/scroll' -H 'Content-Type: application/json' -d '{
"scroll": "1m",
"scroll_id": "<上一批返回的_scroll_id>"
}' > /导出目录/batch_2.json
scroll 是对时间点的快照式读取,适合取证(导出期间新写入的数据不会混进来)。
方法三:快照导出(最完整,保真度最高)
快照保留 Lucene 原始文件,可跨集群完整还原:
#1. 注册 fs 类型仓库(路径必须在 elasticsearch.yml 的 path.repo 中声明,改完需重启节点)
# elasticsearch.yml 增加: path.repo: ["/var/lib/elasticsearch/snapshots"]
curl -s -XPUT 'http://127.0.0.1:9200/_snapshot/my_repo?pretty' -H 'Content-Type: application/json' -d '{
"type": "fs",
"settings": { "location": "/var/lib/elasticsearch/snapshots", "compress": true }
}'
#2. 创建快照(全部索引)
curl -s -XPUT 'http://127.0.0.1:9200/_snapshot/my_repo/snapshot_1?wait_for_completion=true&pretty'
# 只快照指定索引: body 加 "indices": "index1,index2"
#3. 查看快照
curl -s 'http://127.0.0.1:9200/_snapshot/_all?pretty' # 全部仓库
curl -s 'http://127.0.0.1:9200/_snapshot/my_repo/_all?pretty' # 仓库内快照及起止时间
curl -s 'http://127.0.0.1:9200/_snapshot/my_repo/snapshot_1?pretty' # 快照详情(索引列表、分片、耗时)
#4. 打包带走
tar -czvf /导出目录/es_snapshot.tar.gz -C /var/lib/elasticsearch/snapshots .
sha256sum /导出目录/es_snapshot.tar.gz | tee es_snapshot.sha256 # 完整性校验
还原到分析环境:在新集群同样注册该目录为仓库后 POST /_snapshot/my_repo/snapshot_1/_restore。
注意:若现场 elasticsearch.yml 没有 path.repo,需要先添加并滚动重启节点才能注册 fs 仓库;也可用 S3/HDFS 等仓库类型(需插件)。
存储取证
数据目录结构
<path.data>(默认 /var/lib/elasticsearch/)布局:
/var/lib/elasticsearch/
├── node.lock # 节点运行锁(运行时存在)
├── nodes/0/
│ ├── _state/ # 集群级元数据:cluster state、索引元数据(.st 二进制 + manifest)
│ ├── node.lock
│ └── indices/
│ └── <索引UUID>/
│ ├── _state/state-*.st # 索引级元数据(索引名可读)
│ └── 0/ # 分片号
│ ├── index/ # Lucene 段文件(.cfs/.cfe/.si/_*.liv 等,真实数据)
│ ├── translog/ # 未刷盘的事务日志(最近的写入)
│ └── _state/ # 分片元数据(分配信息、是否主分片)
索引名 → UUID 的映射可通过 _cat/indices 的 uuid 列获取,也可直接 strings state-*.st 查看:
curl -s http://127.0.0.1:9200/_cat/indices?v&h=index,uuid,docs.count
strings /var/lib/elasticsearch/nodes/0/indices/<UUID>/_state/state-*.st | less
磁盘占用
curl -s http://127.0.0.1:9200/_cat/indices?v&h=index,store.size,pri.store.size&s=store.size:desc # 各索引占用
curl -s http://127.0.0.1:9200/_cat/allocation?v # 各节点分片数与磁盘
du -sh /var/lib/elasticsearch/nodes/0/indices/* # 离线核对
快照仓库
curl -s http://127.0.0.1:9200/_snapshot/_all?pretty # 注册的仓库、类型、位置
grep path.repo /etc/elasticsearch/elasticsearch.yml # 本地允许的仓库路径
ls /var/lib/elasticsearch/snapshots/ # fs 仓库内容(index-* 元数据 + 分片数据目录)
fs 仓库目录中的 index-0、index.latest 是快照清单,indices/<索引名>/<分片>/ 下是增量块文件,可直接打包带走异地还原。
用户与权限取证
安全功能状态
grep xpack.security /etc/elasticsearch/elasticsearch.yml
# xpack.security.enabled: false → 无认证,谁都能读写(常见于被入侵或配置失误现场)
# 该配置缺失且版本为 8.x → 默认开启安全
用户列表
curl -s -u elastic:<密码> http://127.0.0.1:9200/_security/user?pretty # 全部 native 用户、角色、邮箱、启用状态
curl -s -u elastic:<密码> http://127.0.0.1:9200/_security/role?pretty # 自定义角色与权限(cluster/indices 权限点)
curl -s -u elastic:<密码> http://127.0.0.1:9200/_security/role_mapping?pretty # 外部用户/组的映射规则
curl -s -u elastic:<密码> http://127.0.0.1:9200/_security/api_key?pretty # 现存 API Key
curl -s -u elastic:<密码> http://127.0.0.1:9200/_security/service/_all/credential?pretty # Service Account Token
重点排查:可疑的新建用户、superuser 角色分配、来源不明的 API Key(常用于持久化)。
离线:native realm 密码文件
8.x 默认把 native 用户存在 .security-7 系统索引里,但 file realm 仍是文件形式:
cat /etc/elasticsearch/users # 格式 user:bcrypt哈希,可交给 hashcat(-m 3200)破解
cat /etc/elasticsearch/users_roles # 用户-角色映射
cat /etc/elasticsearch/role_mapping.yml
命令行用户工具
/usr/share/elasticsearch/bin/elasticsearch-users list # file realm 用户列表
/usr/share/elasticsearch/bin/elasticsearch-users useradd admin2 -p xxx -r superuser # 应急添加管理员
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic # 重置 elastic 内置用户密码
/usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -a # 自动生成强密码
/usr/share/elasticsearch/bin/elasticsearch-keystore list # keystore 中的敏感配置键名
证书
ls /etc/elasticsearch/certs/ # 常见 elastic-certificates.p12、http.p12、transport.p12、ca.crt
openssl pkcs12 -info -in /etc/elasticsearch/certs/http.p12 -nokeys # 查看证书主题/有效期
/usr/share/elasticsearch/bin/elasticsearch-certutil cert --ca ... # 需重签时使用
TLS 证书泄露(p12 + 密码)意味着 transport 层可被伪造节点接入,需一并取证带走。
日志取证
日志目录默认 /var/log/elasticsearch/,实际位置以 elasticsearch.yml 的 path.logs 为准。
主日志(重点)
ls /var/log/elasticsearch/
# <集群名>.log —— 主日志:启动、选举、分片分配、异常堆栈
# <集群名>.json —— JSON 格式主日志(8.x 默认)
# <集群名>_deprecation.log/json —— 弃用功能告警
# gc.log / gc.log.* —— JVM GC 日志
cat /var/log/elasticsearch/<集群名>.log
journalctl -u elasticsearch --since "2024-01-01" --no-pager
主日志可还原:节点加入/退出集群时间、master 选举变更(elected-as-master)、分片恢复失败原因、OOM/磁盘满事件、非法请求报错。
慢查询日志
按索引生成,记录执行超过阈值的搜索/写入,是发现数据窃取(大批量导出)的重要线索:
ls /var/log/elasticsearch/*slowlog*
# <集群名>_index_search_slowlog.log/json —— 慢查询(含具体查询语句)
# <集群名>_index_indexing_slowlog.log/json —— 慢写入
阈值由索引设置控制(可在 _settings 中看到):
curl -s http://127.0.0.1:9200/<索引名>/_settings?pretty | grep -A20 slowlog
# index.search.slowlog.threshold.query.warn / index.indexing.slowlog.threshold.index.warn …
# "-1" 表示关闭
安全审计日志
需 xpack.security.audit.enabled: true(配置在 elasticsearch.yml),默认输出到 <集群名>_audit.json:
cat /var/log/elasticsearch/<集群名>_audit.json | head
关键事件(event.action 字段):
authentication_success/authentication_failed—— 登录成功/失败(含来源 IP、用户名,可查爆破)access_denied—— 越权访问尝试run_as_granted/run_as_denied—— 代理身份切换security_config_change—— 用户/角色被增删改(攻击者留后门的直接证据)tampered_request—— 请求被篡改
审计事件类型由 xpack.security.audit.logfile.events.include/exclude 控制,取证时确认该配置是否被攻击者关闭过。
HTTP 访问层
Elasticsearch 本身没有 access log,HTTP 请求日志需前置代理(Nginx)或 audit log 的 access_granted 事件。若现场部署了 Kibana,其日志(/var/log/kibana/ 或 journalctl)与 Nginx access.log 可补充访问来源。
系统日志
journalctl -u elasticsearch -e --no-pager
cat /var/log/auth.log # SSH 登录(谁登上了节点)
cat /var/log/syslog