-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Critical - P2
-
Affects Version/s: None
-
Component/s: None
-
None
-
0.2
-
Fully Compatible
-
Dotnet Drivers
-
Not Needed
-
None
-
None
-
None
-
None
-
None
-
None
Summary
User data captured into a LINQ GroupBy constant reaches the database unescaped, letting dollar-prefixed strings read hidden fields or invoke server-side operators.
Impact
The MongoDB server evaluates the attacker's content as aggregation expressions inside $group, disclosing arbitrary fields of every grouped document through the app's result path, silently corrupting grouping results, and — with key control on scripting-enabled servers — executing attacker JavaScript via $function.
Severity: high (upper bound critical)
Exploitability: likely_exploitable — Exploitation is realistic wherever an app embeds attacker strings inside a document/array-shaped GroupBy key or element constant — a common pattern (grouping by a user-selected composite key); the attacker fully controls the injected field path or operator.
Customer data: customer_content, pii, credentials (api_response)
Location
- src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/GroupByMethodToPipelineTranslator.cs:55 in Translate
- src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstConstantExpression.cs:45 in Render
- src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstGroupStage.cs:51 in Render
- src/MongoDB.Driver/Linq/Linq3Implementation/GroupingWithOutputExpressionStageDefinitions.cs:201 in RenderGroupStage
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 string values nested in the constant (field-path injection: '$anyField', '$$variable') and, when the app builds the constant from a dictionary with attacker-supplied keys, the document keys themselves (operator injection: $cond, $function, etc.).
- (source) src/MongoDB.Driver/Linq/Linq3Implementation/Misc/LinqExpressionPreprocessor.cs:30 in Preprocess — PartialEvaluator folds the parameter-independent GroupBy selector body — carrying attacker-supplied strings captured in a closure object, dictionary, or array — into a ConstantExpression.
- (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/GroupByMethodToPipelineTranslator.cs:47 in Translate — TranslateLambdaBody translates the key selector (and element selector at line 81), dispatching the ConstantExpression to the constant translator.
- (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConstantExpressionToAggregationExpressionTranslator.cs:35 in Translate — Serializes the constant to an arbitrary BsonValue shape (document/array with attacker strings and keys) and wraps it in AstConstantExpression.
- (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToPipelineTranslators/GroupByMethodToPipelineTranslator.cs:55 in Translate — Passes the constant AST directly as the $group _id (line 55) and $push accumulator argument (line 56) with no quoting step.
- (hop) src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstConstantExpression.cs:45 in Render — Quoting gap: only a top-level $-prefixed BsonString is $literal-wrapped (lines 40-43); constant documents and arrays with nested $-prefixed content are returned raw.
- (sink) src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Stages/AstGroupStage.cs:51 in Render — The raw constant is embedded into the $group command document sent to the MongoDB server, whose aggregation evaluator interprets $-prefixed strings as field paths and $-prefixed keys as operators.
Root cause
- PartialEvaluator folds any parameter-independent GroupBy key/element selector (closure-captured objects, dictionaries, arrays holding user data) into a ConstantExpression (src/MongoDB.Driver/Linq/Linq3Implementation/Misc/LinqExpressionPreprocessor.cs:30).
- ConstantExpressionToAggregationExpressionTranslator serializes the constant to an arbitrary BsonValue and wraps it in AstConstantExpression (ConstantExpressionToAggregationExpressionTranslator.cs:35-36); GroupByMethodToPipelineTranslator passes that AST directly as the $group _id and $push argument (GroupByMethodToPipelineTranslator.cs:55-56).
- AstConstantExpression.Render() only $literal-wraps a top-level $-prefixed BsonString; constant documents and arrays — including nested $-prefixed values and keys — are returned raw (AstConstantExpression.cs:40-45), and AstGroupStage.Render() embeds them verbatim into the $group command document (AstGroupStage.cs:51).
- The existing safeguard, ProjectionHelper.QuoteIfNecessary (ProjectionHelper.cs:140-165), $literal-quotes document/boolean/numeric constants but is only invoked on the $project paths — never on the $group path; AstSimplifier and AstGroupingPipelineOptimizer add no quoting either.
Ownership
Assigned teams: @mongodb/dbx-csharp-dotnet
Filed from Aegis finding 60f097f03309 (scan scan-320b0a32a42c) · primitive: Nested dollar-prefixed strings and keys in constant GroupBy selectors render unquoted into the group stage, becoming live server-side aggregation expressions. · categories: NoSQL Injection, PII Exposure, Data Corruption
- is related to
-
CSHARP-6209 NoSQL injection via unquoted constant GroupBy keys in LINQ pipeline translation (port to main branch)
-
- Needs Triage
-