Vulnerability Research

Froxlor Remembered-2FA Bypass: When User IDs Lose Their Namespace

An analysis of GHSA-9fq7-9w8p-c3qh, where missing account-type binding let a customer remembered-2FA token satisfy an administrator login.

The vulnerability in one sentence

CVE-2026-100709, documented in GHSA-9fq7-9w8p-c3qh, is a high-severity second-factor bypass in Froxlor: a remembered-2FA token created for a customer account could be accepted during an administrator login when the two accounts had the same numeric ID.

The advisory assigns a CVSS 4.0 score of 7.7. It classifies the issue as CWE-287, Improper Authentication. It lists Froxlor versions through 2.3.10 as affected and version 2.3.12 as patched.

PropertyAdvisory information
IdentifiersCVE-2026-100709, GHSA-9fq7-9w8p-c3qh
SeverityHigh, CVSS 4.0 score 7.7
VectorCVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Affected versionsFroxlor <= 2.3.10
Patched versionFroxlor 2.3.12
WeaknessCWE-287: Improper Authentication
Attacker already needsA customer account, its valid remembered-2FA token, a colliding numeric ID, and the administrator's correct password
Security boundary crossedCustomer remembered-2FA state to administrator authentication

This is not a password bypass. The attacker still needs the target administrator's correct password. It is also not a TOTP prediction attack or a weakness in the authenticator algorithm. The failure is subtler: Froxlor accepted proof of a completed second factor for the wrong account.

How this became an administrator compromise during a penetration test

I found the issue through source-code review during an authorized penetration test, then validated it with controlled runtime testing. It completed an attack path that initially appeared to stop at 2FA.

An earlier finding had exposed valid login credentials for multiple Froxlor users. The credentials included ordinary customer accounts and an administrator account. The administrator password passed the first authentication step, but the administrator account was protected by TOTP, so the leaked password alone did not produce an administrative session. At that point, 2FA was working as the expected containment control.

Reviewing Froxlor's remembered-2FA implementation showed that customers and administrators occupied separate account tables, while the token record and lookup bound the credential only to a numeric user ID. The customer and target administrator shared that numeric ID. Because the remembered-token record preserved the number but not the account type that gave it meaning, the code admitted the following chain:

leaked customer credentials
  -> sign in to the customer account
  -> enroll an attacker-controlled TOTP authenticator
  -> complete customer 2FA and create a remembered token

leaked administrator password
  + customer remembered token with the same numeric user ID
  -> administrator 2FA accepted as already satisfied
  -> authenticated administrator session

After signing in to one of the lower-privileged accounts, we could enroll a TOTP authenticator that we controlled. We then completed the customer's 2FA flow and used Froxlor's trusted-browser feature to obtain a legitimate remembered-2FA token for that customer.

The customer credential did not become an administrator password, and our TOTP secret did not replace or predict the administrator's TOTP secret. The privilege boundary failed because proof created for the customer namespace was consumed in the administrator namespace.

This changed the meaning of the earlier credential leakage. Without the namespace-confusion flaw, the exposed administrator password was contained by 2FA. Without the exposed administrator password, the customer remembered token was insufficient. Together, the two findings produced a complete administrator-compromise path.

That composition is an important penetration-testing lesson. Findings should not be assessed only in isolation: a lower-privileged foothold, a first-factor disclosure, and an authentication-binding flaw can combine into impact that none of them produces alone. Once administrative access is demonstrated, evidence collection should remain minimal and non-destructive, and the compromised credentials, sessions, and remembered tokens should be revoked through the engagement's agreed response process.

Numeric IDs were unique only inside each account table

Froxlor has separate customer and administrator account namespaces. Each table allocates its own numeric primary keys, so both of these identities can legitimately exist:

(account_type=customer, userid=1)
(account_type=admin,    userid=1)

Those are different security principals. The vulnerable remembered-token record, however, represented the account with only userid:

selector
hashed authenticator
userid
expiration

Because no account-type discriminator was stored, the record could express “user 1” but not “customer user 1.” The database value was valid, but its meaning was incomplete.

This is namespace confusion. A local identifier is treated as globally unique after it crosses the boundary of the namespace that gives it meaning. The same design error appears when systems confuse:

  • A user ID from tenant A with the same user ID from tenant B.
  • An OIDC subject without its issuer.
  • A human account with a service account from another identity store.
  • A resource ID without its organization, region, environment, or resource type.
  • A session or recovery token whose purpose is not bound to the operation consuming it.

The secure identity was the composite (account_type, userid), not userid alone.

