GitHub Security Advisory: GHSA-gqc5-xv7m-gcjq
Package: shopware/core (COMPOSER)
Severity: MODERATE (CVSS 5.3)
CWE: CWE-204: Observable Response Discrepancy
Vulnerable versions: < 6.6.10.15
Patched in: 6.6.10.15

Summary: Shopware has user enumeration via distinct error codes on Store API login endpoint

Details:
## Summary

The Store API login endpoint (`POST /store-api/account/login`) returns different error codes depending on whether the submitted email address belongs to a registered customer (`CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS`) or is unknown (`CHECKOUT__CUSTOMER_NOT_FOUND`). The "not found" response also echoes the probed email address. This allows an unauthenticated attacker to enumerate valid customer accounts. The storefront login controller correctly unifies both error paths, but the Store API does not — indicating an inconsistent defense.

## CWE

- **CWE-204**: Observable Response Discrepancy

## Description

### Distinct error codes leak account existence

The login flow in `AccountService::getCustomerByLogin()` calls `getCustomerByEmail()` first, which throws `CustomerNotFoundException` if the email is not found. If the email IS found but the password is wrong, a separate `BadCredentialsException` is thrown:

```php
// src/Core/Checkout/Customer/SalesChannel/AccountService.php:116-145
public function getCustomerByLogin(string $email, string $password, SalesChannelContext $context): CustomerEntity
{
    if ($this->isPasswordTooLong($password)) {
        throw CustomerException::badCredentials();
    }

    $customer = $this->getCustomerByEmail($email, $context);
    // ↑ Throws CustomerNotFoundException with CHECKOUT__CUSTOMER_NOT_FOUND if email unknown

    if ($customer->hasLegacyPassword()) {
        if (!$this->legacyPasswordVerifier->verify($password, $customer)) {
            throw CustomerException::badCredentials();
            // ↑ Throws BadCredentialsException with CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS
        }
        // ...
    }

    if ($customer->getPassword() === null
        || !password_verify($password, $customer->getPassword())) {
        throw CustomerException::badCredentials();
        // ↑ Same: CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS
    }
    // ...
}
```

The two exception types produce clearly distinguishable API responses:

**Email not registered:**
```json
{
  "errors": [{
    "status": "401",
    "code": "CHECKOUT__CUSTOMER_NOT_FOUND",
    "detail": "No matching customer for the email \"probe@example.com\" was found.",
    "meta": { "parameters": { "email": "probe@example.com" } }
  }]
}
```

**Email registered, wrong password:**
```json
{
  "errors": [{
    "status": "401",
    "code": "CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS",
    "detail": "Invalid username and/or password."
  }]
}
```

### Storefront is protected — Store API is not

The storefront login controller demonstrates that Shopware's developers are aware of this risk class. `AuthController::login()` catches both exceptions together and returns a generic error:

```php
// src/Storefront/Controller/AuthController.php:203
} catch (BadCredentialsException|CustomerNotFoundException) {
    // Unified handling — no distinction exposed to the user
}
```

The Store API `LoginRoute::login()` does NOT catch these exceptions. They propagate to the glo