-
Type:
Improvement
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: 6.0.13
-
Component/s: None
-
Query Execution
Please excuse the duplication, but I no longer have permission to view the SERVER-100504 ticket. Therefore, I had to submit this clarification in a new ticket. On the original Jira ticket, I received an error: "You can't view this issue"
In MongoDB version 6.0.13-10, a user without permission to rename collections or execute the drop command is still able to overwrite a protected collection using the $out operator in an aggregation pipeline.
According to the "$out (aggregation)" documentation, the $out stage "calls the renameCollection command with dropTarget: true to rename the temporary collection to the destination collection." Therefore, if a user lacks permission for the rename or drop commands, they should not be able to overwrite a protected collection via an aggregation pipeline.
MongoDB version: 6.0.13-10
The following operations correctly prevent execution due to insufficient permissions on the app_test collection:
db.getCollection("app_test").renameCollection("app_test_2"); db.getCollection("app_test").renameCollection("app_test_2", { dropTarget: true }); db.getCollection("app_test").drop();
They all return (it is the except behavior):
MongoServerError[Unauthorized]: not authorized on admin to execute command...
However, the following aggregation is capable of overwriting the app_test collection:
db.getCollection("test").aggregate([ { $out: "app_test" } ]);
We need the ability to insert, update, and delete data in a specific collection (i.e., perform basic CRUD operations). Additionally, we require the capability to generate reports or temporary/helper collections using the aggregation framework, which we then materialize with the $out operator.
However, we want to prevent the accidental overwriting of an existing collection's data. For this reason, we created a custom role that allows for both CRUD operations and the use of the $out operator. Unfortunately, there was an incident where an operator unintentionally used $out and overwrote an existing collection, resulting in data loss.
Based on the $out documentation, under the "Replace Existing Collection" section, the following command is executed internally:
https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/out/
db.getCollection("tmp.agg_out.ce468f99-a4fa-4a6a-ad5a-5cc04f481979").renameCollection("app_test", { dropTarget: true });
This command renames the temporary collection to "app_test" and drops the existing "app_test" collection if it exists.
However, I do not have permission to drop the "app_test" collection.
Based on my understanding, I am able to perform an operation that I don't have permission for, and as a result, I could inadvertently cause data loss using the `$out` operator. a következőhöz: pár másodperc
Based on my understanding, I can execute an operation without having the required permissions, which can inadvertently lead to data loss using the $out operator.
Below is the code that reproduces the issue:
use mydb; // Create test data if (!db.getCollectionNames().includes("app_test")) { db.createCollection("app_test"); db.app_test.insertMany([{ name: "item1" }, { name: "item2" }]); } if (!db.getCollectionNames().includes("test")) { db.createCollection("test"); db.test.insertMany([{ name: "other1" }, { name: "other2" }]); } // Create a custom role db.createRole( { role: "customReadWriteRole", privileges: [ { resource: { db: "mydb", collection: "" }, actions: [ "createCollection", //"dropCollection", --> dropCollection not allowed by default "find", "insert", "listCollections", "remove", //"renameCollectionSameDB", --> renameCollectionSameDB not allowed by default "update", ] }, ], roles: [] }, { w: "majority", wtimeout: 5000 } ); // Create a user with custom role db.createUser({ user: "limitedUser", pwd: "password", roles: [{ role: "customReadWriteRole", db: "mydb" }] });
Login with limited user
mongosh -u limitedUser -p password --authenticationDatabase mydb
// Use my test database use mydb; // Try to drop or rename a collection, I want to see an error (and I got an error, so it is okay) db.getCollection("app_test").renameCollection("app_test_2"); db.getCollection("app_test").renameCollection("app_test_2", { dropTarget: true }); db.getCollection("app_test").drop(); // I try an aggregate, I want to see an error because I do not have the right to drop or rename a collection // But I do not see an error, I can replace the original app_test collection with the result of the aggregate db.getCollection("test").aggregate([ { $out: "app_test" } ]);
Impact:
A user can inadvertently execute an aggregation pipeline with $out that drops an existing collection—even though they do not have explicit permission to perform such operations—leading to potential data loss.
Conclusion:
The $out stage should enforce the same authorization checks as the underlying renameCollection and drop commands. The current behavior allows privilege escalation, as the internal operation bypasses user permissions, which is a security risk.