How the login path crossed the boundary

The vulnerable path combined information from two different contexts:

Attack flow showing a customer remembered-2FA token crossing into an administrator login because both accounts share numeric user ID N.

The arrows summarize Froxlor's server-side lookups and writes initiated by the client. The decisive collision occurs between the first login's customer userid N and the second login's administrator userid N: the vulnerable token lookup reaches the Tokens Table without carrying the Customer Table or Admin Table namespace with it.

The selector and authenticator still belonged to a genuine token. The numeric ID still matched the newly resolved administrator row. Each check appeared locally reasonable. The missing check was whether the token's issuing account namespace matched the account namespace currently being authenticated.

What changed from Froxlor 2.3.10 to 2.3.11

The remembered-token insertion in Froxlor 2.3.10 index.php stored the selector, hashed authenticator, numeric user ID, and expiry:

`selector` = :selector,
`token` = :authenticator,
`userid` = :userid,
`valid_until` = :valid_until

The corresponding Froxlor 2.3.10 lookup applied the same incomplete identity:

WHERE `selector` = :selector
  AND `userid` = :uid
  AND `valid_until` >= UNIX_TIMESTAMP()

The :uid value came from the account resolved by the current username-and-password login. For a customer with ID 1 it meant customer 1; during an administrator login it meant administrator 1. The query could not distinguish those meanings because it had no customer-or-administrator predicate.

Froxlor 2.3.11 introduced the namespace mapping. Its token insertion stores whether the issuing account is an administrator:

`admin` = :isadmin,
'isadmin' => $isadmin ? 1 : 0,

The 2.3.11 lookup applies the same discriminator:

AND `userid` = :uid
AND `admin` = :isadmin

Token issuance now stores the same admin flag. Verification binds :isadmin to the account type selected by the active login flow, so matching the numeric ID is no longer enough.

Decision pointFroxlor 2.3.10Froxlor 2.3.11
Token issuanceStores userid onlyStores userid and admin
Token lookupMatches selector, userid, and expiryAlso matches admin = :isadmin
Namespace resultCustomer and administrator IDs can collideToken must belong to the current account type

Froxlor 2.3.12 retains that binding and adds database-version guards so the application does not use the new column before migration completes. The published remediation target remains 2.3.12 or later.

That distinction explains why strong cryptography did not prevent the bypass. Hashing the authenticator protects the token secret at rest, and constant-time comparison protects the equality check. Neither control can repair an incomplete security identity. A cryptographically valid credential can still be authorized in the wrong context.

Exploitation has important prerequisites

Accurate severity communication matters. Exploitation requires all of the following:

  1. The attacker controls a Froxlor customer account.
  2. That customer completes its own TOTP flow and obtains a valid remembered-2FA token.
  3. The customer ID equals the numeric ID of a target administrator.
  4. The attacker independently knows the target administrator's correct password.
  5. The token remains valid when it is presented during the administrator login.

The flaw therefore does not let an anonymous attacker guess an administrator password, derive a TOTP secret, or mint an arbitrary remembered token. The attack requirements are real and should not be omitted.

They also do not make the issue harmless. Separate account tables commonly produce overlapping low numeric IDs. More importantly, 2FA exists so that a stolen, reused, phished, or otherwise exposed password remains insufficient. If these prerequisites are met, the vulnerable logic removes exactly that protection and creates a normal administrator session.

Why the impact is high

An administrator session is a high-value control-plane identity. The advisory notes that a successful login can expose customers, configuration, credentials, and the administrative operations available to the compromised account. That can affect confidentiality, integrity, and availability across the managed hosting environment.

The risk can be stated without exaggeration:

The first factor is already compromised.
The second factor should contain that compromise.
Namespace confusion causes the second factor to be accepted from another account.
The attacker receives the target administrator's authenticated session.

This is why “the attacker needs the password” is not a dismissal. It is the condition under which the failed security control was designed to matter most.

How the fixed implementation binds the namespace

The fixed design makes the account namespace part of the remembered-token identity. The server-side panel_2fa_tokens table receives a non-null unsigned admin flag. Froxlor stores 1 for an administrator token and 0 for a customer token.

Token creation now stores the namespace of the account that completed 2FA. Token verification independently derives the expected namespace from the username-and-password login in progress. A remembered token must match:

  • The cookie selector.
  • The numeric user ID.
  • The account namespace represented by the admin flag.
  • The validity period.
  • The existing authenticator-hash comparison.

The corrected decision is therefore based on the complete principal:

remembered token subject == (account_type, userid)
current login subject     == (account_type, userid)

