{"id":"CVE-2026-54736","aliases":["EUVD-2026-43097","GHSA-8jqh-95g6-7jpj"],"status":"published","title":"Phalcon: Non-constant-time HMAC verification in `Encryption\\Crypt::decrypt` (timing side-channel)","description":"Phalcon is a high-performance, full-stack PHP framework. Prior to 5.14.1, Phalcon\\Encryption\\Crypt::decrypt compares the attacker-supplied HMAC tag against the freshly computed HMAC using PHP/Zephir identity comparison, which lowers to a byte-wise comparison that returns early on the first differing byte. This observable timing discrepancy can allow an attacker to recover a valid tag byte-by-byte and attach it to a chosen IV and ciphertext so that decrypt() accepts tampered encrypted content as authentic. This issue is fixed in version 5.14.1.","severity":{"version":"4.0","score":8.2,"vector":"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","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.0018,"percentile":0.07709,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-208","CWE-347"],"published":"2026-07-10T21:02:53Z","modified":"2026-10-28T02:30:38Z","fixes":[{"ecosystem":"Packagist","package":"phalcon/cphalcon","fixed_in":["5.14.1"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"phalcon/cphalcon","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"5.14.0"},{"fixed":"5.14.1"}]}]},{"source":"osv","ecosystem":"Packagist","package":"phalcon/cphalcon","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.14.1"}]}],"versions":["5.10.0","5.12.0","5.9.0","5.9.1","5.9.2","5.9.3","v3.0.0","v3.0.1","v3.0.2","v3.0.3","v3.0.4","v3.1.0","v3.1.0-alpha1","v3.1.0-alpha2","v3.1.1","v3.1.2","v3.2.0","v3.2.1","v3.2.2","v3.2.3","v3.2.4","v3.3.0","v3.3.1","v3.3.2","v3.4.0","v3.4.1","v3.4.2","v3.4.3","v3.4.4","v3.4.5","v4.0.0","v4.0.0-alpha.2","v4.0.0-alpha.3","v4.0.0-alpha.4","v4.0.0-alpha.5","v4.0.0-alpha1","v4.0.0-beta.1","v4.0.0-beta.2","v4.0.0-rc.1","v4.0.0-rc.2","v4.0.0-rc.3","v4.0.1","v4.0.2","v4.0.3","v4.0.4","v4.0.5","v4.0.6","v4.1.0","v4.1.1","v4.1.2","v4.1.3","v5.0.0","v5.0.0-alpha.1","v5.0.0-alpha.2","v5.0.0RC1","v5.0.0RC2","v5.0.0RC3","v5.0.0RC4","v5.0.0alpha3","v5.0.0alpha4","v5.0.0alpha5","v5.0.0alpha6","v5.0.0alpha7","v5.0.0beta1","v5.0.0beta2","v5.0.0beta3","v5.0.1","v5.0.2","v5.0.3","v5.0.4","v5.0.5","v5.1.0","v5.1.1","v5.1.2","v5.1.3","v5.1.4","v5.11.0","v5.11.1","v5.12.1","v5.13.0","v5.14.0","v5.2.0","v5.2.1","v5.2.2","v5.2.3","v5.3.0","v5.3.1","v5.4.0","v5.5.0","v5.6.0","v5.6.1","v5.6.2","v5.7.0","v5.8.0"]}],"products":[{"vendor":"phalcon","product":"cphalcon"}],"references":[{"url":"https://github.com/phalcon/cphalcon/security/advisories/GHSA-8jqh-95g6-7jpj","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/phalcon/cphalcon/issues/17090","tags":["x_refsource_MISC","report","web"]},{"url":"https://github.com/phalcon/cphalcon/pull/17091","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/phalcon/cphalcon/commit/ad53ab1b2e7ec59b3af92b0b37b8aaa099011137","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/phalcon/cphalcon/releases/tag/v5.14.1","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54736","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-8jqh-95g6-7jpj"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/54xxx/CVE-2026-54736.json","tags":["advisory"]},{"url":"https://github.com/phalcon/cphalcon","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-54736","title":"Phalcon: Non-constant-time HMAC verification in `Encryption\\Crypt::decrypt` (timing side-channel)","description":"Phalcon is a high-performance, full-stack PHP framework. Prior to 5.14.1, Phalcon\\Encryption\\Crypt::decrypt compares the attacker-supplied HMAC tag against the freshly computed HMAC using PHP/Zephir identity comparison, which lowers to a byte-wise comparison that returns early on the first differing byte. This observable timing discrepancy can allow an attacker to recover a valid tag byte-by-byte and attach it to a chosen IV and ciphertext so that decrypt() accepts tampered encrypted content as authentic. This issue is fixed in version 5.14.1.","status":"published","published":"2026-07-10T21:02:53.873Z","modified":"2026-07-13T18:01:40.822Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high","assessor":"cna"}],"cwes":["CWE-208","CWE-347"],"updated_at":"2026-11-02T13:38:09.774038Z"}],"euvd":[{"id":"EUVD-2026-43097","description":"Phalcon is a high-performance, full-stack PHP framework. Prior to 5.14.1, Phalcon\\Encryption\\Crypt::decrypt compares the attacker-supplied HMAC tag against the freshly computed HMAC using PHP/Zephir identity comparison, which lowers to a byte-wise comparison that returns early on the first differing byte. This observable timing discrepancy can allow an attacker to recover a valid tag byte-by-byte and attach it to a chosen IV and ciphertext so that decrypt() accepts tampered encrypted content as authentic. This issue is fixed in version 5.14.1.","status":"published","published":"2026-07-10T21:02:53Z","modified":"2026-07-13T18:01:40Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"updated_at":"2026-11-02T18:20:01.134466Z"}],"ghsa":[{"id":"GHSA-8jqh-95g6-7jpj","title":"Phalcon: Non-constant-time HMAC verification in `Encryption\\Crypt::decrypt` (timing side-channel)","description":"## 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(...)` — 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.\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'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.\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","status":"published","published":"2026-08-28T16:01:05Z","modified":"2026-08-28T16:01:08Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-208","CWE-347"],"updated_at":"2026-11-02T14:41:07.031507Z"}],"nvd":[{"id":"CVE-2026-54736","description":"Phalcon is a high-performance, full-stack PHP framework. Prior to 5.14.1, Phalcon\\Encryption\\Crypt::decrypt compares the attacker-supplied HMAC tag against the freshly computed HMAC using PHP/Zephir identity comparison, which lowers to a byte-wise comparison that returns early on the first differing byte. This observable timing discrepancy can allow an attacker to recover a valid tag byte-by-byte and attach it to a chosen IV and ciphertext so that decrypt() accepts tampered encrypted content as authentic. This issue is fixed in version 5.14.1.","status":"published","published":"2026-07-10T22:16:42.97Z","modified":"2026-10-25T15:10:00.147Z","cvss":[{"version":"4.0","score":8.2,"vector":"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"}],"cwes":["CWE-208","CWE-347"],"updated_at":"2026-11-02T14:12:28.771746Z"}],"osv":[{"id":"CVE-2026-54736","title":"Phalcon: Non-constant-time HMAC verification in `Encryption\\Crypt::decrypt` (timing side-channel)","description":"Phalcon is a high-performance, full-stack PHP framework. Prior to 5.14.1, Phalcon\\Encryption\\Crypt::decrypt compares the attacker-supplied HMAC tag against the freshly computed HMAC using PHP/Zephir identity comparison, which lowers to a byte-wise comparison that returns early on the first differing byte. This observable timing discrepancy can allow an attacker to recover a valid tag byte-by-byte and attach it to a chosen IV and ciphertext so that decrypt() accepts tampered encrypted content as authentic. This issue is fixed in version 5.14.1.","status":"published","published":"2026-07-10T21:02:53.873Z","modified":"2026-10-28T02:30:38.049191415Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-208","CWE-347"],"updated_at":"2026-11-02T15:45:10.818486Z"},{"id":"GHSA-8jqh-95g6-7jpj","title":"Phalcon: Non-constant-time HMAC verification in `Encryption\\Crypt::decrypt` (timing side-channel)","description":"## 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(...)` — 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.\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'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.\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","status":"published","published":"2026-08-28T16:01:05Z","modified":"2026-08-28T16:15:06.432419689Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-208","CWE-347"],"updated_at":"2026-11-02T15:46:42.437513Z"}]}}
