CVE-2022-24748 (published 2022-03-09T23:15Z)

Shopware is an open commerce platform based on the Symfony php Framework and the Vue javascript framework. In versions prior to 6.4.8.2 it is possible to modify customers and to create orders without App Permission. This issue is a result of improper api route checking. Users are advised to upgrade to version 6.4.8.2. There are no known workarounds.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-863
cwe_name: Incorrect Authorization
description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are incorrectly applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/shopware/core
commit_details: repo_url: https://github.com/shopware/core
author: Frederik Schmitt
author_date: 2022-03-02 12:05:37+01:00
msg: NEXT-20305 - Secure proxy route to switch customer with ACL
repository: repo_url: https://github.com/shopware/core
repo_name: shopware/core
repo_language: PHP
owner: shopware
file_changes: filename: SalesChannelProxyController.php
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,7 @@ use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaI
 use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
 use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
 use Shopware\Core\Framework\DataAbstractionLayer\Validation\EntityExists;
+use Shopware\Core\Framework\Routing\Annotation\Acl;
 use Shopware\Core\Framework\Routing\Annotation\RouteScope;
 use Shopware\Core\Framework\Routing\Annotation\Since;
 use Shopware\Core\Framework\Routing\Exception\MissingRequestParameterException;
@@ -167,6 +168,7 @@ class SalesChannelProxyController extends AbstractController
     /**
      * @Since("6.2.0.0")
      * @Route("/api/_proxy/switch-customer", name="api.proxy.switch-customer", methods={"PATCH"})
+     * @Acl({"api_proxy_switch-customer"})
      *
      * @throws InconsistentCriteriaIdsException
      * @throws InvalidSalesChannelIdException

programming_language: PHP
filename: SalesChannelProxyControllerTest.php
change_type: ModificationType.MODIFY
diff: @@ -1062,6 +1062,7 @@ class SalesChannelProxyControllerTest extends TestCase
             static::assertSame('credit', $cart['lineItems'][1]['type']);
 
             $orderPrivileges = [
+                'api_proxy_switch-customer',
                 'order:create',
                 'order_customer:create',
                 'order_address:create',
@@ -1075,7 +1076,7 @@ class SalesChannelProxyControllerTest extends TestCase
             foreach ([true, false] as $testOrderOnly) {
                 TestUser::createNewTestUser(
                     $browser->getContainer()->get(Connection::class),
-                    $testOrderOnly ? $orderPrivileges : [CreditOrderLineItemListener::ACL_ORDER_CREATE_DISCOUNT_PRIVILEGE],
+                    $testOrderOnly ? $orderPrivileges : ['api_proxy_switch-customer', CreditOrderLineItemListener::ACL_ORDER_CREATE_DISCOUNT_PRIVILEGE],
                 )->authorizeBrowser($browser);
                 $browser->request('POST', $this->getCreateOrderApiUrl($salesChannelContext->getSalesChannel()->getId()));
 

programming_language: PHP
filename: Migration1646397836UpdateRolePrivilegesOfOrderCreatorUpdateRolePrivilegesOfOrderCreator.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace Shopware\Core\Migration;
+
+/**
+ * @deprecated tag:v6.5.0 - Will be deleted. Migrations are now namespaced by major version
+ */
+class Migration1646397836UpdateRolePrivilegesOfOrderCreatorUpdateRolePrivilegesOfOrderCreator extends \Shopware\Core\Migration\V6_4\Migration1646397836UpdateRolePrivilegesOfOrderCreator
+{
+}

