From 16c77562085c6fae6811e6f96bd4013896805572 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 11 Jun 2026 20:59:08 +0200 Subject: [PATCH 1/5] fix(Filters): check both keys and values in InvalidChars arrays --- system/Filters/InvalidChars.php | 16 ++++++++++++-- tests/system/Filters/InvalidCharsTest.php | 26 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/system/Filters/InvalidChars.php b/system/Filters/InvalidChars.php index 1cd775153c0e..85e71c5d4f59 100644 --- a/system/Filters/InvalidChars.php +++ b/system/Filters/InvalidChars.php @@ -87,11 +87,18 @@ public function after(RequestInterface $request, ResponseInterface $response, $a * @param array|string $value * * @return array|string + * + * @throws SecurityException */ protected function checkEncoding($value) { if (is_array($value)) { - array_map($this->checkEncoding(...), $value); + foreach ($value as $key => $item) { + if (is_string($key)) { + $this->checkEncoding($key); + } + $this->checkEncoding($item); + } return $value; } @@ -113,7 +120,12 @@ protected function checkEncoding($value) protected function checkControl($value) { if (is_array($value)) { - array_map($this->checkControl(...), $value); + foreach ($value as $key => $item) { + if (is_string($key)) { + $this->checkControl($key); + } + $this->checkControl($item); + } return $value; } diff --git a/tests/system/Filters/InvalidCharsTest.php b/tests/system/Filters/InvalidCharsTest.php index 8aa935b14383..f537c42e266a 100644 --- a/tests/system/Filters/InvalidCharsTest.php +++ b/tests/system/Filters/InvalidCharsTest.php @@ -103,6 +103,19 @@ public function testBeforeInvalidUTF8StringCausesException(): void $this->invalidChars->before($this->request); } + public function testBeforeInvalidUTF8StringInArrayKeyCausesException(): void + { + $this->expectException(SecurityException::class); + $this->expectExceptionMessage('Invalid UTF-8 characters in post:'); + + $sjisString = mb_convert_encoding('SJISの文字列です。', 'SJIS'); + service('superglobals')->setPost('val', [ + $sjisString => 'valid string', + ]); + + $this->invalidChars->before($this->request); + } + public function testBeforeInvalidControlCharCausesException(): void { $this->expectException(SecurityException::class); @@ -114,6 +127,19 @@ public function testBeforeInvalidControlCharCausesException(): void $this->invalidChars->before($this->request); } + public function testBeforeInvalidControlCharInArrayKeyCausesException(): void + { + $this->expectException(SecurityException::class); + $this->expectExceptionMessage('Invalid Control characters in cookie:'); + + $stringWithNullChar = "String contains null char and line break.\0\n"; + service('superglobals')->setCookie('val', [ + $stringWithNullChar => 'valid string', + ]); + + $this->invalidChars->before($this->request); + } + #[DataProvider('provideCheckControlStringWithLineBreakAndTabReturnsTheString')] #[DoesNotPerformAssertions] public function testCheckControlStringWithLineBreakAndTabReturnsTheString(string $input): void From ee6e9aae2aff98e91eb52e22a74d67d53596acbb Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 11 Jun 2026 21:15:40 +0200 Subject: [PATCH 2/5] test(Filters): use DataProvider for InvalidChars control characters test --- tests/system/Filters/InvalidCharsTest.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/system/Filters/InvalidCharsTest.php b/tests/system/Filters/InvalidCharsTest.php index f537c42e266a..558526e3950a 100644 --- a/tests/system/Filters/InvalidCharsTest.php +++ b/tests/system/Filters/InvalidCharsTest.php @@ -116,17 +116,26 @@ public function testBeforeInvalidUTF8StringInArrayKeyCausesException(): void $this->invalidChars->before($this->request); } - public function testBeforeInvalidControlCharCausesException(): void + #[DataProvider('provideBeforeInvalidControlCharCausesException')] + public function testBeforeInvalidControlCharCausesException(string $invalidString): void { $this->expectException(SecurityException::class); $this->expectExceptionMessage('Invalid Control characters in cookie:'); - $stringWithNullChar = "String contains null char and line break.\0\n"; - service('superglobals')->setCookie('val', $stringWithNullChar); + service('superglobals')->setCookie('val', $invalidString); $this->invalidChars->before($this->request); } + public static function provideBeforeInvalidControlCharCausesException(): iterable + { + yield 'null byte' => ["String with null char \0"]; + + yield 'backspace' => ["String with backspace \x08"]; + + yield 'escape' => ["String with escape \x1b"]; + } + public function testBeforeInvalidControlCharInArrayKeyCausesException(): void { $this->expectException(SecurityException::class); From 676bba6b849409ffda124c56946222c4f1c2635e Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 11 Jun 2026 21:20:56 +0200 Subject: [PATCH 3/5] style: fix PHPStan iterable return type in tests --- tests/system/Filters/InvalidCharsTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/system/Filters/InvalidCharsTest.php b/tests/system/Filters/InvalidCharsTest.php index 558526e3950a..4cf112b6da91 100644 --- a/tests/system/Filters/InvalidCharsTest.php +++ b/tests/system/Filters/InvalidCharsTest.php @@ -127,6 +127,9 @@ public function testBeforeInvalidControlCharCausesException(string $invalidStrin $this->invalidChars->before($this->request); } + /** + * @return iterable> + */ public static function provideBeforeInvalidControlCharCausesException(): iterable { yield 'null byte' => ["String with null char \0"]; From b4fef4298c2442cb59191809ddd3a709427c8450 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 11 Jun 2026 21:41:21 +0200 Subject: [PATCH 4/5] chore: re-trigger CI From 2066ce75ef0bf82b685b2d011d5db223ac382060 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sat, 13 Jun 2026 20:16:19 +0200 Subject: [PATCH 5/5] docs: update changelog for InvalidChars array keys fix --- user_guide_src/source/changelogs/v4.7.4.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/user_guide_src/source/changelogs/v4.7.4.rst b/user_guide_src/source/changelogs/v4.7.4.rst index f9cc995ad22a..b31f5507687d 100644 --- a/user_guide_src/source/changelogs/v4.7.4.rst +++ b/user_guide_src/source/changelogs/v4.7.4.rst @@ -32,6 +32,7 @@ Bugs Fixed - **API:** Fixed a bug in Transformers where the root request's ``fields`` and ``include`` query parameters leaked into nested transformers created inside ``include*()`` methods, causing incorrect field filtering, unexpected includes, or infinite recursion. - **Database:** Fixed a bug where ``updateBatch()`` could be called after Query Builder ``where()`` conditions, even though it's not supported. In this situation, now the ``DatabaseException`` is thrown. +- **Filters:** Fixed a bug in ``InvalidChars`` filter where invalid UTF-8 or control characters in array keys were not checked. - **HTTP:** Fixed a bug where the User Agent library reported Safari's WebKit version instead of the browser version from the ``Version`` token. - **Model:** Fixed a bug in ``Model::objectToRawArray()`` where the ``$recursive`` parameter was ignored.