From f4b20e0a1c4d5a86d4d319c583ce0ca730c48ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20D=C3=A1vila?= Date: Wed, 19 Jun 2024 16:39:34 +0200 Subject: [PATCH 1/4] fix: validate iat and nbf on payload --- src/JWT.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/JWT.php b/src/JWT.php index e9d75639..00f259fa 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -127,6 +127,16 @@ public static function decode( if (!$payload instanceof stdClass) { throw new UnexpectedValueException('Payload must be a JSON object'); } + if (isset($payload->iat) && !\is_numeric($payload->iat)) { + throw new UnexpectedValueException('Payload iat must be a number'); + } + if (isset($payload->nbf) && !\is_numeric($payload->nbf)) { + throw new UnexpectedValueException('Payload nbf must be a number'); + } + if (isset($payload->exp) && !\is_numeric($payload->exp)) { + throw new UnexpectedValueException('Payload exp must be a number'); + } + $sig = static::urlsafeB64Decode($cryptob64); if (empty($header->alg)) { throw new UnexpectedValueException('Empty algorithm'); @@ -172,7 +182,7 @@ public static function decode( } // Check if this token has expired. - if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + if (isset($payload->exp) && floor($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { $ex = new ExpiredException('Expired token'); $ex->setPayload($payload); throw $ex; From f0d5ea1a453e6f4edda2df8e5ba5767fe60b1812 Mon Sep 17 00:00:00 2001 From: Azure Pipeplines CI Date: Wed, 16 Apr 2025 10:49:59 +0200 Subject: [PATCH 2/4] Remove unnecessary floor on comparation --- src/JWT.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JWT.php b/src/JWT.php index 22d739f1..5386b601 100644 --- a/src/JWT.php +++ b/src/JWT.php @@ -182,7 +182,7 @@ public static function decode( } // Check if this token has expired. - if (isset($payload->exp) && floor($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { $ex = new ExpiredException('Expired token'); $ex->setPayload($payload); throw $ex; From aa462144312d116fe94862c38eb1b14c2f181afc Mon Sep 17 00:00:00 2001 From: Azure Pipeplines CI Date: Wed, 16 Apr 2025 10:51:16 +0200 Subject: [PATCH 3/4] Add tests with JWT decode with invalid iat, nbf and exp properties --- tests/JWTTest.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/JWTTest.php b/tests/JWTTest.php index d09d43e3..2dda95e5 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -546,4 +546,34 @@ public function testAdditionalHeaderOverrides() $this->assertEquals('my_key_id', $headers->kid, 'key param not overridden'); $this->assertEquals('HS256', $headers->alg, 'alg param not overridden'); } + + public function testDecodeExpectsIntegerIat() + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Payload iat must be a number'); + JWT::decode( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6IkpvaG4gU25vdyIsImlhdCI6InRlc3QifQ.B8cbURVQAPay3-Ep0DAm1Ji2rhij-hxfNA5PIDarf5o', + new Key('secret', 'HS256') + ); + } + + public function testDecodeExpectsIntegerNbf() + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Payload nbf must be a number'); + JWT::decode( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6IkpvaG4gU25vdyIsIm5iZiI6InRlc3QifQ.9KdFz3ktQoPO5QFG3E7J86PEuw5Vmx0VPrUKszP7DDs', + new Key('secret', 'HS256') + ); + } + + public function testDecodeExpectsIntegerExp() + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Payload exp must be a number'); + JWT::decode( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6IkpvaG4gU25vdyIsImV4cCI6InRlc3QifQ.LXevvGvchI3PTBZo9jZ5-4d0OvONVU-_8Tbg_22-PTo', + new Key('secret', 'HS256') + ); + } } From 12fb41248c1ba1be297d12b8a48be877309b8407 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 16 Apr 2025 09:43:50 -0700 Subject: [PATCH 4/4] clean up tests --- tests/JWTTest.php | 55 ++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/tests/JWTTest.php b/tests/JWTTest.php index 2dda95e5..805b867a 100644 --- a/tests/JWTTest.php +++ b/tests/JWTTest.php @@ -547,33 +547,30 @@ public function testAdditionalHeaderOverrides() $this->assertEquals('HS256', $headers->alg, 'alg param not overridden'); } - public function testDecodeExpectsIntegerIat() - { - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Payload iat must be a number'); - JWT::decode( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6IkpvaG4gU25vdyIsImlhdCI6InRlc3QifQ.B8cbURVQAPay3-Ep0DAm1Ji2rhij-hxfNA5PIDarf5o', - new Key('secret', 'HS256') - ); - } - - public function testDecodeExpectsIntegerNbf() - { - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Payload nbf must be a number'); - JWT::decode( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6IkpvaG4gU25vdyIsIm5iZiI6InRlc3QifQ.9KdFz3ktQoPO5QFG3E7J86PEuw5Vmx0VPrUKszP7DDs', - new Key('secret', 'HS256') - ); - } - - public function testDecodeExpectsIntegerExp() - { - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Payload exp must be a number'); - JWT::decode( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzM3IiwibmFtZSI6IkpvaG4gU25vdyIsImV4cCI6InRlc3QifQ.LXevvGvchI3PTBZo9jZ5-4d0OvONVU-_8Tbg_22-PTo', - new Key('secret', 'HS256') - ); - } + public function testDecodeExpectsIntegerIat() + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Payload iat must be a number'); + + $payload = JWT::encode(['iat' => 'not-an-int'], 'secret', 'HS256'); + JWT::decode($payload, new Key('secret', 'HS256')); + } + + public function testDecodeExpectsIntegerNbf() + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Payload nbf must be a number'); + + $payload = JWT::encode(['nbf' => 'not-an-int'], 'secret', 'HS256'); + JWT::decode($payload, new Key('secret', 'HS256')); + } + + public function testDecodeExpectsIntegerExp() + { + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Payload exp must be a number'); + + $payload = JWT::encode(['exp' => 'not-an-int'], 'secret', 'HS256'); + JWT::decode($payload, new Key('secret', 'HS256')); + } }