-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Security: Fix RCE via unsafe deserialization in Cosmos metadata cache #47971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3eba79e
da78f67
2fe22cb
0971089
0c9ee02
8b8cf06
ad3bed2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| // Licensed under the MIT License. | ||
| package com.azure.cosmos.implementation.caches; | ||
|
|
||
| import com.azure.cosmos.implementation.CosmosClientMetadataCachesSnapshot; | ||
| import com.azure.cosmos.implementation.DocumentCollection; | ||
| import com.azure.cosmos.implementation.apachecommons.lang.RandomStringUtils; | ||
| import com.azure.cosmos.implementation.directconnectivity.ReflectionUtils; | ||
|
|
@@ -116,6 +117,77 @@ private DocumentCollection generateDocumentCollectionDefinition() { | |
|
|
||
| return collection; | ||
| } | ||
|
|
||
| @Test(groups = { "unit" }) | ||
| public void deserializeWithInvalidClassType_shouldFail() throws Exception { | ||
| // Create a malicious payload with a different class type | ||
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
| ObjectOutputStream oos = new ObjectOutputStream(baos); | ||
|
|
||
| // Write a valid SerializableAsyncCollectionCache structure but with a malicious value | ||
| oos.writeInt(1); // size = 1 | ||
| oos.writeUTF("testKey"); // key | ||
|
|
||
| // Write a malicious object instead of SerializableDocumentCollection | ||
| oos.writeObject("MaliciousString"); | ||
|
|
||
| // Write the equality comparer | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests are failing as well please fix them c.a.c.i.c.SerializableAsyncCacheTest.deserializeWithInvalidClassType_shouldFail
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in commit ad3bed2. The tests were failing because the allowlist was incomplete - it only included the top-level class but |
||
| oos.writeObject((IEqualityComparer<DocumentCollection>) (v1, v2) -> v1 == v2); | ||
| oos.flush(); | ||
|
|
||
| byte[] maliciousBytes = baos.toByteArray(); | ||
|
|
||
| // Attempt to deserialize - should fail with InvalidClassException | ||
| try { | ||
| ByteArrayInputStream bais = new ByteArrayInputStream(maliciousBytes); | ||
| ObjectInputStream ois = new ObjectInputStream(bais); | ||
| SerializableAsyncCollectionCache cache = (SerializableAsyncCollectionCache) ois.readObject(); | ||
|
|
||
| // Should not reach here | ||
| org.testng.Assert.fail("Expected InvalidClassException to be thrown"); | ||
| } catch (java.io.InvalidClassException e) { | ||
| // Expected - the malicious class type was rejected | ||
| assertThat(e.getMessage()).contains("Expected SerializableDocumentCollection"); | ||
| } | ||
| } | ||
|
|
||
| @Test(groups = { "unit" }) | ||
| public void safeObjectInputStream_rejectsUnauthorizedClasses() throws Exception { | ||
| // Create a malicious payload with an unauthorized class | ||
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
| ObjectOutputStream oos = new ObjectOutputStream(baos); | ||
|
|
||
| // Write a malicious object (String instead of SerializableAsyncCollectionCache) | ||
| oos.writeObject("MaliciousPayload"); | ||
| oos.flush(); | ||
|
|
||
| byte[] maliciousBytes = baos.toByteArray(); | ||
|
|
||
| // Create a CosmosClientMetadataCachesSnapshot with the malicious payload | ||
| CosmosClientMetadataCachesSnapshot snapshot = new CosmosClientMetadataCachesSnapshot(); | ||
| snapshot.collectionInfoByNameCache = maliciousBytes; | ||
|
|
||
| // Attempt to deserialize - should fail with InvalidClassException | ||
| try { | ||
| AsyncCache<String, DocumentCollection> cache = snapshot.getCollectionInfoByNameCache(); | ||
|
|
||
| // Should not reach here | ||
| org.testng.Assert.fail("Expected exception to be thrown for unauthorized class"); | ||
| } catch (Exception e) { | ||
| // Expected - the unauthorized class was rejected | ||
| // The exception could be wrapped in a CosmosException, so check the cause chain | ||
| Throwable cause = e; | ||
| boolean foundInvalidClassException = false; | ||
| while (cause != null && !foundInvalidClassException) { | ||
| if (cause instanceof java.io.InvalidClassException) { | ||
| foundInvalidClassException = true; | ||
| assertThat(cause.getMessage()).contains("Unauthorized deserialization attempt"); | ||
| } | ||
| cause = cause.getCause(); | ||
| } | ||
| assertThat(foundInvalidClassException).as("Expected InvalidClassException in cause chain").isTrue(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -9,9 +9,10 @@ | |||||
| #### Bugs Fixed | ||||||
| * Fixed an issue where `query plan` failed with `400` or query return empty result when `CosmosQueryRequestOptions` has partition key filter and partition key value contains non-ascii character. See [PR 47881](https://github.com/Azure/azure-sdk-for-java/pull/47881) | ||||||
| * Fixed an issue where operation failed with `400` when configured with pre-trigger or post-trigger with non-ascii character. Only impact for gateway mode. See [PR 47881](https://github.com/Azure/azure-sdk-for-java/pull/47881) | ||||||
| * Fixed Remote Code Execution (RCE) vulnerability via unsafe Java deserialization in `CosmosClientMetadataCachesSnapshot`, `AsyncCache`, and `DocumentCollection`. Added `SafeObjectInputStream` with class allowlisting to prevent deserialization of unauthorized classes. See [PR 47971](https://github.com/Azure/azure-sdk-for-java/pull/47971) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| #### Other Changes | ||||||
| * Added `x-ms-hub-region-processing-only` header to allow hub-region stickiness when 404 `READ SESSION NOT AVAIALBLE` is hit for Single-Writer accounts. - [PR 47631](https://github.com/Azure/azure-sdk-for-java/pull/47631) | ||||||
| * Added `x-ms-hub-region-processing-only` header to allow hub-region stickiness when 404 `READ SESSION NOT AVAILABLE` is hit for Single-Writer accounts. - [PR 47631](https://github.com/Azure/azure-sdk-for-java/pull/47631) | ||||||
|
|
||||||
| ### 4.77.0 (2026-01-26) | ||||||
|
|
||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.