CVE-2026-31888: Shopware has user enumeration via distinct error codes on Store API login endpoint

Published Mar 11, 2026
·
Updated

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 (CHECKOUTCUSTOMERAUTHBADCREDENTIALS) or is unknown (CHECKOUTCUSTOMERNOTFOUND). 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 CHECKOUTCUSTOMERNOTFOUND if email unknown

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

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

The two exception types produce clearly distinguishable API responses:

Email not registered: json { "errors": [{ "status": "401", "code": "CHECKOUTCUSTOMERNOTFOUND", "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": "CHECKOUTCUSTOMERAUTHBADCREDENTIALS", "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 global ErrorResponseFactory, which serializes the distinct error codes into the JSON response:

php // src/Core/Checkout/Customer/SalesChannel/LoginRoute.php:54-58 $token = $this->accountService->loginByCredentials( $email, (string) $data->get('password'), $context ); // No try/catch — exceptions propagate with distinct codes

This inconsistency confirms the Store API exposure is an oversight, not a design decision.

Rate limiting is present but insufficient for enumeration

The login route has rate limiting (LoginRoute.php:47-51) keyed on strtolower($email) . '-' . $clientIp. This slows bulk enumeration but does not prevent it because:

1. The attacker only needs one request per email to determine existence 2. The rate limit key includes the IP, so rotating IPs resets the counter 3. The rate limiter is designed to prevent brute-force password guessing, not single-probe enumeration

Impact

- Customer email enumeration: An attacker can confirm whether specific email addresses are registered as customers, enabling targeted attacks - Phishing enablement: Confirmed customer emails can be targeted with store-specific phishing campaigns (e.g., fake order confirmations, password reset lures) - Credential stuffing optimization: Attackers with breached credential databases can first filter for valid emails before attempting password guesses, improving efficiency against rate limits - Privacy violation: Confirms an individual's association with a specific store, which may be sensitive depending on the store's nature (e.g., medical supplies, adult products) - Email reflection: The CHECKOUTCUSTOMERNOTFOUND response echoes the probed email in the detail and meta.parameters.email fields, which could be leveraged in reflected content attacks

Recommended Remediation

Option 1: Catch both exceptions in LoginRoute and throw a unified error (Preferred)

Apply the same pattern already used in the storefront controller:

php // src/Core/Checkout/Customer/SalesChannel/LoginRoute.php public function login(#[\SensitiveParameter] RequestDataBag $data, SalesChannelContext $context): ContextTokenResponse { EmailIdnConverter::encodeDataBag($data); $email = (string) $data->get('email', $data->get('username'));

if ($this->requestStack->getMainRequest() !== null) { $cacheKey = strtolower($email) . '-' . $this->requestStack->getMainRequest()->getClientIp();

try { $this->rateLimiter->ensureAccepted(RateLimiter::LOGINROUTE, $cacheKey); } catch (RateLimitExceededException $exception) { throw CustomerException::customerAuthThrottledException($exception->getWaitTime(), $exception); } }

try { $token = $this->accountService->loginByCredentials( $email, (string) $data->get('password'), $context ); } catch (CustomerNotFoundException) { // Normalize to the same exception as bad credentials throw CustomerException::badCredentials(); }

if (isset($cacheKey)) { $this->rateLimiter->reset(RateLimiter::LOGINROUTE, $cacheKey); }

return new ContextTokenResponse($token); }

This ensures both "not found" and "bad credentials" return the same CHECKOUTCUSTOMERAUTHBADCREDENTIALS code and generic message.

Option 2: Unify at the AccountService layer

For defense in depth, change AccountService::getCustomerByLogin() to throw BadCredentialsException instead of letting CustomerNotFoundException propagate:

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

try { $customer = $this->getCustomerByEmail($email, $context); } catch (CustomerNotFoundException) { throw CustomerException::badCredentials(); }

// ... rest of password verification }

This protects all callers of getCustomerByLogin() regardless of how they handle exceptions. Note: getCustomerByEmail() is also called independently (e.g., password recovery), so that method should continue to throw CustomerNotFoundException for internal use — the normalization should happen at the login boundary.

Additional: Fix registration endpoint

The registration endpoint (POST /store-api/account/register) also leaks email existence via CUSTOMEREMAILNOTUNIQUE. For complete remediation, consider returning a generic success response and sending a notification email to the existing address instead.

Credit

This vulnerability was discovered and reported by bugbunny.ai.

Other sources

Shopware is an open commerce platform. Prior to 6.7.8.1 and 6.6.10.15, 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 (CHECKOUTCUSTOMERAUTHBADCREDENTIALS) or is unknown (CHECKOUTCUSTOMERNOTFOUND). 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. This vulnerability is fixed in 6.7.8.1 and 6.6.10.15.

MITRE

Affected Software

8 affected componentsFixes available
Shopware Shopware<6.7.8.1
Shopware Shopware<6.6.10.15
composer/shopware/core<6.6.10.15
6.6.10.15
composer/shopware/core>=6.7.0.0<6.7.8.1
6.7.8.1
composer/shopware/platform<6.6.10.14
6.6.10.14
composer/shopware/platform>=6.7.0.0<6.7.8.1
6.7.8.1
Shopware Shopware<6.6.10.15
Shopware Shopware>=6.7.0.0<6.7.8.1

Event History

Mar 11, 2026
CVE Published
via MITRE·06:53 PM
Data Sourced
via MITRE·06:53 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·07:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·07:16 PM
Affected Software
Advisory Published
via GitHub·07:23 PM
Data Sourced
via GitHub·07:23 PM
DescriptionSeverityWeaknessAffected Software
Jan 29, 58178
Event
via FIRST·09:01 PM
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-31888?

CVE-2026-31888 has been classified as a high severity vulnerability.

2

How do I fix CVE-2026-31888?

To mitigate CVE-2026-31888, upgrade to Shopware version 6.7.8.1 or 6.6.10.15 or later.

3

What is CVE-2026-31888?

CVE-2026-31888 is a vulnerability in Shopware that allows user enumeration through distinct error codes on the Store API login endpoint.

4

Which versions of Shopware are affected by CVE-2026-31888?

CVE-2026-31888 affects Shopware versions prior to 6.6.10.15 and 6.7.8.1.

5

Is user data at risk with CVE-2026-31888?

Yes, CVE-2026-31888 may allow attackers to perform user enumeration, potentially compromising account security.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203