From e80efda7c2c5d7ac42a07aefea3e3776a062535c Mon Sep 17 00:00:00 2001 From: marouanehassine Date: Fri, 17 Jul 2026 14:44:04 +0200 Subject: [PATCH] fix: return "Invalid data." instead of HTTP 500 on malformed v2 payloads Under strict_types on PHP 8, malformed JSON API v2 payloads triggered uncaught TypeErrors (HTTP 500) instead of the standard JSON error response {"status":1,"message":"Invalid data."}: - a non-numeric `v` reached `$data['v'] + 0` in Request::getData() - a scalar `meta` reached `count($message['meta'])` in FormatV2::isValid() - non-array adata[0] / non-string ct fields reached base64_decode(), strlen() and count() with the wrong types Validate the types before the numeric coercion and before the string/count operations, so all such inputs are rejected cleanly with "Invalid data." instead of a fatal error. Both crashes are remotely triggerable without authentication (availability only, no data exposure). Adds FormatV2Test coverage for the malformed-type inputs. Fixes #1883 --- lib/FormatV2.php | 17 ++++++++++++++++- lib/Request.php | 9 +++++++-- tst/FormatV2Test.php | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lib/FormatV2.php b/lib/FormatV2.php index 7f1912fa..b7e749f9 100644 --- a/lib/FormatV2.php +++ b/lib/FormatV2.php @@ -56,7 +56,21 @@ class FormatV2 return false; } - $cipherParams = $isComment ? $message['adata'] : $message['adata'][0]; + $cipherParams = $isComment ? $message['adata'] : ($message['adata'][0] ?? null); + + // Make sure the cipher parameters are a properly sized array. + if (!is_array($cipherParams) || count($cipherParams) < 8) { + return false; + } + + // Make sure the ciphertext and the cipher parameters used in the + // string operations below are actually strings, so that malformed + // input yields "Invalid data." instead of a fatal type error. + if (!is_string($message['ct']) || + !is_string($cipherParams[0] ?? null) || + !is_string($cipherParams[1] ?? null)) { + return false; + } // Make sure some fields are base64 data: // - initialization vector @@ -119,6 +133,7 @@ class FormatV2 // require only the key 'expire' in the metadata of pastes if (!$isComment && ( + !is_array($message['meta']) || count($message['meta']) === 0 || !array_key_exists('expire', $message['meta']) || count($message['meta']) > 1 diff --git a/lib/Request.php b/lib/Request.php index 31233472..c1902b72 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -189,8 +189,13 @@ class Request foreach ($required_keys as $key) { $data[$key] = $this->getParam($key, $key === 'v' ? 1 : ''); } - // forcing a cast to int or float - $data['v'] = $data['v'] + 0; + // forcing a cast to int or float, but only when the value is + // numeric; a non-numeric version is left untouched so the format + // validator rejects it with "Invalid data." instead of a fatal + // type error + if (is_numeric($data['v'])) { + $data['v'] = $data['v'] + 0; + } return $data; } diff --git a/tst/FormatV2Test.php b/tst/FormatV2Test.php index 2e13d8f5..b5dfca1b 100644 --- a/tst/FormatV2Test.php +++ b/tst/FormatV2Test.php @@ -72,4 +72,41 @@ class FormatV2Test extends TestCase $paste = Helper::getPaste(); $this->assertFalse(FormatV2::isValid($paste), 'invalid meta key'); } + + public function testFormatV2ValidatorRejectsMalformedTypes() + { + // malformed input must yield "false" (Invalid data) rather than a + // fatal type error under strict_types + $paste = Helper::getPastePost(); + $paste['v'] = 'abc'; + $this->assertFalse(FormatV2::isValid($paste), 'non-numeric version'); + + $paste = Helper::getPastePost(); + $paste['v'] = array(); + $this->assertFalse(FormatV2::isValid($paste), 'array version'); + + $paste = Helper::getPastePost(); + $paste['ct'] = array('not', 'a', 'string'); + $this->assertFalse(FormatV2::isValid($paste), 'non-string ciphertext'); + + $paste = Helper::getPastePost(); + $paste['meta'] = 'not-an-array'; + $this->assertFalse(FormatV2::isValid($paste), 'non-array meta'); + + $paste = Helper::getPastePost(); + $paste['adata'][0] = 'not-an-array'; + $this->assertFalse(FormatV2::isValid($paste), 'non-array cipher parameters'); + + $paste = Helper::getPastePost(); + $paste['adata'][0] = array(); + $this->assertFalse(FormatV2::isValid($paste), 'empty cipher parameters'); + + $paste = Helper::getPastePost(); + $paste['adata'][0][0] = array(); + $this->assertFalse(FormatV2::isValid($paste), 'non-string iv'); + + $comment = Helper::getCommentPost(); + $comment['ct'] = 42; + $this->assertFalse(FormatV2::isValid($comment, true), 'non-string comment ciphertext'); + } }