GitHub Security Advisory: GHSA-xw36-67f8-339x
Package: kelvinmo/simplejwt (COMPOSER)
Severity: HIGH (CVSS 7.5)
CWE: CWE-400: Uncontrolled Resource Consumption
Vulnerable versions: <= 1.1.0
Patched in: 1.1.1

Summary: SimpleJWT has an Unauthenticated Denial of Service via JWE header tampering

Details:
## Summary

An unauthenticated attacker can perform a Denial of Service via JWE header tampering when PBES2 algorithms are used.  
Applications that call `JWE::decrypt()` on attacker-controlled JWEs using PBES2 algorithms are affected.

## Details

PHP version: `PHP 8.4.11`
SimpleJWT version: `v1.1.0`

The relevant portion of the vulnerable implementation is shown below ([PBES2.php](https://github.com/kelvinmo/simplejwt/blob/edb7807a240b72c59e72d7dca31add9d16555f9f/src/SimpleJWT/Crypt/KeyManagement/PBES2.php)):

```PHP
<?php
/* ... SNIP ... */
class PBES2 extends BaseAlgorithm implements KeyEncryptionAlgorithm {
    use AESKeyWrapTrait;

    /** @var array<string, mixed> $alg_params */
    static protected $alg_params = [
        'PBES2-HS256+A128KW' => ['hash' => 'sha256'],
        'PBES2-HS384+A192KW' => ['hash' => 'sha384'],
        'PBES2-HS512+A256KW' => ['hash' => 'sha512']
    ];

    /** @var truthy-string $hash_alg */
    protected $hash_alg;

    /** @var int $iterations */
    protected $iterations = 4096;
    
    /* ... SNIP ... */

    /**
     * Sets the number of iterations to use in PBKFD2 key generation.
     *
     * @param int $iterations number of iterations
     * @return void
     */
    public function setIterations(int $iterations) {
        $this->iterations = $iterations;
    }
    
    /* ... SNIP ... */

    /**
     * {@inheritdoc}
     */
    public function decryptKey(string $encrypted_key, KeySet $keys, array $headers, ?string $kid = null): string {
        /** @var SymmetricKey $key */
        $key = $this->selectKey($keys, $kid);
        if ($key == null) {
            throw new CryptException('Key not found or is invalid', CryptException::KEY_NOT_FOUND_ERROR);
        }
        if (!isset($headers['p2s']) || !isset($headers['p2c'])) {
            throw new CryptException('p2s or p2c headers not set', CryptException::INVALID_DATA_ERROR);
        }

        $derived_key = $this->generateKeyFromPassword($key->toBinary(), $headers);
        return $this->unwrapKey($encrypted_key, $derived_key, $headers);
    }
    
    /* ... SNIP ... */

    /**
     * @param array<string, mixed> $headers
     */
    private function generateKeyFromPassword(string $password, array $headers): string {
        $salt = $headers['alg'] . "\x00" . Util::base64url_decode($headers['p2s']);
        /** @var int<0, max> $length */
        $length = intdiv($this->getAESKWKeySize(), 8);

        return hash_pbkdf2($this->hash_alg, $password, $salt, $headers['p2c'], $length, true);
    }
}
?>
```

The security flaw lies in the lack of input validation when handling JWEs that uses PBES2.  
A "sanity ceiling" is not set on the iteration count, which is the parameter known in the JWE specification as `p2c` ([RFC7518](https://datatracker.ietf.org/doc/html/rfc7518#section-4.8.1.2)).  
The library calls `decryptKey()` with the untrusted input `$headers` which then use the PHP function `hash_pbkdf2()` with the user-supplied value `$headers['p2c']`.