GHSA-8JQH-95G6-7JPJ

Vulnerability from github – Published: 2026-08-28 16:01 – Updated: 2026-08-28 16:01
VLAI
Summary
Phalcon: Non-constant-time HMAC verification in `Encryption\Crypt::decrypt` (timing side-channel)
Details

Summary

Phalcon\Encryption\Crypt provides authenticated encryption: when useSigning is enabled (the default), encrypt() appends an HMAC tag and decrypt() verifies it before returning the plaintext. The verification compares the attacker-supplied tag against the freshly computed HMAC using PHP/Zephir identity comparison (!==), which the Zephir compiler lowers to !ZEPHIR_IS_IDENTICAL(...) — a byte-wise memcmp that returns early on the first differing byte. The comparison time therefore depends on how many leading bytes of the supplied tag are correct, a classic MAC-verification timing side-channel. Every other secret/MAC comparison in the framework uses the constant-time hash_equals() (zephir_hash_equals) — the CSRF token check (Security::checkToken) and the JWT signature check (Signer\Hmac::verify); Crypt::decrypt is the lone deviation.

Details

Vulnerable code

phalcon/Encryption/Crypt.zep:246 (Zephir source):

if true === this->useSigning {
    // Checks on the decrypted message digest using the HMAC method.
    if digest !== hash_hmac(hashAlgorithm, padded, decryptKey, true) {
        throw new Mismatch("Hash does not match.");
    }
}

Generated C --> ext/phalcon/encryption/crypt.zep.c:364-367:

