Ver Fonte

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
marouanehassine há 1 semana atrás
pai
commit
e80efda7c2
3 ficheiros alterados com 60 adições e 3 exclusões
  1. 16 1
      lib/FormatV2.php
  2. 7 2
      lib/Request.php
  3. 37 0
      tst/FormatV2Test.php

+ 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

+ 7 - 2
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;
     }
 

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