CWE-204
AllowedObservable Response Discrepancy
Abstraction: Base · Status: Incomplete
The product provides different responses to incoming requests in a way that reveals internal state information to an unauthorized actor outside of the intended control sphere.
297 vulnerabilities reference this CWE, most recent first.
GHSA-FGMC-2HQJ-86V4
Vulnerability from github – Published: 2026-06-05 16:45 – Updated: 2026-07-09 21:06Impact
Vantage6 currently provides an initial user with username root and password root. This is not ideal for the following reasons:
- Attackers know that almost all vantage6 servers have a user with username root that probably has admin rights
- The initial password is very weak and it is possible that administrators forget to reset it.
Patches
No
Workarounds
It is possible to delete the root user after it has been used to create other users
References
We could consider doing this like mongodb
Additional info
Luis uses the following patch to mitigate it:
diff --git a/vantage6-server/vantage6/server/__init__.py b/vantage6-server/vantage6/server/__init__.py
index ea362c1e..c6dcbbd9 100644
--- a/vantage6-server/vantage6/server/__init__.py
+++ b/vantage6-server/vantage6/server/__init__.py
@@ -618,18 +618,30 @@ class ServerApp:
# TODO use constant instead of 'Root' literal
root = db.Role.get_by_name("Root")
- log.warn(
- f"Creating root user: "
- f"username={SUPER_USER_INFO['username']}, "
- f"password={SUPER_USER_INFO['password']}"
- )
+ # Temporary patch
+ # read initial root password from file (docker secret) if provided
+ # TODO: This is a workaround so we don't have an insecure vserver
+ # at the start. Ideally, we would provide an already hashed
+ # password. But as hashing is implemented via @validates on
+ # the field 'password', there isn't a nice way around this.
+ if os.environ.get("V6_INITIAL_ROOT_PASSWORD_FILE"):
+ with open(
+ os.environ.get("V6_INITIAL_ROOT_PASSWORD_FILE")
+ ) as password_file:
+ initial_root_password = password_file.read().strip()
+ log.info(
+ f"Creating root user with password provided via V6_INITIAL_ROOT_PASSWORD_FILE"
+ )
+ else:
+ initial_root_password = SUPER_USER_INFO["password"]
+ log.warn(f"Creating root user with default credentials!")
user = db.User(
username=SUPER_USER_INFO["username"],
roles=[root],
organization=org,
email="root@domain.ext",
- password=SUPER_USER_INFO["password"],
+ password=initial_root_password,
failed_login_attempts=0,
last_login_attempt=None,
)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.3"
},
"package": {
"ecosystem": "PyPI",
"name": "vantage6"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.0.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54445"
],
"database_specific": {
"cwe_ids": [
"CWE-1393",
"CWE-204"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-05T16:45:22Z",
"nvd_published_at": "2026-06-17T23:17:05Z",
"severity": "MODERATE"
},
"details": "### Impact\nVantage6 currently provides an initial user with username `root` and password `root`. This is not ideal for the following reasons:\n- Attackers know that almost all vantage6 servers have a user with username `root` that probably has admin rights\n- The initial password is very weak and it is possible that administrators forget to reset it.\n\n### Patches\nNo\n\n### Workarounds\nIt is possible to delete the `root` user after it has been used to create other users\n\n### References\nWe could consider doing this like [mongodb](https://hub.docker.com/_/mongo)\n\n### Additional info\n\nLuis uses the following patch to mitigate it:\n```diff\ndiff --git a/vantage6-server/vantage6/server/__init__.py b/vantage6-server/vantage6/server/__init__.py\nindex ea362c1e..c6dcbbd9 100644\n--- a/vantage6-server/vantage6/server/__init__.py\n+++ b/vantage6-server/vantage6/server/__init__.py\n@@ -618,18 +618,30 @@ class ServerApp:\n # TODO use constant instead of \u0027Root\u0027 literal\n root = db.Role.get_by_name(\"Root\")\n \n- log.warn(\n- f\"Creating root user: \"\n- f\"username={SUPER_USER_INFO[\u0027username\u0027]}, \"\n- f\"password={SUPER_USER_INFO[\u0027password\u0027]}\"\n- )\n+ # Temporary patch\n+ # read initial root password from file (docker secret) if provided\n+ # TODO: This is a workaround so we don\u0027t have an insecure vserver\n+ # at the start. Ideally, we would provide an already hashed\n+ # password. But as hashing is implemented via @validates on\n+ # the field \u0027password\u0027, there isn\u0027t a nice way around this.\n+ if os.environ.get(\"V6_INITIAL_ROOT_PASSWORD_FILE\"):\n+ with open(\n+ os.environ.get(\"V6_INITIAL_ROOT_PASSWORD_FILE\")\n+ ) as password_file:\n+ initial_root_password = password_file.read().strip()\n+ log.info(\n+ f\"Creating root user with password provided via V6_INITIAL_ROOT_PASSWORD_FILE\"\n+ )\n+ else:\n+ initial_root_password = SUPER_USER_INFO[\"password\"]\n+ log.warn(f\"Creating root user with default credentials!\")\n \n user = db.User(\n username=SUPER_USER_INFO[\"username\"],\n roles=[root],\n organization=org,\n email=\"root@domain.ext\",\n- password=SUPER_USER_INFO[\"password\"],\n+ password=initial_root_password,\n failed_login_attempts=0,\n last_login_attempt=None,\n )\n```",
"id": "GHSA-fgmc-2hqj-86v4",
"modified": "2026-07-09T21:06:52Z",
"published": "2026-06-05T16:45:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/vantage6/vantage6/security/advisories/GHSA-fgmc-2hqj-86v4"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54445"
},
{
"type": "WEB",
"url": "https://github.com/vantage6/vantage6/issues/1932"
},
{
"type": "PACKAGE",
"url": "https://github.com/vantage6/vantage6"
},
{
"type": "WEB",
"url": "https://github.com/vantage6/vantage6/blob/main/docs/release_notes.rst#500"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Vantage6: Set admin user and password from environment or configuration"
}
GHSA-FVCV-8G7R-6893
Vulnerability from github – Published: 2026-04-09 15:35 – Updated: 2026-04-13 21:30An observable response discrepancy vulnerability in the SonicWall SMA1000 series appliances allows a remote attacker to enumerate SSL VPN user credentials.
{
"affected": [],
"aliases": [
"CVE-2026-4113"
],
"database_specific": {
"cwe_ids": [
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-09T15:16:13Z",
"severity": "HIGH"
},
"details": "An observable response discrepancy vulnerability in the SonicWall SMA1000 series appliances allows a remote attacker to enumerate SSL VPN user credentials.",
"id": "GHSA-fvcv-8g7r-6893",
"modified": "2026-04-13T21:30:39Z",
"published": "2026-04-09T15:35:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-4113"
},
{
"type": "WEB",
"url": "https://psirt.global.sonicwall.com/vuln-detail/SNWLID-2026-0003"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-G4WF-PJGV-XRC5
Vulnerability from github – Published: 2025-05-20 18:30 – Updated: 2025-05-20 18:30Failed login response could be different depending on whether the username was local or central.
{
"affected": [],
"aliases": [
"CVE-2025-48015"
],
"database_specific": {
"cwe_ids": [
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-05-20T16:15:35Z",
"severity": "LOW"
},
"details": "Failed login response could be different depending on whether the username was local or central.",
"id": "GHSA-g4wf-pjgv-xrc5",
"modified": "2025-05-20T18:30:57Z",
"published": "2025-05-20T18:30:57Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-48015"
},
{
"type": "WEB",
"url": "https://selinc.com/products/software/latest-software-versions"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-G55H-GQ76-W8V9
Vulnerability from github – Published: 2023-10-31 18:31 – Updated: 2023-11-08 18:30An issue discovered in Elenos ETG150 FM transmitter v3.12 allows attackers to enumerate user accounts based on server responses when credentials are submitted.
{
"affected": [],
"aliases": [
"CVE-2023-37831"
],
"database_specific": {
"cwe_ids": [
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-10-31T18:15:08Z",
"severity": "MODERATE"
},
"details": "An issue discovered in Elenos ETG150 FM transmitter v3.12 allows attackers to enumerate user accounts based on server responses when credentials are submitted.",
"id": "GHSA-g55h-gq76-w8v9",
"modified": "2023-11-08T18:30:31Z",
"published": "2023-10-31T18:31:44Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-37831"
},
{
"type": "WEB",
"url": "https://github.com/strik3r0x1/Vulns/blob/main/User%20enumeration%20-%20Elenos.md"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-G58R-FCX4-MV8R
Vulnerability from github – Published: 2025-01-14 15:30 – Updated: 2025-01-14 15:30An observable response discrepancy vulnerability [CWE-204] in FortiClientEMS 7.4.0, 7.2.0 through 7.2.4, 7.0 all versions, and FortiSOAR 7.5.0, 7.4.0 through 7.4.4, 7.3.0 through 7.3.2, 7.2 all versions, 7.0 all versions, 6.4 all versions may allow an unauthenticated attacker to enumerate valid users via observing login request responses.
{
"affected": [],
"aliases": [
"CVE-2024-36510"
],
"database_specific": {
"cwe_ids": [
"CWE-203",
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-01-14T14:15:30Z",
"severity": "MODERATE"
},
"details": "An observable response discrepancy vulnerability [CWE-204] in FortiClientEMS 7.4.0, 7.2.0 through 7.2.4, 7.0 all versions, and FortiSOAR 7.5.0, 7.4.0 through 7.4.4, 7.3.0 through 7.3.2, 7.2 all versions, 7.0 all versions, 6.4 all versions may allow an unauthenticated attacker to enumerate valid users via observing login request responses.",
"id": "GHSA-g58r-fcx4-mv8r",
"modified": "2025-01-14T15:30:53Z",
"published": "2025-01-14T15:30:53Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-36510"
},
{
"type": "WEB",
"url": "https://fortiguard.fortinet.com/psirt/FG-IR-24-071"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-GQ4G-FPC9-VJFQ
Vulnerability from github – Published: 2026-07-07 23:39 – Updated: 2026-07-07 23:40Impact
Webauthn\SimpleFakeCredentialGenerator is the library-provided default implementation of the FakeCredentialGenerator interface. It returns a stable list of decoy PublicKeyCredentialDescriptor objects for a given username so that an assertion request for an unknown user looks the same as a request for a real one, which mitigates username enumeration.
The generator derives the whole decoy list from a single seed:
$seed = hash('sha256', $username . $this->secret, true);
When it is constructed without a secret (its constructor default, $secret = ''), the seed depends only on the username. The username is attacker-chosen and the algorithm is public, so an unauthenticated requester can recompute the exact, byte-for-byte decoy list the server returns for any username. The attacker then compares a probed username's response against the locally computed list and decides whether the account is real or fake, which is precisely the distinction the mechanism is meant to hide.
With any non-empty secret the seed becomes a value the attacker cannot evaluate and the mitigation holds. The defect is the empty default, not the algorithm.
Affected configurations
- Direct use of the library (
web-auth/webauthn-lib) whereSimpleFakeCredentialGeneratoris instantiated without a secret. - Any integration that wires the generator with an empty secret.
The Symfony bundle is not affected with its default configuration: it injects the application secret (kernel.secret) into the generator, so out-of-the-box deployments already use a non-empty secret. Deployments that set an empty kernel.secret are affected.
Patches
Fixed in 5.3.5. The generator now emits a deprecation when it is constructed without a secret, which surfaces the misconfiguration in logs and the Symfony profiler. A non-empty secret will be required in 6.0.0. The recommended remediation is to always provide a non-empty, deployment-specific secret.
Workarounds
Construct SimpleFakeCredentialGenerator with a non-empty secret value (for example the application secret), or provide a custom FakeCredentialGenerator implementation seeded with a secret.
Proof of concept
<?php
declare(strict_types=1);
require $src . '/PublicKeyCredentialDescriptor.php';
require $src . '/FakeCredentialGenerator.php';
require $src . '/SimpleFakeCredentialGenerator.php';
use Webauthn\PublicKeyCredentialDescriptor;
use Webauthn\SimpleFakeCredentialGenerator;
$username = 'alice@example.com';
// 1. The "server" runs the library default wiring (cache=null, secret='').
$server = new SimpleFakeCredentialGenerator();
$refl = new ReflectionMethod(SimpleFakeCredentialGenerator::class, 'generateCredentials');
$refl->setAccessible(true);
$serverDescriptors = $refl->invoke($server, $username);
// 2. The "attacker" recomputes the same algorithm, knowing only the username.
function attackerRecompute(string $username): array {
$transports = [
PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_USB,
PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_NFC,
PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_BLE,
PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_HYBRID,
PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_INTERNAL,
PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_SMART_CARD,
];
$seed = hash('sha256', $username . '', true); // empty secret
$count = (ord($seed[0]) % 3) + 1;
$out = [];
for ($i = 0; $i < $count; $i++) {
$credSeed = hash('sha256', $seed . pack('N', $i), true);
$transportCount = (ord($credSeed[0]) % 2) + 1;
$sel = [];
for ($j = 0; $j < $transportCount; $j++) {
$sel[] = $transports[ord($credSeed[$j + 1]) % count($transports)];
}
$sel = array_values(array_unique($sel));
$out[] = ['type' => PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,
'id' => hash('sha256', $credSeed . $username), 'transports' => $sel];
}
return $out;
}
// 3. The two lists match byte-for-byte, so the decoy is reproducible.
// The same call with a non-empty secret diverges, confirming the defect
// is the default value rather than the algorithm.
With the default empty secret the library's fake-credential list is bit-for-bit reproducible from the public username alone, which defeats the username enumeration mitigation. The same call with a non-empty secret diverges.
Severity
Low. The decoy responses are still well-formed and the issue only re-enables username enumeration, and only when the generator is used without a secret (which is not the case for default Symfony bundle deployments).
Credits
Found during an internal security audit of the project.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "web-auth/webauthn-lib"
},
"ranges": [
{
"events": [
{
"introduced": "4.9.0"
},
{
"fixed": "5.3.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-204",
"CWE-330"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-07T23:39:43Z",
"nvd_published_at": null,
"severity": "LOW"
},
"details": "## Impact\n\n`Webauthn\\SimpleFakeCredentialGenerator` is the library-provided default implementation of the `FakeCredentialGenerator` interface. It returns a stable list of decoy `PublicKeyCredentialDescriptor` objects for a given username so that an assertion request for an unknown user looks the same as a request for a real one, which mitigates username enumeration.\n\nThe generator derives the whole decoy list from a single seed:\n\n```php\n$seed = hash(\u0027sha256\u0027, $username . $this-\u003esecret, true);\n```\n\nWhen it is constructed without a secret (its constructor default, `$secret = \u0027\u0027`), the seed depends only on the username. The username is attacker-chosen and the algorithm is public, so an unauthenticated requester can recompute the exact, byte-for-byte decoy list the server returns for any username. The attacker then compares a probed username\u0027s response against the locally computed list and decides whether the account is real or fake, which is precisely the distinction the mechanism is meant to hide.\n\nWith any non-empty secret the seed becomes a value the attacker cannot evaluate and the mitigation holds. The defect is the empty default, not the algorithm.\n\n## Affected configurations\n\n- Direct use of the library (`web-auth/webauthn-lib`) where `SimpleFakeCredentialGenerator` is instantiated without a secret.\n- Any integration that wires the generator with an empty secret.\n\nThe Symfony bundle is not affected with its default configuration: it injects the application secret (`kernel.secret`) into the generator, so out-of-the-box deployments already use a non-empty secret. Deployments that set an empty `kernel.secret` are affected.\n\n## Patches\n\nFixed in 5.3.5. The generator now emits a deprecation when it is constructed without a secret, which surfaces the misconfiguration in logs and the Symfony profiler. A non-empty secret will be required in 6.0.0. The recommended remediation is to always provide a non-empty, deployment-specific secret.\n\n## Workarounds\n\nConstruct `SimpleFakeCredentialGenerator` with a non-empty secret value (for example the application secret), or provide a custom `FakeCredentialGenerator` implementation seeded with a secret.\n\n## Proof of concept\n\n```php\n\u003c?php\ndeclare(strict_types=1);\n\nrequire $src . \u0027/PublicKeyCredentialDescriptor.php\u0027;\nrequire $src . \u0027/FakeCredentialGenerator.php\u0027;\nrequire $src . \u0027/SimpleFakeCredentialGenerator.php\u0027;\n\nuse Webauthn\\PublicKeyCredentialDescriptor;\nuse Webauthn\\SimpleFakeCredentialGenerator;\n\n$username = \u0027alice@example.com\u0027;\n\n// 1. The \"server\" runs the library default wiring (cache=null, secret=\u0027\u0027).\n$server = new SimpleFakeCredentialGenerator();\n$refl = new ReflectionMethod(SimpleFakeCredentialGenerator::class, \u0027generateCredentials\u0027);\n$refl-\u003esetAccessible(true);\n$serverDescriptors = $refl-\u003einvoke($server, $username);\n\n// 2. The \"attacker\" recomputes the same algorithm, knowing only the username.\nfunction attackerRecompute(string $username): array {\n $transports = [\n PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_USB,\n PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_NFC,\n PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_BLE,\n PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_HYBRID,\n PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_INTERNAL,\n PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_SMART_CARD,\n ];\n $seed = hash(\u0027sha256\u0027, $username . \u0027\u0027, true); // empty secret\n $count = (ord($seed[0]) % 3) + 1;\n $out = [];\n for ($i = 0; $i \u003c $count; $i++) {\n $credSeed = hash(\u0027sha256\u0027, $seed . pack(\u0027N\u0027, $i), true);\n $transportCount = (ord($credSeed[0]) % 2) + 1;\n $sel = [];\n for ($j = 0; $j \u003c $transportCount; $j++) {\n $sel[] = $transports[ord($credSeed[$j + 1]) % count($transports)];\n }\n $sel = array_values(array_unique($sel));\n $out[] = [\u0027type\u0027 =\u003e PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY,\n \u0027id\u0027 =\u003e hash(\u0027sha256\u0027, $credSeed . $username), \u0027transports\u0027 =\u003e $sel];\n }\n return $out;\n}\n\n// 3. The two lists match byte-for-byte, so the decoy is reproducible.\n// The same call with a non-empty secret diverges, confirming the defect\n// is the default value rather than the algorithm.\n```\n\nWith the default empty secret the library\u0027s fake-credential list is bit-for-bit reproducible from the public username alone, which defeats the username enumeration mitigation. The same call with a non-empty secret diverges.\n\n## Severity\n\nLow. The decoy responses are still well-formed and the issue only re-enables username enumeration, and only when the generator is used without a secret (which is not the case for default Symfony bundle deployments).\n\n## Credits\n\nFound during an internal security audit of the project.",
"id": "GHSA-gq4g-fpc9-vjfq",
"modified": "2026-07-07T23:40:01Z",
"published": "2026-07-07T23:39:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/web-auth/webauthn-framework/security/advisories/GHSA-gq4g-fpc9-vjfq"
},
{
"type": "PACKAGE",
"url": "https://github.com/web-auth/webauthn-framework"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "Webauthn: SimpleFakeCredentialGenerator with an empty secret produces predictable fake credentials, weakening username enumeration protection"
}
GHSA-GQC5-XV7M-GCJQ
Vulnerability from github – Published: 2026-03-11 19:23 – Updated: 2026-03-11 21:37Summary
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:
// 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:
{
"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:
{
"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:
// 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:
// 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:
- The attacker only needs one request per email to determine existence
- The rate limit key includes the IP, so rotating IPs resets the counter
- 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
CHECKOUT__CUSTOMER_NOT_FOUNDresponse echoes the probed email in thedetailandmeta.parameters.emailfields, 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:
// 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::LOGIN_ROUTE, $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::LOGIN_ROUTE, $cacheKey);
}
return new ContextTokenResponse($token);
}
This ensures both "not found" and "bad credentials" return the same CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS 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:
// 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 CUSTOMER_EMAIL_NOT_UNIQUE. 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.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "shopware/platform"
},
"ranges": [
{
"events": [
{
"introduced": "6.7.0.0"
},
{
"fixed": "6.7.8.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "shopware/platform"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "6.6.10.14"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "shopware/core"
},
"ranges": [
{
"events": [
{
"introduced": "6.7.0.0"
},
{
"fixed": "6.7.8.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "shopware/core"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "6.6.10.15"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-31888"
],
"database_specific": {
"cwe_ids": [
"CWE-204"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-11T19:23:49Z",
"nvd_published_at": "2026-03-11T19:16:05Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nThe 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 \u2014 indicating an inconsistent defense.\n\n## CWE\n\n- **CWE-204**: Observable Response Discrepancy\n\n## Description\n\n### Distinct error codes leak account existence\n\nThe 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:\n\n```php\n// src/Core/Checkout/Customer/SalesChannel/AccountService.php:116-145\npublic function getCustomerByLogin(string $email, string $password, SalesChannelContext $context): CustomerEntity\n{\n if ($this-\u003eisPasswordTooLong($password)) {\n throw CustomerException::badCredentials();\n }\n\n $customer = $this-\u003egetCustomerByEmail($email, $context);\n // \u2191 Throws CustomerNotFoundException with CHECKOUT__CUSTOMER_NOT_FOUND if email unknown\n\n if ($customer-\u003ehasLegacyPassword()) {\n if (!$this-\u003elegacyPasswordVerifier-\u003everify($password, $customer)) {\n throw CustomerException::badCredentials();\n // \u2191 Throws BadCredentialsException with CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS\n }\n // ...\n }\n\n if ($customer-\u003egetPassword() === null\n || !password_verify($password, $customer-\u003egetPassword())) {\n throw CustomerException::badCredentials();\n // \u2191 Same: CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS\n }\n // ...\n}\n```\n\nThe two exception types produce clearly distinguishable API responses:\n\n**Email not registered:**\n```json\n{\n \"errors\": [{\n \"status\": \"401\",\n \"code\": \"CHECKOUT__CUSTOMER_NOT_FOUND\",\n \"detail\": \"No matching customer for the email \\\"probe@example.com\\\" was found.\",\n \"meta\": { \"parameters\": { \"email\": \"probe@example.com\" } }\n }]\n}\n```\n\n**Email registered, wrong password:**\n```json\n{\n \"errors\": [{\n \"status\": \"401\",\n \"code\": \"CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS\",\n \"detail\": \"Invalid username and/or password.\"\n }]\n}\n```\n\n### Storefront is protected \u2014 Store API is not\n\nThe storefront login controller demonstrates that Shopware\u0027s developers are aware of this risk class. `AuthController::login()` catches both exceptions together and returns a generic error:\n\n```php\n// src/Storefront/Controller/AuthController.php:203\n} catch (BadCredentialsException|CustomerNotFoundException) {\n // Unified handling \u2014 no distinction exposed to the user\n}\n```\n\nThe 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:\n\n```php\n// src/Core/Checkout/Customer/SalesChannel/LoginRoute.php:54-58\n$token = $this-\u003eaccountService-\u003eloginByCredentials(\n $email,\n (string) $data-\u003eget(\u0027password\u0027),\n $context\n);\n// No try/catch \u2014 exceptions propagate with distinct codes\n```\n\nThis inconsistency confirms the Store API exposure is an oversight, not a design decision.\n\n### Rate limiting is present but insufficient for enumeration\n\nThe login route has rate limiting (LoginRoute.php:47-51) keyed on `strtolower($email) . \u0027-\u0027 . $clientIp`. This slows bulk enumeration but does not prevent it because:\n\n1. The attacker only needs **one request per email** to determine existence\n2. The rate limit key includes the IP, so rotating IPs resets the counter\n3. The rate limiter is designed to prevent brute-force password guessing, not single-probe enumeration\n\n## Impact\n\n- **Customer email enumeration**: An attacker can confirm whether specific email addresses are registered as customers, enabling targeted attacks\n- **Phishing enablement**: Confirmed customer emails can be targeted with store-specific phishing campaigns (e.g., fake order confirmations, password reset lures)\n- **Credential stuffing optimization**: Attackers with breached credential databases can first filter for valid emails before attempting password guesses, improving efficiency against rate limits\n- **Privacy violation**: Confirms an individual\u0027s association with a specific store, which may be sensitive depending on the store\u0027s nature (e.g., medical supplies, adult products)\n- **Email reflection**: The `CHECKOUT__CUSTOMER_NOT_FOUND` response echoes the probed email in the `detail` and `meta.parameters.email` fields, which could be leveraged in reflected content attacks\n\n## Recommended Remediation\n\n### Option 1: Catch both exceptions in LoginRoute and throw a unified error (Preferred)\n\nApply the same pattern already used in the storefront controller:\n\n```php\n// src/Core/Checkout/Customer/SalesChannel/LoginRoute.php\npublic function login(#[\\SensitiveParameter] RequestDataBag $data, SalesChannelContext $context): ContextTokenResponse\n{\n EmailIdnConverter::encodeDataBag($data);\n $email = (string) $data-\u003eget(\u0027email\u0027, $data-\u003eget(\u0027username\u0027));\n\n if ($this-\u003erequestStack-\u003egetMainRequest() !== null) {\n $cacheKey = strtolower($email) . \u0027-\u0027 . $this-\u003erequestStack-\u003egetMainRequest()-\u003egetClientIp();\n\n try {\n $this-\u003erateLimiter-\u003eensureAccepted(RateLimiter::LOGIN_ROUTE, $cacheKey);\n } catch (RateLimitExceededException $exception) {\n throw CustomerException::customerAuthThrottledException($exception-\u003egetWaitTime(), $exception);\n }\n }\n\n try {\n $token = $this-\u003eaccountService-\u003eloginByCredentials(\n $email,\n (string) $data-\u003eget(\u0027password\u0027),\n $context\n );\n } catch (CustomerNotFoundException) {\n // Normalize to the same exception as bad credentials\n throw CustomerException::badCredentials();\n }\n\n if (isset($cacheKey)) {\n $this-\u003erateLimiter-\u003ereset(RateLimiter::LOGIN_ROUTE, $cacheKey);\n }\n\n return new ContextTokenResponse($token);\n}\n```\n\nThis ensures both \"not found\" and \"bad credentials\" return the same `CHECKOUT__CUSTOMER_AUTH_BAD_CREDENTIALS` code and generic message.\n\n### Option 2: Unify at the AccountService layer\n\nFor defense in depth, change `AccountService::getCustomerByLogin()` to throw `BadCredentialsException` instead of letting `CustomerNotFoundException` propagate:\n\n```php\n// src/Core/Checkout/Customer/SalesChannel/AccountService.php\npublic function getCustomerByLogin(string $email, string $password, SalesChannelContext $context): CustomerEntity\n{\n if ($this-\u003eisPasswordTooLong($password)) {\n throw CustomerException::badCredentials();\n }\n\n try {\n $customer = $this-\u003egetCustomerByEmail($email, $context);\n } catch (CustomerNotFoundException) {\n throw CustomerException::badCredentials();\n }\n\n // ... rest of password verification\n}\n```\n\nThis 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 \u2014 the normalization should happen at the login boundary.\n\n### Additional: Fix registration endpoint\n\nThe registration endpoint (`POST /store-api/account/register`) also leaks email existence via `CUSTOMER_EMAIL_NOT_UNIQUE`. For complete remediation, consider returning a generic success response and sending a notification email to the existing address instead.\n\n## Credit\n\nThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).",
"id": "GHSA-gqc5-xv7m-gcjq",
"modified": "2026-03-11T21:37:32Z",
"published": "2026-03-11T19:23:49Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/shopware/shopware/security/advisories/GHSA-gqc5-xv7m-gcjq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31888"
},
{
"type": "PACKAGE",
"url": "https://github.com/shopware/shopware"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Shopware has user enumeration via distinct error codes on Store API login endpoint"
}
GHSA-GV23-J6X4-4X4X
Vulnerability from github – Published: 2024-06-29 06:31 – Updated: 2024-06-29 06:31IBM Storage Defender - Resiliency Service 2.0.0 through 2.0.4 agent username and password error response discrepancy exposes product to brute force enumeration. IBM X-Force ID: 294869.
{
"affected": [],
"aliases": [
"CVE-2024-38322"
],
"database_specific": {
"cwe_ids": [
"CWE-203",
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-06-28T19:15:06Z",
"severity": "MODERATE"
},
"details": "IBM Storage Defender - Resiliency Service 2.0.0 through 2.0.4 agent username and password error response discrepancy exposes product to brute force enumeration. IBM X-Force ID: 294869.",
"id": "GHSA-gv23-j6x4-4x4x",
"modified": "2024-06-29T06:31:39Z",
"published": "2024-06-29T06:31:39Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-38322"
},
{
"type": "WEB",
"url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/294869"
},
{
"type": "WEB",
"url": "https://www.ibm.com/support/pages/node/7158446"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-GWWW-55J7-6P52
Vulnerability from github – Published: 2023-05-15 12:30 – Updated: 2026-06-01 15:30Observable Response Discrepancy in SICK FTMg AIR FLOW SENSOR with Partnumbers 1100214, 1100215, 1100216, 1120114, 1120116, 1122524, 1122526 allows a remote attacker to gain information about valid usernames by analyzing challenge responses from the server via the REST interface.
{
"affected": [],
"aliases": [
"CVE-2023-23449"
],
"database_specific": {
"cwe_ids": [
"CWE-203",
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-05-15T11:15:09Z",
"severity": "MODERATE"
},
"details": "Observable Response Discrepancy in SICK FTMg AIR FLOW SENSOR with Partnumbers 1100214, 1100215, 1100216, 1120114, 1120116, 1122524, 1122526 allows a remote attacker\nto gain information about valid usernames by analyzing challenge responses from the server via the\nREST interface.",
"id": "GHSA-gwww-55j7-6p52",
"modified": "2026-06-01T15:30:31Z",
"published": "2023-05-15T12:30:16Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-23449"
},
{
"type": "WEB",
"url": "https://sick.com/.well-known/csaf/white/2023/sca-2023-0004.json"
},
{
"type": "WEB",
"url": "https://sick.com/.well-known/csaf/white/2023/sca-2023-0004.pdf"
},
{
"type": "WEB",
"url": "https://sick.com/psirt"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-GXJQ-F9VV-WJQM
Vulnerability from github – Published: 2025-12-05 00:31 – Updated: 2025-12-08 18:30Kalmia CMS version 0.2.0 contains a user enumeration vulnerability in its authentication mechanism. The application returns different error messages for invalid users (user_not_found) versus valid users with incorrect passwords (invalid_password). This observable response discrepancy allows unauthenticated attackers to enumerate valid usernames on the system.
{
"affected": [],
"aliases": [
"CVE-2025-65899"
],
"database_specific": {
"cwe_ids": [
"CWE-204"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-12-04T22:15:48Z",
"severity": "MODERATE"
},
"details": "Kalmia CMS version 0.2.0 contains a user enumeration vulnerability in its authentication mechanism. The application returns different error messages for invalid users (user_not_found) versus valid users with incorrect passwords (invalid_password). This observable response discrepancy allows unauthenticated attackers to enumerate valid usernames on the system.",
"id": "GHSA-gxjq-f9vv-wjqm",
"modified": "2025-12-08T18:30:29Z",
"published": "2025-12-05T00:31:05Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-65899"
},
{
"type": "WEB",
"url": "https://github.com/DifuseHQ/Kalmia"
},
{
"type": "WEB",
"url": "https://github.com/Noxurge/CVE-2025-65899/blob/main/README.md"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
Mitigation MIT-46
Strategy: Separation of Privilege
- Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area.
- Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
Mitigation MIT-39
- Ensure that error messages only contain minimal details that are useful to the intended audience and no one else. The messages need to strike the balance between being too cryptic (which can confuse users) or being too detailed (which may reveal more than intended). The messages should not reveal the methods that were used to determine the error. Attackers can use detailed information to refine or optimize their original attack, thereby increasing their chances of success.
- If errors must be captured in some detail, record them in log messages, but consider what could occur if the log messages can be viewed by attackers. Highly sensitive information such as passwords should never be saved to log files.
- Avoid inconsistent messaging that might accidentally tip off an attacker about internal state, such as whether a user account exists or not.
CAPEC-331: ICMP IP Total Length Field Probe
An adversary sends a UDP packet to a closed port on the target machine to solicit an IP Header's total length field value within the echoed 'Port Unreachable" error message. This type of behavior is useful for building a signature-base of operating system responses, particularly when error messages contain other types of information that is useful identifying specific operating system responses.
CAPEC-332: ICMP IP 'ID' Field Error Message Probe
An adversary sends a UDP datagram having an assigned value to its internet identification field (ID) to a closed port on a target to observe the manner in which this bit is echoed back in the ICMP error message. This allows the attacker to construct a fingerprint of specific OS behaviors.
CAPEC-541: Application Fingerprinting
An adversary engages in fingerprinting activities to determine the type or version of an application installed on a remote target.
CAPEC-580: System Footprinting
An adversary engages in active probing and exploration activities to determine security information about a remote target system. Often times adversaries will rely on remote applications that can be probed for system configurations.