ZEPHIR_CALL_FUNCTION(&_8$$7, "hash_hmac", NULL, 245, &hashAlgorithm, &padded, &decryptKey, &__$true);
...
if (!ZEPHIR_IS_IDENTICAL(&digest, &_8$$7)) {                 // <-- non-constant-time
    ZEPHIR_THROW_EXCEPTION_DEBUG_STR(..., "Hash does not match.", "phalcon/Encryption/Crypt.zep", 247);

ZEPHIR_IS_IDENTICAL --> zephir_is_identical() (ext/kernel/operators.c:472) --> Zend is_identical_function --> for equal-length strings a memcmp that exits on the first mismatching byte (data-dependent timing).

Impact

The HMAC is the integrity/authentication tag of Phalcon's authenticated-encryption scheme. A successful timing attack (Keyczar/CVE-2009-0654-style: fix the IV+ciphertext so the target tag is constant, then recover it byte-by-byte from response timing) yields a tag the attacker can attach to a chosen IV+ciphertext so that decrypt() accepts it as authentic, defeating the integrity guarantee. Combined with CFB malleability (flipping a ciphertext byte flips the corresponding plaintext byte), an attacker who recovers the forging capability can tamper with the decrypted contents the application trusts (e.g. encrypted cookies carrying authorization/identity state). There is no confidentiality break by itself.

Suggested fix

Replace the identity comparison with the constant-time helper already used elsewhere in the framework. In phalcon/Encryption/Crypt.zep:246:

// before
if digest !== hash_hmac(hashAlgorithm, padded, decryptKey, true) {
    throw new Mismatch("Hash does not match.");
}
// after
if true !== hash_equals(hash_hmac(hashAlgorithm, padded, decryptKey, true), digest) {
    throw new Mismatch("Hash does not match.");
}

hash_equals() returns false for unequal-length inputs, so it also covers the truncated-tag case. Optional further hardening: verify the MAC before unpadding (functionally moot here because cryptUnpadText never throws) and consider migrating the default toward an AEAD mode such as aes-256-gcm.

Addressed Issue:

  • https://github.com/phalcon/cphalcon/issues/17090

Patched Stream:

  • https://github.com/phalcon/cphalcon/issues/17090
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.14.0"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "phalcon/cphalcon"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "5.14.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54736"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208",
      "CWE-347"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-28T16:01:05Z",
    "nvd_published_at": "2026-07-10T22:16:42Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\n`Phalcon\\Encryption\\Crypt` provides authenticated encryption: when `useSigning` is enabled (the default), `encrypt()` appends an HMAC tag and `decrypt()` verifies it before returning the plaintext. The verification compares the attacker-supplied tag against the freshly computed HMAC using PHP/Zephir identity comparison (`!==`), which the Zephir compiler lowers to `!ZEPHIR_IS_IDENTICAL(...)` \u2014 a byte-wise `memcmp` that returns early on the first differing byte. The comparison time therefore depends on how many leading bytes of the supplied tag are correct, a classic MAC-verification timing side-channel. Every other secret/MAC comparison in the framework uses the constant-time `hash_equals()` (`zephir_hash_equals`) \u2014 the CSRF token check (`Security::checkToken`) and the JWT signature check (`Signer\\Hmac::verify`); `Crypt::decrypt` is the lone deviation.\n\n## Details\n\n### Vulnerable code\n\n`phalcon/Encryption/Crypt.zep:246` (Zephir source):\n\n```zephir\nif true === this-\u003euseSigning {\n    // Checks on the decrypted message digest using the HMAC method.\n    if digest !== hash_hmac(hashAlgorithm, padded, decryptKey, true) {\n        throw new Mismatch(\"Hash does not match.\");\n    }\n}\n```\n\nGenerated C --\u003e `ext/phalcon/encryption/crypt.zep.c:364-367`:\n\n```c\nZEPHIR_CALL_FUNCTION(\u0026_8$$7, \"hash_hmac\", NULL, 245, \u0026hashAlgorithm, \u0026padded, \u0026decryptKey, \u0026__$true);\n...\nif (!ZEPHIR_IS_IDENTICAL(\u0026digest, \u0026_8$$7)) {                 // \u003c-- non-constant-time\n    ZEPHIR_THROW_EXCEPTION_DEBUG_STR(..., \"Hash does not match.\", \"phalcon/Encryption/Crypt.zep\", 247);\n```\n\n`ZEPHIR_IS_IDENTICAL` --\u003e `zephir_is_identical()` (`ext/kernel/operators.c:472`) --\u003e Zend `is_identical_function` --\u003e for equal-length strings a `memcmp` that exits on the first mismatching byte (data-dependent timing).\n\n\n\n### Impact\n\nThe HMAC is the integrity/authentication tag of Phalcon\u0027s authenticated-encryption scheme. A successful timing attack (Keyczar/CVE-2009-0654-style: fix the IV+ciphertext so the target tag is constant, then recover it byte-by-byte from response timing) yields a tag the attacker can attach to a chosen IV+ciphertext so that `decrypt()` accepts it as authentic, defeating the integrity guarantee. Combined with CFB malleability (flipping a ciphertext byte flips the corresponding plaintext byte), an attacker who recovers the forging capability can tamper with the decrypted contents the application trusts (e.g. encrypted cookies carrying authorization/identity state). There is no confidentiality break by itself.\n\n## Suggested fix\n\nReplace the identity comparison with the constant-time helper already used elsewhere in the framework. In `phalcon/Encryption/Crypt.zep:246`:\n\n```zephir\n// before\nif digest !== hash_hmac(hashAlgorithm, padded, decryptKey, true) {\n    throw new Mismatch(\"Hash does not match.\");\n}\n// after\nif true !== hash_equals(hash_hmac(hashAlgorithm, padded, decryptKey, true), digest) {\n    throw new Mismatch(\"Hash does not match.\");\n}\n```\n\n`hash_equals()` returns false for unequal-length inputs, so it also covers the truncated-tag case. Optional further hardening: verify the MAC before unpadding (functionally moot here because `cryptUnpadText` never throws) and consider migrating the default toward an AEAD mode such as `aes-256-gcm`.\n\nAddressed Issue: \n\n- https://github.com/phalcon/cphalcon/issues/17090\n\nPatched Stream: \n\n- https://github.com/phalcon/cphalcon/issues/17090",
  "id": "GHSA-8jqh-95g6-7jpj",
  "modified": "2026-08-28T16:01:05Z",
  "published": "2026-08-28T16:01:05Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/security/advisories/GHSA-8jqh-95g6-7jpj"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54736"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/issues/17090"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/pull/17091"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/commit/ad53ab1b2e7ec59b3af92b0b37b8aaa099011137"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/phalcon/cphalcon"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/releases/tag/v5.14.1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Phalcon: Non-constant-time HMAC verification in `Encryption\\Crypt::decrypt` (timing side-channel)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.