Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

fix(security): SQL injection in MagicMapper UNION search (wave-3 C2, C3)#1994

Open
rubenvdlinde wants to merge 1 commit into
developmentfrom
fix/security/wave-3-sql-injection-c2-c3
Open

fix(security): SQL injection in MagicMapper UNION search (wave-3 C2, C3)#1994
rubenvdlinde wants to merge 1 commit into
developmentfrom
fix/security/wave-3-sql-injection-c2-c3

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Wave-3 critical security sweep — PR 1 of 4

Fixes two SQL-injection vectors in lib/Db/MagicMapper.php::searchAcrossMultipleTablesWithUnion().

C2 — _limit / _offset interpolated raw into UNION SQL

MagicMapper.php:1243-1245 (pre-fix) built " LIMIT {limit} OFFSET {offset}" from query['_limit'] / query['_offset'] without casting. SearchQueryHandler preserves _-prefixed system params untouched, so the raw user value flowed from any GET /api/objects (incl. @PublicPage paths) into a raw prepared SQL string.

Fix: cast + clamp at the boundary: (int), then max(1, min(1000, …)) for limit and max(0, …) for offset.

C3 — ORDER BY accepted unsanitised @self.

MagicMapper.php:1213-1224 (pre-fix): the @self. branch set columnName = METADATA_PREFIX . substr(field, 6) with no sanitizeColumnName + no quoteIdentifier, then concatenated into the ORDER BY clause. Only the non-_ else-if branch was sanitised + quoted.

Fix: allowlist the @self.<suffix> candidate against getMetadataColumns(); sanitise + quote in every branch (incl. the _-prefixed system-column path); skip unknown suffixes instead of risking arbitrary-identifier injection.

Why one PR for both

Both are the same class of bug (SQL injection via unfiltered string interpolation in the same method), both reachable via any UNION-search path including anonymous-reachable @PublicPage reads. Keeping them together gives reviewers a single diff to audit the entire UNION-clause assembly.

Test plan

  • php -l lib/Db/MagicMapper.php
  • composer phpstan against modified file — [OK] No errors
  • PHPUnit MagicMapper suite — 85 tests, 217 assertions passing
  • Manual: GET /api/objects/{r}/{s} with crafted _limit / _offset / _order[@self.X] values

…icMapper UNION search (C2, C3)

C2 — _limit/_offset interpolated raw into UNION SQL:
searchAcrossMultipleTablesWithUnion() concatenated $query['_limit'] /
$query['_offset'] directly into the final LIMIT/OFFSET clause. The
boundary that strips '_'-prefixed params (SearchQueryHandler) explicitly
preserves them, so the raw user value flowed from any /api/objects GET
through to a raw prepared SQL string. Cast at the boundary and clamp:
LIMIT 1..1000, OFFSET >= 0.

C3 — ORDER BY accepted unsanitised @self.<field>:
The @self. branch set the column name to METADATA_PREFIX . substr($field, 6)
with no sanitizeColumnName / quoteIdentifier, then concatenated into the
ORDER BY clause. Only the non-underscore branch was sanitised. Allowlist
the metadata-suffix against getMetadataColumns(), sanitise + quote in
every branch, and skip unknown suffixes instead of risking injection.

Both reachable from any caller that hits a UNION-search code path,
including @publicpage object reads — i.e. anonymous-reachable injection.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant