NoSQL injection via unescaped dollar-prefixed values in LINQ constant arrays and documents (port to main branch)

XMLWordPrintableJSON

    • None
    • 0.1
    • Dotnet Drivers
    • None
    • None
    • None
    • None
    • None
    • None

      Summary

      Strings a user sneaks into a list or dictionary used in a LINQ query become live database expressions, letting them read hidden fields.

      Impact

      Unauthenticated application users can read database fields the application never exposes (including credential material and other users' data) and inject aggregation operators that alter query semantics, executed with the application's database privileges.
      Severity: critical
      Exploitability: exploitable — The attacker fully controls string elements and dictionary keys inside the constant; common patterns (multi-select filters via Contains in a Select/GroupBy or an $expr-fallback Where, projected lists, dictionary constants) place them in aggregation-expression contexts where the server evaluates them.
      Customer data: credentials, pii, customer_content (api_response)

      Location

      • src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstConstantExpression.cs:40 in AstConstantExpression.Render
      • src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConstantExpressionToAggregationExpressionTranslator.cs:35 in ConstantExpressionToAggregationExpressionTranslator.Translate
      • src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstNaryExpression.cs:64 in AstNaryExpression.Render
      • src/MongoDB.Driver/Linq/Linq3Implementation/Ast/AstComputedField.cs:50 in AstComputedField.RenderAsElement
      • src/MongoDB.Driver/Linq/Linq3Implementation/Misc/ProjectionHelper.cs:151 in ProjectionHelper.QuoteIfNecessary.ValueNeedsToBeQuoted
      • src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExecutableQuery.cs:118 in ExecutableQuery.Execute

      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 nested string values and document/dictionary key names inside any LINQ constant collection, dictionary, or document built from application input; each '$'-prefixed string becomes a live field-path reference and each '$'-prefixed key a live aggregation operator on the server.

      1. (source) src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConstantExpressionToAggregationExpressionTranslator.cs:35 in Translate — Application-held constant containing attacker-supplied strings (list/dictionary/document) is serialized to an arbitrary BsonValue and wrapped in AstExpression.Constant with no inspection of nested content.
      2. (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstConstantExpression.cs:45 in Render — Escaping guard at line 40 matches only a top-level BsonString starting with '$'; arrays and documents with '$'-prefixed nested strings or keys are returned raw with no $literal wrapper.
      3. (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstNaryExpression.cs:64 in Render — Raw rendered constant embedded as an operator argument (e.g. the source array of an aggregation $in built at ContainsMethodToAggregationExpressionTranslator.cs:50) — a context where array elements are evaluated as expressions.
      4. (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ExpressionToFilterTranslator.cs:46 in Translate — Where predicates the query language cannot express fall back to $expr, pulling constant collections into aggregation-expression evaluation even in plain filters.
      5. (sink) src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToExecutableQueryTranslators/ExecutableQuery.cs:118 in Execute — Rendered pipeline dispatched as an aggregate command; the server evaluates nested '$'-strings as field paths and '$'-keys as operators, leaking unrelated field values or altering query semantics.

      Root cause

      • The only escaping in constant rendering checks solely whether the constant itself is a top-level BsonString starting with '$' (src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstConstantExpression.cs:40-43); any BsonArray or BsonDocument — including ones whose nested string elements start with '$' or whose keys start with '$' — is returned verbatim at line 45 with no $literal wrapper.
      • The producer imposes no constraint: ConstantExpressionToAggregationExpressionTranslator.cs:35 serializes arbitrary application CLR constants (lists, dictionaries, BsonDocuments) into the BsonValue that Render() emits.
      • Parent renderers embed the raw value in server-evaluated expression contexts: AstNaryExpression.cs:64 as an operator argument (e.g. the array in an aggregation $in, where elements are evaluated as expressions) and AstComputedField.cs:50 as a $project/$set/$addFields value (where '$'-keys are treated as operators); Where predicates reach these contexts via the $expr fallback at ExpressionToFilterTranslators/ExpressionToFilterTranslator.cs:44-47.
      • No downstream layer re-escapes: the only other $literal site, ProjectionHelper.cs:140-166, quotes documents in one narrow projection case but its ValueNeedsToBeQuoted switch omits BsonType.Array entirely; the rendered pipeline is sent as-is to the server at ExecutableQuery.cs:113-119.

      Ownership

      Assigned teams: @mongodb/dbx-csharp-dotnet


      Filed from Aegis finding 50935b2a04b5 (scan scan-320b0a32a42c) · primitive: Dollar-prefixed strings and keys nested in LINQ constant arrays/documents render unescaped and are evaluated server-side as field paths and aggregation operators. · categories: NoSQL Injection, Credential Exposure, PII Exposure

      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: