-
Type:
Task
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: 2.13.0
-
Component/s: None
-
None
-
0.1
-
Fully Compatible
-
Dotnet Drivers
-
Not Needed
-
None
-
None
-
None
-
None
-
None
-
None
Summary
A user who controls the field names in a replaced document can smuggle update operators, silently changing what the application writes to the database.
Impact
The server executes attacker-chosen update operators ($set/$unset/$rename/$inc/...) against the matched document instead of storing a literal replacement, silently corrupting records and enabling manipulation of application-managed fields (e.g. preserving privileges a replace was meant to wipe, or setting nested fields the application never exposed).
Severity: high
Exploitability: exploitable — Any caller-supplied replacement whose first top-level key starts with '$' is serialized and executed unmodified; the attacker needs only to feed such a key to an application that passes untrusted field names to FindOneAndReplace, a common pattern with BsonDocument/dynamic models.
Customer data: customer_content (none)
Location
- src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs:133 in GetCommandValidator
- src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs:114 in CreateCommand
- src/MongoDB.Driver/MongoCollectionImpl.cs:1057 in CreateFindOneAndReplaceOperation
- src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs:180 in CreateOperation
- src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs:42 in CommandUsingCommandMessageWireProtocol (field _commandValidator)
Reproduction / trigger path
Analyzed trigger path from static analysis — not an executed PoC. Confirm with a concrete repro before handing off.
Attacker controls: Attacker controls the top-level field names and values of the replacement document passed to IMongoCollection.FindOneAndReplace; a first key such as "$set" selects operator-update semantics, after which every top-level key is an attacker-chosen update operator with dotted-path targets.
- (source) src/MongoDB.Driver/MongoCollectionImpl.cs:463 in FindOneAndReplace — Public driver API accepts the caller-supplied replacement with only a null check — no content or element-name validation.
- (hop) src/MongoDB.Driver/MongoCollectionImpl.cs:1057 in CreateFindOneAndReplaceOperation — Replacement is wrapped raw in a BsonDocumentWrapper; serialization (and thus any validation opportunity) is deferred to wire time.
- (hop) src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs:114 in CreateCommand — Raw replacement becomes the 'update' field of the findAndModify command — the field whose first key selects replace vs operator semantics on the server.
- (hop) src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs:133 in GetCommandValidator — Returns NoOpElementNameValidator.Instance instead of the replacement validator the driver uses on every other replace path.
- (hop) src/MongoDB.Driver/Core/Operations/FindAndModifyOperationBase.cs:180 in CreateOperation — The no-op validator is installed as the WriteCommandOperation's CommandValidator — the only wire-level element-name gate for this command.
- (sink) src/MongoDB.Driver/Core/WireProtocol/CommandUsingCommandMessageWireProtocol.cs:42 in CommandUsingCommandMessageWireProtocol — On OP_MSG the command validator is stored but never applied (in-code TODO); the command is serialized unvalidated and the server interprets the '$'-prefixed first key as update operators.
Root cause
- FindOneAndReplaceOperation.GetCommandValidator returns NoOpElementNameValidator.Instance (src/MongoDB.Driver/Core/Operations/FindOneAndReplaceOperation.cs:133), so no element name in the command — including the replacement — is ever validated; the constructor only null-checks the replacement (line 44).
- The raw replacement is embedded as the findAndModify 'update' field (FindOneAndReplaceOperation.cs:114), the field whose first key the server inspects to choose replacement vs operator-update semantics.
- The no-op is installed as the sole wire-level gate (FindAndModifyOperationBase.cs:180), and on the modern OP_MSG protocol even that validator is never invoked (CommandUsingCommandMessageWireProtocol.cs:42 stores it with a TODO and never uses it).
- The driver's own convention proves the gap: bulk/ReplaceOne pushes ReplacementElementNameValidator, which rejects top-level names starting with '$' (RetryableUpdateCommandOperation.cs:162, ReplacementElementNameValidator.cs:52), and FindOneAndUpdate routes 'update' through UpdateElementNameValidator (FindOneAndUpdateOperation.cs:183) — findOneAndReplace is the one replace path with no validation anywhere.
Ownership
Assigned teams: @mongodb/dbx-csharp-dotnet
Filed from Aegis finding 6fc65c50212c (scan scan-2f1d896ebfb5) · primitive: Attacker-controlled first field name flips a literal document replacement into server-executed update operators because the driver skips replacement element-name validation. · categories: NoSQL Injection, Data Corruption