Limited code execution in zenstruck/collections
Description
A callable string injection vulnerability in zenstruck/collections allows arbitrary function execution via user input passed to EntityRepository::find() or query().
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A callable string injection vulnerability in zenstruck/collections allows arbitrary function execution via user input passed to EntityRepository::find() or query().
Vulnerability
Overview
The zenstruck/collections library, a set of helpers for iterating, paginating, and filtering collections, contains a callable string injection vulnerability. The EntityRepository::find() and query() methods accept a $specification parameter that can be a callable string (e.g., "system"). When such a string is passed, the library executes it as a PHP callable, leading to arbitrary code execution [1][4].
Exploitation
An attacker can exploit this by providing a callable string as user input to the affected methods. The commit f4b1c48820 shows that the fix adds a check to ensure the callable is an object, preventing string-based callables from being executed [2]. The vulnerability is triggered when user-supplied data reaches find() or query() without proper sanitization [4].
Impact
Successful exploitation allows an attacker to execute arbitrary PHP functions, such as system, which can lead to remote code execution and full compromise of the application server [1][4].
Mitigation
The issue is fixed in version 0.2.1 of the library [3]. Users are advised to upgrade immediately. As a workaround, ensure that user input is never passed to EntityRepository::find() or query() [1].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
zenstruck/collectionPackagist | < 0.2.1 | 0.2.1 |
Affected products
2- Range: < 0.2.1
Patches
1f4b1c488206esecurity(orm): prevent passing callable strings to `find()`/`query()`
3 files changed · +29 −6
src/Collection/Doctrine/ORM/EntityRepositoryBridge.php+2 −2 modified@@ -25,7 +25,7 @@ trait EntityRepositoryBridge private EntityRepository $collectionRepo; /** - * @param mixed|Criteria|array<string,mixed>|callable(QueryBuilder):void $specification + * @param mixed|Criteria|array<string,mixed>|(object&callable(QueryBuilder):void) $specification */ public function find($specification, $lockMode = null, $lockVersion = null): ?object { @@ -38,7 +38,7 @@ public function find($specification, $lockMode = null, $lockVersion = null): ?ob } /** - * @param Criteria|array<string,mixed>|callable(QueryBuilder):void $specification + * @param Criteria|array<string,mixed>|(object&callable(QueryBuilder):void) $specification * * @return EntityResult<V> */
src/Collection/Doctrine/ORM/EntityRepository.php+4 −4 modified@@ -33,7 +33,7 @@ public function __construct(private EntityManagerInterface $em, private string $ } /** - * @param mixed|Criteria|array<string,mixed>|callable(QueryBuilder):void $specification + * @param mixed|Criteria|array<string,mixed>|(object&callable(QueryBuilder):void) $specification */ public function find(mixed $specification): ?object { @@ -46,7 +46,7 @@ public function find(mixed $specification): ?object return $this->em()->getUnitOfWork()->getEntityPersister($this->class)->load($specification, limit: 1); // @phpstan-ignore-line } - if (\is_callable($specification)) { + if (\is_callable($specification) && \is_object($specification)) { $specification($qb = $this->qb(), 'e'); return $qb->getQuery()->getSingleResult(); @@ -59,7 +59,7 @@ public function find(mixed $specification): ?object } /** - * @param Criteria|array<string,mixed>|callable(QueryBuilder):void $specification + * @param Criteria|array<string,mixed>|(object&callable(QueryBuilder):void) $specification * * @return EntityResult<V> */ @@ -71,7 +71,7 @@ public function query(mixed $specification): EntityResult return $qb->addCriteria($specification)->result(); } - if (\is_callable($specification)) { + if (\is_callable($specification) && \is_object($specification)) { $specification($qb, 'e'); return $qb->result();
tests/Doctrine/ORM/EntityRepositoryTest.php+23 −0 modified@@ -155,6 +155,29 @@ public function can_filter_with_callable(): void $this->assertSame('value 2', \iterator_to_array($objects)[0]->value); } + /** + * @test + */ + public function cannot_find_with_callable_strings(): void + { + $this->assertIsCallable('system'); + $this->assertNull($this->repo()->find('system')); + } + + /** + * @test + */ + public function cannot_query_with_callable_strings(): void + { + $this->assertIsCallable('system'); + + $repo = $this->repo(); + + $this->expectException(\InvalidArgumentException::class); + + $repo->query('system'); + } + /** * @test */
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-7xr2-8ff7-6fjqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-37473ghsaADVISORY
- github.com/zenstruck/collection/commit/f4b1c488206e1b1581b06fcd331686846f13f19cghsax_refsource_MISCWEB
- github.com/zenstruck/collection/releases/tag/v0.2.1ghsax_refsource_MISCWEB
- github.com/zenstruck/collection/security/advisories/GHSA-7xr2-8ff7-6fjqghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.