CVE-2022-24748 (PHP, published 2022-03-09T23:15Z)
Description: value: 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 v3 base score: 7.5)
Weakness: CWE-863 \u2014 Incorrect Authorization
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.
Repository: https://github.com/shopware/core
Files changed:
Framework/Api/Controller/SalesChannelProxyController.php
Framework/Test/Api/Controller/SalesChannelProxyControllerTest.php
Migration/Migration1646397836UpdateRolePrivilegesOfOrderCreatorUpdateRolePrivilegesOfOrderCreator.php
Migration/Test/Migration1646397836UpdateRolePrivilegesOfOrderCreatorTest.php
Migration/V6_4/Migration1646397836UpdateRolePrivilegesOfOrderCreator.php
Commit message: NEXT-20305 - Secure proxy route to switch customer with ACL


Vulnerable code:
                    $testOrderOnly ? $orderPrivileges : [CreditOrderLineItemListener::ACL_ORDER_CREATE_DISCOUNT_PRIVILEGE],

Fixed code:
use Shopware\Core\Framework\Routing\Annotation\Acl;
     * @Acl({"api_proxy_switch-customer"})
                'api_proxy_switch-customer',
                    $testOrderOnly ? $orderPrivileges : ['api_proxy_switch-customer', CreditOrderLineItemListener::ACL_ORDER_CREATE_DISCOUNT_PRIVILEGE],
<?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
{
}
<?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);
    }
}
<?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);
    }
}