Przeglądaj źródła

Merge pull request #1885 from marouane-hassine/fix/1883-malformed-v2-json-500

fix: return "Invalid data." instead of HTTP 500 on malformed v2 payloads
El RIDO 1 tydzień temu
rodzic
commit
8a77305838
5 zmienionych plików z 79 dodań i 3 usunięć
  1. 1 0
      CHANGELOG.md
  2. 16 1
      lib/FormatV2.php
  3. 5 2
      lib/Request.php
  4. 37 0
      tst/FormatV2Test.php
  5. 20 0
      tst/RequestTest.php

+ 1 - 0
CHANGELOG.md

@@ -3,6 +3,7 @@
 ## 2.0.6 (not yet released)
 * CHANGED: Upgrading libraries to: DOMpurify 3.4.12
 * FIXED: Gracefully handle YOURLS replies with a 200 status code but no shorturl, instead of raising a TypeError
+* FIXED: Return "Invalid data." instead of HTTP 500 on malformed v2 JSON payloads (#1883)
 
 ## 2.0.5 (2026-07-11)
 * CHANGED: Show OS-specific copy hotkey hint (Cmd+c on Mac, Ctrl+c on others) (#1506)

+ 16 - 1
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

+ 5 - 2
lib/Request.php

@@ -113,6 +113,11 @@ class Request
                 try {
                     $data          = file_get_contents(self::$_inputStream);
                     $this->_params = Json::decode($data);
+                    // a valid JSON scalar (number, bool or string) decodes
+                    // without error, but is not a usable set of parameters
+                    if (!is_array($this->_params)) {
+                        $this->_params = array();
+                    }
                 } catch (JsonException $e) {
                     // ignore error, $this->_params will remain empty
                 }
@@ -189,8 +194,6 @@ 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;
         return $data;
     }
 

+ 37 - 0
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');
+    }
 }

+ 20 - 0
tst/RequestTest.php

@@ -158,6 +158,26 @@ class RequestTest extends TestCase
         $this->assertEquals('create', $request->getOperation());
     }
 
+    public function testPostScalarJson()
+    {
+        // a valid JSON scalar body (number, boolean or quoted string) must not
+        // be treated as a set of parameters, so that it is rejected cleanly
+        // instead of triggering a type error further down
+        foreach (array('1', '0.0', 'true', 'false', '"example"') as $scalar) {
+            $this->reset();
+            $_SERVER['REQUEST_METHOD']        = 'POST';
+            $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
+            $file                             = Helper::createTempFile();
+            file_put_contents($file, $scalar);
+            Request::setInputStream($file);
+            $request = new Request;
+            unlink($file);
+            $this->assertTrue($request->isJsonApiCall(), 'is JSON API call');
+            $this->assertEquals('create', $request->getOperation());
+            $this->assertEquals('', $request->getParam('ct'), "scalar body {$scalar} yields no parameters");
+        }
+    }
+
     public function testReadWithNegotiation()
     {
         $this->reset();