Back to blog
June 5, 2025Security Research - Insecure Deserialization

CVE-2025-1974 - Auth0 Wordpress Plugin - Insecure Deserialization

Abstract

The Auth0 Wordpress plugin contains a critical vulnerability due to insecure deserialization of cookie data. If exploited, since SDKs process cookie content without prior authentication, a threat actor could send a specially crafted cookie containing malicious serialized data.

Analysis

The vulnerability stems from the CookieStore.php within the Auth0 SDK, which is used by the Wordpress plugin (also laravel ...). The SDK processes cookie content without prior checks and pass it to serialize() .

The core issue lies in the decrypt function, which deserializes arbitrary data retrieved from cookies without proper protections. This allows us to inject arbitrary PHP objects into the application's scope

Vulnerability Details

The CookieStore class is responsible for managing the storage of data in cookies. The getState method retrieves data from cookies, decrypts it (if encryption is enabled), and unserializes it. basically the lack of proper validation on the unserialized data is the root cause of this vulnerability.

 public function getState(
        ?array $state = null
    ): array {
        // Overwrite our internal state with one passed (presumably during unit tests.)
        if ($state !== null) {
            if ($this->store !== $state) {
                $this->dirty = true;
            }

            return $this->store = $state;
        }

        $request = Application::Instance()->HttpRequest();
        $data = $request->cookie($this->namespace);

        if (is_array($data) || $data === null) {
            return $this->store = [];
        }

        // If no cookies were found, set an empty state and continue.
        if ($data === '') {
            return $this->store = [];
        }

        $data = $this->decrypt($data);

        if ($data === null) {
            return $this->store = [];
        }

        if (is_array($data)) {
            return $this->store = $data;
        }

        return $this->store = [];
    }

The decrypt function is responsible for decrypting the cookie data. If encryption is disabled, it simply decodes the data using rawurldecode and json_decode. If encryption is enabled, it performs the decryption using openssl_decrypt and then decodes the data using json_decode. In both cases, the data is not validated before being used.

public function decrypt(
    string $data
) {
    if (! $this->encrypt) {
        $decoded = rawurldecode($data);
        $decoded = json_decode($decoded, true);

        if (is_array($decoded)) {
            return $decoded;
        }

        return [];
    }

    [$data] = Toolkit::filter([$data])->string()->trim();

    $secret = $this->configuration->getCookieSecret();

    if ($secret === null) {
        throw \Auth0\SDK\Exception\ConfigurationException::requiresCookieSecret();
    }

    $decoded = rawurldecode((string) $data);
    $stripped = stripslashes($decoded);
    $data = json_decode($stripped, true, 512);

    /** @var array{iv?: int|string|null, tag?: int|string|null, data: string} $data */

    if (! isset($data['iv']) || ! isset($data['tag']) || ! is_string($data['iv']) || ! is_string($data['tag'])) {
        return null;
    }

    $iv = base64_decode($data['iv'], true);
    $tag = base64_decode($data['tag'], true);

    if (! is_string($iv) || ! is_string($tag)) {
        return null;
    }

    $data = openssl_decrypt($data['data'], self::VAL_CRYPTO_ALGO, $secret, 0, $iv, $tag);

    if (! is_string($data)) {
        return null;
    }

    $data = json_decode($data, true);

    /** @var array<mixed> $data */
    return $data;
}

Patch Review

The patch addresses the vulnerability by introducing a new setEncrypted method to enable or disable cookie encryption.

--- a/src/Store/CookieStore.php
+++ b/src/Store/CookieStore.php
@@ -50,6 +50,16 @@
      */
     private bool $dirty = false;
 
+    /**
+     * Determine if changes have been made since the last setState.
+     */
+    private bool $encrypt = true;
+
+    /**
+     * Returns the current encryption state
+     */
+    public function getEncrypted(): bool
+    {
+        return $this->encrypt;
+    }

+    /**
+     * Toggle the encryption state
+     *
+     * @param bool $encrypt Enable or disable cookie encryption.
+     */
+    public function setEncrypted(bool $encrypt = true): self
+    {
+        $this->encrypt = $encrypt;
+        return $this;
+    }

The patch modifies the encrypt and decrypt methods to use rawurlencode and rawurldecode when encryption is disabled. This prevents the unserialization of data.

--- a/src/Store/CookieStore.php
+++ b/src/Store/CookieStore.php
@@ -371,12 +371,12 @@
     public function encrypt(
         array $data,
         array $options = []
     ): string {
         if (! $this->encrypt) {
             $data = $options['encoded1'] ?? json_encode($data);

             if (! is_string($data)) {
                 return '';
             }

             return rawurlencode($data);
         }
--- a/src/Store/CookieStore.php
+++ b/src/Store/CookieStore.php
@@ -434,7 +434,7 @@
         if (! $this->encrypt) {
             $decoded = rawurldecode($data);
             $decoded = json_decode($decoded, true);

Exploitation Path

To exploit this vulnerability, an attacker would:

  1. Craft a PHP object (Too lazy to look for a pop chain in this codebase)
  2. Serialize this object.
  3. Base64 encode the serialized data.
  4. Set a cookie with the name matching the $namespace used by the CookieStore instance. The value of the cookie would be the base64 encoded serialized data.
  5. When the application processes the request with the cookie, the getState method will retrieve the cookie data, the decrypt method will decode the data, and the unserialize function will execute the methods that exists in our serialized object

References :