A token for (customer, 1) can no longer satisfy a login for (admin, 1). The external cookie format and the existing token cryptography do not need to change because the missing binding is added on the server.

Revoking old tokens is the safe migration

Existing token rows contain no trustworthy account-type information. Inferring it from the numeric user ID would be unsafe because that is the ambiguous value that caused the vulnerability. If both an administrator and customer row have ID 1, a migration cannot determine which identity issued an old token merely by joining on userid=1.

Froxlor's migration therefore adds the required namespace field and deletes every existing remembered-2FA token rather than attempting to classify ambiguous rows. That is the correct fail-closed choice. After upgrading, users may need to enter their TOTP again and reselect the trusted-browser option. The advisory states that TOTP enrollment secrets, passwords, and ordinary login sessions are not changed by this migration.

This is a broader migration principle: when legacy authentication data lacks enough provenance to classify safely, revoke it. Guessing preserves convenience by carrying the ambiguity into the new schema.

What operators should do

Operators should treat the upstream advisory as the authoritative source and:

  1. Inventory the exact Froxlor version in every environment, including standby and disaster-recovery instances.
  2. Upgrade affected installations to Froxlor 2.3.12 or a later supported release using the project's normal update procedure.
  3. Confirm the database migration completed and expect remembered browsers to require TOTP again.
  4. Review administrator authentication and activity logs for unexpected successful logins, especially cases where a password-authenticated administrator did not complete a fresh TOTP challenge.
  5. If compromise is suspected, revoke administrator sessions through the available operational controls, reset exposed credentials, and review panel configuration and customer-impacting changes.

Deleting a cookie only in a browser is not a fleet-wide response. The security decision is backed by server-side token state. The upgrade migration invalidates the ambiguous remembered-token records centrally.

How to test this class of authentication flaw

A good authentication test matrix varies namespaces as well as identifiers. For remembered devices, password resets, API keys, recovery codes, magic links, and sessions, record the complete credential scope:

DimensionExample questions
Subject namespaceCustomer, administrator, employee, service account, or partner?
Tenant or issuerWhich organization or identity provider owns the subject?
Credential purposeLogin, recovery, enrollment, transaction approval, or delegation?
AudienceWhich application, endpoint, or relying party may consume it?
Authentication strengthWhich factors and assurance level produced it?
Lifetime and revocationWhen does it expire, and what event invalidates it?

Then use deliberate collisions. Create synthetic identities with the same local ID in different namespaces and verify negative cases in both directions:

customer token + same-numbered administrator login -> reject
administrator token + same-numbered customer login -> reject
customer token + same customer login               -> accept
administrator token + same administrator login     -> accept
expired or wrong-authenticator token                -> reject

The positive controls matter. A fix that rejects every remembered token closes the bypass but breaks the feature. The security invariant is not “cross-namespace tests fail”; it is “only a valid token bound to the exact current principal succeeds.”

Testing should inspect the resulting session and privileges, not only the immediate redirect. Authentication flaws often emerge where one component resolves identity, another verifies a credential, and a third creates the session. Evidence should show which principal completed each step and which authorization context reached the final state.

The architecture lesson: bind proof to the complete claim

Two-factor authentication is not just two successful checks. Both factors must authenticate the same subject, in the same security domain, for the same login transaction.

A remembered-2FA token is a delegated proof that a particular principal previously completed an additional factor. Its security properties depend on binding that proof to every field required to identify the principal unambiguously. Where identities come from multiple tables, tenants, issuers, or account classes, the namespace is part of the identity—not optional metadata.

The durable design rule is:

credential scope >= authorization identity scope

If authorization distinguishes (tenant, account_type, user_id), a credential indexed only by user_id is under-scoped. The same review question should be applied to session caches, rate-limit keys, audit events, password-reset records, WebAuthn credentials, impersonation grants, and API-token tables.

GHSA-9fq7-9w8p-c3qh is a compact example of why authentication testing must follow identities across storage and control-flow boundaries. The individual checks can all be implemented correctly while the complete decision is wrong.

Related assurance work includes penetration testing that exercises complete authentication flows, security architecture review of identity and trust boundaries, and testing automation that preserves cross-namespace cases as regression controls.

Disclosure reference

I discovered and reported this vulnerability to the Froxlor project. The full technical record, affected-version range, CVSS vector, patch description, and proof-of-concept are available in the Froxlor GitHub security advisory.

Turn research into assurance

Need a controlled security assessment?

AI-Adversary helps teams design, execute, and validate security testing for applications, AI systems, and automation workflows.