-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Critical - P2
-
Affects Version/s: None
-
None
-
5
-
0.2
-
Needed
-
-
None
-
None
-
None
-
None
-
None
-
None
Context
Reported via SECBUG-4402 (Aegis-IH). See that ticket for the full analysis and reproduction script.
Problem
In the query builder, when where($column, '=', $value) is called with an array $value, the compiled MongoDB filter embeds the array as-is:
where('token', '=', ['$ne' => null]) // compiles to ['token' => ['$ne' => null]]
The array is interpreted by MongoDB as an operator document, which turns an intended equality check into an arbitrary predicate. This also affects the internal call sites find($id) and delete($id), both of which route through where('_id', '=', $id).
An application that forwards unvalidated request input to these entry points can be tricked into:
- find(['$ne' => null]) returning the first document, bypassing an id lookup.
- delete(['$ne' => null]) removing every document in the collection.
- where('token', '=', $userInput) matching every row when the input is an operator document.
Repro script attached to SECBUG-4402.
Scope of the fix
Only the 3-argument form with an explicit = or eq operator should be hardened. The developer explicitly expressed equality intent, so an array value must be compared as a literal document, not as an operator document.
The 2-argument form where($column, $arrayValue) must keep its current behavior: it is a documented laravel-mongodb pattern for building operator documents (for example whereNot('title', ['$in' => ['admin']]), where('$text', ['$search' => 'x'])).
Proposed change
In the where() override in src/Query/Builder.php, after the existing operator normalization, when:
- func_num_args() >= 3, and
- $operator is '=' or 'eq', and
- is_array($value)
wrap the value in an explicit $eq:
$value = ['$eq' => $value];
Result:
- where('token', '=', ['$ne' => null]) compiles to ['token' => ['$eq' => ['$ne' => null]]], which MongoDB treats as a literal document comparison and matches nothing unless the field actually stores that exact sub-document.
- find(['$ne' => null]) and delete(['$ne' => null]) are neutralized through the same code path.
- where('token', ['$ne' => null]) is unchanged (2-arg form).
- where('foo', '$type', 2) is unchanged (operator not =).
- where('$text', ['$search' => 'x']) is unchanged (2-arg form).
- whereNot('title', ['$in' => ['admin']]) is unchanged (different where type).
Tests
Add cases in tests/Query/BuilderTest.php covering each vector above, plus the internal call sites find and delete.
Docs / changelog
Document the intentional divergence: in laravel-mongodb, where('x', $v) and where('x', '=', $v) are no longer equivalent when $v is an array. The 3-arg form enforces literal equality; the 2-arg form remains the operator-document shortcut. Note this in the upgrade guide and the query builder documentation.