programming_language: PHP
filename: Migration1646397836UpdateRolePrivilegesOfOrderCreatorTest.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,64 @@
+<?php declare(strict_types=1);
+
+namespace Shopware\Core\Migration\Test;
+
+use Doctrine\DBAL\Connection;
+use PHPUnit\Framework\TestCase;
+use Shopware\Core\Framework\Api\Acl\Role\AclRoleEntity;
+use Shopware\Core\Framework\Context;
+use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
+use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
+use Shopware\Core\Framework\Uuid\Uuid;
+use Shopware\Core\Migration\V6_4\Migration1646397836UpdateRolePrivilegesOfOrderCreator;
+
+class Migration1646397836UpdateRolePrivilegesOfOrderCreatorTest extends TestCase
+{
+    use IntegrationTestBehaviour;
+
+    public function testNewPermissionsAreAdded(): void
+    {
+        $repo = $this->getContainer()->get('acl_role.repository');
+        $connection = $this->getContainer()->get(Connection::class);
+
+        $id = Uuid::randomHex();
+        $context = Context::createDefaultContext();
+        $repo->create([[
+            'id' => $id,
+            'name' => 'test',
+            'privileges' => ['order.creator'],
+        ]], $context);
+
+        $migration = new Migration1646397836UpdateRolePrivilegesOfOrderCreator();
+        $migration->update($connection);
+
+        /** @var AclRoleEntity $role */
+        $role = $repo->search(new Criteria([$id]), $context)->first();
+        static::assertNotNull($role);
+
+        static::assertContains('api_proxy_switch-customer', $role->getPrivileges());
+    }
+
+    public function testUnrelatedRolesAreNotUpdated(): void
+    {
+        $repo = $this->getContainer()->get('acl_role.repository');
+        $connection = $this->getContainer()->get(Connection::class);
+
+        $id = Uuid::randomHex();
+        $context = Context::createDefaultContext();
+        $privileges = ['order:create', 'order:read', 'order:update', 'order:delete'];
+        $repo->create([[
+            'id' => $id,
+            'name' => 'test',
+            'privileges' => $privileges,
+        ]], $context);
+
+        $before = $connection->fetchAssociative('SELECT * FROM `acl_role` WHERE id = :id', ['id' => Uuid::fromHexToBytes($id)]);
+
+        $migration = new Migration1646397836UpdateRolePrivilegesOfOrderCreator();
+        $migration->update($connection);
+
+        $after = $connection->fetchAssociative('SELECT * FROM `acl_role` WHERE id = :id', ['id' => Uuid::fromHexToBytes($id)]);
+
+        static::assertSame($before, $after);
+    }
+}

programming_language: PHP
filename: Migration1646397836UpdateRolePrivilegesOfOrderCreator.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,56 @@
+<?php declare(strict_types=1);
+
+namespace Shopware\Core\Migration\V6_4;
+
+use Doctrine\DBAL\Connection;
+use Shopware\Core\Defaults;
+use Shopware\Core\Framework\Migration\MigrationStep;
+
+class Migration1646397836UpdateRolePrivilegesOfOrderCreator extends MigrationStep
+{
+    public const NEW_PRIVILEGES = [
+        'order.creator' => [
+            'api_proxy_switch-customer',
+        ],
+    ];
+
+    public function getCreationTimestamp(): int
+    {
+        return 1646397836;
+    }
+
+    public function update(Connection $connection): void
+    {
+        $roles = $connection->fetchAllAssociative('SELECT * from `acl_role`');
+
+        foreach ($roles as $role) {
+            $currentPrivileges = \json_decode($role['privileges'], true, 512, \JSON_THROW_ON_ERROR);
+            $newPrivileges = array_values($this->fixRolePrivileges($currentPrivileges));
+
+            if ($currentPrivileges === $newPrivileges) {
+                continue;
+            }
+
+            $role['privileges'] = json_encode($newPrivileges);
+            $role['updated_at'] = (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_FORMAT);
+
+            $connection->update('acl_role', $role, ['id' => $role['id']]);
+        }
+    }
+
+    public function updateDestructive(Connection $connection): void
+    {
+        // implement update destructive
+    }
+
+    private function fixRolePrivileges(array $rolePrivileges): array
+    {
+        foreach (self::NEW_PRIVILEGES as $key => $new) {
+            if (\in_array($key, $rolePrivileges, true)) {
+                $rolePrivileges = array_merge($rolePrivileges, $new);
+            }
+        }
+
+        return array_unique($rolePrivileges);
+    }
+}

programming_language: PHP