Reject a MongoDB operator wherever only a scalar id or relation key is valid

XMLWordPrintableJSON

    • Type: Bug
    • Resolution: Fixed
    • Priority: Critical - P2
    • 5.11.0
    • Affects Version/s: None
    • None
    • 5
    • 0.4
    • Not Needed
    • None
    • None
    • None
    • None
    • None
    • None

      Context

      Reported via SECBUG-4403 and SECBUG-4406 (Aegis-IH). See those tickets for the full analysis and reproduction scripts. This is the umbrella fix for the class of "a MongoDB operator document is smuggled into a position where only a scalar id or relation key is valid".

      Related but independent: PHPLARA-260 hardens the general 3-argument where($column, '=', $array) equality by wrapping the value in $eq. That change happens to neutralize the single-equality relation paths, but it does not cover the embedded pull() path or whereIn eager loading, which this ticket addresses with an explicit id guard.

      Problem

      convertKey() in src/Query/Builder.php is the shared normalizer for document ids. It returns any value that is not a 24-hex or 16-byte string unchanged, arrays included. Wherever the library uses a value that can only legitimately be a scalar id or relation key, an attacker-supplied array such as {{

      {"$ne": null}

      }} survives verbatim into the compiled MongoDB filter and becomes a live operator document.

      Confirmed attack surfaces:

      • Polymorphic relation lookup (MorphTo): a stored operator array on the morph foreign key resolves to {_id: {$ne: null}} and returns an arbitrary document (SECBUG-4403).
      • Embedded document keys (EmbedsMany performDelete via pull(), performUpdate): an operator array as the embedded key deletes or overwrites every embedded document (SECBUG-4406).

      Root cause

      • convertKey() (src/Query/Builder.php) does no shape check on the id value.
      • EmbedsOneOrMany::getForeignKeyValue() only calls convertKey, so the embedded key is never validated.
      • Relation constraints (BelongsTo, MorphTo, BelongsToMany, MorphToMany) build the key comparison from a raw model attribute; when the key is a custom (non _id) column, the value never reaches the _id conversion path at all.
      • DocumentModel::setAttribute() only converts 24-char string _id/id values, so an operator array assigned to the primary key is stored verbatim (plant variant).

      Proposed fix

      Reject any value that is a MongoDB operator document (an array containing one or more $-prefixed keys, checked recursively) in every position where only a scalar id or relation key is valid. Plain arrays without operator keys remain allowed, preserving support for composite _id documents.

      Central guard plus the paths that bypass it:

      1. Add the operator-document check in convertKey() (src/Query/Builder.php). This covers find(), delete($id), where('_id', ...), whereKey, update, whereIn('_id', ...) per element, the default _id relation lookups, and EmbedsOneOrMany::getForeignKeyValue().
      2. Validate the key value in the relation constraints that can use a custom (non _id) key: BelongsTo::addConstraints, MorphTo::addConstraints, BelongsToMany::setWhere, and the whereIn eager-loading and pivot paths in MorphToMany.
      3. Reject an operator array assigned to _id/id in DocumentModel::setAttribute() (write-time planting).

      Mirror the existing style of the column guard in where() (src/Query/Builder.php), which throws InvalidArgumentException with get_debug_type().

      Tests

      Add cases covering: find, delete, where('_id', ...), morphTo resolution, embedded delete and update, belongsTo and belongsToMany with a custom key, whereIn('_id', ...), and setAttribute('_id', ...). Assert that an operator array throws, that a scalar id still works, and that a plain composite array id is still accepted. Use the repro scripts attached to SECBUG-4403 and SECBUG-4406 as the basis.

      Docs / changelog

      Document that passing a MongoDB operator document where an id or relation key is expected now throws.

              Assignee:
              Jérôme Tamarelle
              Reporter:
              Jérôme Tamarelle
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

                Created:
                Updated:
                Resolved: