NoSQL injection via array replacement bypassing update shape validation in driver write path (port to main branch)

XMLWordPrintableJSON

    • None
    • 0.2
    • Dotnet Drivers
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      Anyone whose data an app passes as a replacement document can smuggle database update operators past the driver's injection guard and run them as a pipeline.

      Impact

      An attacker-supplied replacement value is executed by MongoDB as an aggregation-pipeline update with the application's database credentials, enabling reading of hidden document fields into visible ones and persistent, unintended modification of stored documents; on servers with server-side scripting enabled the pipeline expressions extend to $function JavaScript execution.
      Severity: high (upper bound critical)
      Exploitability: likely_exploitable — The driver-side path is unconditionally open and fully attacker-controlled; exploitation depends only on the common application pattern of passing deserialized user data as a loosely-typed replacement value.
      Customer data: customer_content, customer_metadata (api_response)

      Location

      • src/MongoDB.Driver/Core/Operations/UpdateRequest.cs:53 in EnsureUpdateIsValid
      • src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs:166 in SerializeUpdate
      • src/MongoDB.Driver/Core/WireProtocol/Messages/Encoders/BinaryEncoders/ClientBulkWriteOpsSectionFormatter.cs:327 in WriteUpdate
      • src/MongoDB.Driver/Core/Operations/ElementNameValidators/ReplacementElementNameValidator.cs:45 in GetValidatorForChildContent
      • src/MongoDB.Driver/MongoCollectionImpl.cs:706 in ConvertWriteModelToWriteRequest
      • src/MongoDB.Driver/WriteModel.cs:109 in ConvertToReplaceOne

      Reproduction / trigger path

      Analyzed trigger path from static analysis — not an executed PoC. Confirm with a concrete repro before handing off.

      Attacker controls: Full control of the BSON value serialized as the wire update field: an array of arbitrary pipeline stages ($set, $unset, $replaceWith) whose expressions can reference existing document fields, or a scalar that breaks the driver's error reporting.

      1. (source) src/MongoDB.Driver/MongoCollectionBase.cs:563 in ReplaceOne — Public replace API accepts any TDocument replacement; with a loosely-typed collection an attacker-controlled BsonArray or scalar enters here (ReplaceOneModel.cs:45 performs no validation).
      2. (hop) src/MongoDB.Driver/MongoCollectionImpl.cs:706 in ConvertWriteModelToWriteRequest — Builds UpdateRequest(UpdateType.Replacement, ...) wrapping the replacement in BsonDocumentWrapper with the collection's document serializer, deferring serialization to wire time.
      3. (hop) src/MongoDB.Driver/Core/Operations/UpdateRequest.cs:53 in EnsureUpdateIsValid — Shape validation is skipped entirely because updateType is Replacement; the array/scalar is stored as-is (line 82).
      4. (hop) src/MongoDB.Bson/Serialization/Serializers/BsonDocumentWrapperSerializer.cs:76 in SerializeValue — Wrapper delegates to the wrapped serializer, which emits the attacker's actual BSON type (array or scalar) as the update value.
      5. (hop) src/MongoDB.Driver/Core/Operations/ElementNameValidators/ReplacementElementNameValidator.cs:45 in GetValidatorForChildContent — The only defense validates top-level element names; child content gets NoOpElementNameValidator, so array indexes pass the '$' check and pipeline-stage operators are unvalidated.
      6. (sink) src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs:166 in SerializeUpdate — request.Update is serialized verbatim as the wire 'u' field; the MongoDB server interprets an array 'u' as an aggregation-pipeline update and executes the attacker's operators with the application's credentials.

      Root cause

      • src/MongoDB.Driver/Core/Operations/UpdateRequest.cs:53 — EnsureUpdateIsValid gates all shape validation on updateType == UpdateType.Update; for Replacement (and Unknown) any BsonValue, including a BsonArray or scalar, is returned as-is at line 82.
      • src/MongoDB.Driver/MongoCollectionImpl.cs:706-709 — the only Replacement producer wraps the caller's replacement in BsonDocumentWrapper, and src/MongoDB.Bson/Serialization/Serializers/BsonDocumentWrapperSerializer.cs:76 delegates serialization to the wrapped serializer, so a loosely-typed value emits an array/scalar at the wire.
      • src/MongoDB.Driver/Core/Operations/RetryableUpdateCommandOperation.cs:162-166 — the sink serializes request.Update verbatim as 'u'; the pushed ReplacementElementNameValidator checks only element names, and src/MongoDB.Driver/Core/Operations/ElementNameValidators/ReplacementElementNameValidator.cs:45 returns NoOp for child content, so array index names ('0','1') pass the '$' check and the '$'-operators inside pipeline stages are never inspected. The server interprets an array 'u' as an aggregation-pipeline update. The client bulk write path (ClientBulkWriteOpsSectionFormatter.cs:164, 321-327) has the identical hole with no UpdateRequest check at all, as does FindOneAndReplace (MongoCollectionImpl.cs:1057).
      • src/MongoDB.Driver/WriteModel.cs:123-137 and :109 — during bulk-write error/result conversion (MongoBulkWriteException.cs:218), a scalar update is misclassified as a replacement and the hard cast/wrapper materialization throws, crashing error reporting.

      Ownership

      Assigned teams: @mongodb/dbx-csharp-dotnet


      Filed from Aegis finding 136e5c4c62b9 (scan scan-2f1d896ebfb5) · primitive: Attacker-supplied array declared as a replacement document is serialized unvalidated as the wire update field and executed as an aggregation-pipeline update. · categories: NoSQL Injection, Data Corruption, Crash

      https://docs.google.com/document/d/1osPjSiI-pvMXtrI49QVqwrIK21_nU38fsp7tJlQpDlY/edit?tab=t.0#heading=h.biijq6ke6z0p

            Assignee:
            Damien Guard
            Reporter:
            Oleksandr Poliakov
            None
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: