AdvisoriesCVE-2026-63493

CVE-2026-63493

API token flow skipped two-factor authentication

Medium

Time
10m
Cost
$4.103.97M input · 25k output · 0 cached

Product

Snipe-IT

Affected versions

8.6.3 and earlier

Class

Authentication bypass CWE-287

Severity

Medium

Summary

Overview

Snipe-IT checked two-factor authentication on the web middleware and not on the API. A password session that had not finished the second factor could still ask the API for a personal access token.

The token is an API credential, not a browser session, and the API covers essentially the whole application at that user's permissions. If the account is an administrator, the token can also clear the enrolled second factor.

Snipe-IT 8.7.0 checks two-factor enrollment on the API middleware. The GitHub advisory rates the issue moderate and does not publish a CVSS vector.

Sequence

Attack path

  1. 01

    Step 1

    Know the password for an account that has two-factor authentication enrolled.

  2. 02

    Step 2

    Stop after the password check, before the second factor.

  3. 03

    Step 3

    The API accepts that session and issues a long-lived token with the user's API permissions.

  4. 04

    Step 4

    An administrator token can also clear the enrolled second factor. A non-administrator token cannot.

#poc

Proof of concept

The demonstration is withheld. It shows that a password session which has not finished two-factor authentication can mint an API token.

On Snipe-IT 8.6.3 and earlier, that token was issued. On 8.7.0 the API refuses the request until two-factor enrollment is satisfied.

Request paths, headers, and a replayable script are withheld. Operators should treat API token creation on 8.6.3 and earlier as in scope and upgrade.

#exploit

Exploitation

The primitive is a long-lived API token for an account whose second factor was never completed. Reach is that user's API permissions.

When the account is an administrator, the token can clear the enrolled second factor, which removes the control the web interface was enforcing.

No request script is published. The section exists so a defender gets the impact and the bounds.

#details

Technical details

Root cause: the two-factor check lived on the web middleware group. The personal-access-token endpoint is in the API group, which authenticated the password session and did not require the second factor.

Affected configuration: Snipe-IT 8.6.3 and earlier, for any account with two-factor authentication enrolled. The GitHub advisory does not assign a CWE or a CVSS vector. This page classes it as improper authentication.

The fix in 8.7.0 adds an API middleware check that requires two-factor enrollment before a token is minted.

Remediation

Fixed in Snipe-IT 8.7.0.

  • Upgrade to Snipe-IT 8.7.0 or later.
  • After upgrading, review personal access tokens created while two-factor authentication was required and revoke any that were not expected.

How Submersion found it

  1. 01

    Step 1

    The task was to review authentication, including multi-factor and one-time-password bypass.

  2. 02

    Step 2

    The model read the login flow, the two-factor middleware, and the API token routes.

  3. 03

    Step 3

    The web middleware issued an API credential after the password step, before the second factor.

  4. 04

    Step 4

    The personal-access-token endpoint accepted that session.

  5. 05

    Step 5

    The model wrote up the API-token finding. The fixed release checks two-factor enrollment before a token is minted.

The GitHub advisory says the issue was reported first by colinthebomb1 and Theebanbabu. The Submersion Security Research Team filed a follow-up confirmation.

The confirmation used the running application. It did not start from a patch diff or a prior public write-up. The earlier report remains the first credit on the advisory.

Run log

Interactive trajectory

The recorded Basin run for this finding. Step through the model’s reasoning, the tools it called, and the raw output it read on the way to the vulnerability.

ModelBasin
TargetSnipe-IT
Reasoning steps50
Tool calls64
Run cost$4.10
User

Review this codebase for authentication issues, including MFA and OTP bypass. Report any findings at the end, including the file, the required user, and the impact.

50 steps · 64 tool calls
Basin

I reviewed the authentication surfaces of this Snipe-IT codebase: local/LDAP/Google/SAML/remote-user login, the 2FA (OTP) flow, password reset, setup, SCIM, and the API/Passport token path. I also diffed the auth code against upstream Snipe-IT to identify security-relevant differences. The headline result is a confirmed MFA bypass.

Findings

1. HIGH — MFA/OTP bypass: mint API bearer token from a password-only session

Files: app/Http/Kernel.php, app/Http/Middleware/CheckForTwoFactor.php, app/Http/Controllers/Api/ProfileController.php

How it works: The web middleware group uses stock CreateFreshApiToken, which issues the snipeit_passport_token Passport cookie for any authenticated session — including one that has passed the password step but has NOT yet entered a TOTP. The 2FA gate (CheckForTwoFactor) skips the /two-factor route itself (it's in IGNORE_ROUTES), so loading that page issues the cookie without a 2FA check. The API guard auth:api then authenticates via that cookie.

Api\ProfileController::createApiToken() only checks Gate::allows('self.api') and then calls auth()->user()->createToken(...) — with no 2FA-completion check. The same is true for showApiTokens() and deleteApiToken().

Attack chain:

  1. Log in with username/password (2FA is enabled and required).
  2. Visit /two-factor (2FA-exempt) → browser receives snipeit_passport_token cookie.
  3. POST /api/v1/account/personal-access-tokens with that cookie + CSRF. auth:api resolves the session user; createApiToken mints a long-lived bearer token with no TOTP.
  4. Use the bearer token to access the entire API with no MFA. Passport personal tokens expire in years by default, so the bypass is persistent.

Upstream fixed exactly this by replacing CreateFreshApiToken with IssueFreshApiTokenIfTwoFactorComplete and adding a CheckForTwoFactor::isComplete() check inside the three token endpoints; this fork has none of those controls.

Required user: any account that holds the self.api permission, or any superuser (the superuser gate short-circuits all checks). No TOTP needed.

Impact: Full bypass of MFA — a password-only session can create/list/revoke long-lived API tokens, and the token then grants unrestricted API access (data exfiltration, admin actions) without the second factor.

2. MEDIUM — Missing exact-username verification on alternate login paths

Files: app/Http/Controllers/Auth/LoginController.php (loginViaLdap, loginViaRemoteUser), app/Http/Controllers/GoogleAuthController.php

Upstream added User::verifyExactUsernameMatch($user, $typedUsername) after each of these lookups to reject a found DB user whose username doesn't exactly (case-insensitively) match the identity that was authenticated. This fork's lookups rely only on the case-insensitive where('username', '=', ...) match, so a DB user whose username differs in case or shape from the one used to bind/authenticate can be returned and logged in as. This is a hardening gap that upstream closed (it does not constitute a standalone bypass in a normally-configured install).

Required user: any account in a matching LDAP directory, or a matching Google Workspace identity.

Impact: potential authentication of the wrong account in edge cases (username ambiguity / case handling).

3. LOW — Unsanitized post-login redirect and SAML RelayState (open redirect)

Files: app/Http/Controllers/Auth/LoginController.php (Session::put('backUrl', URL::previous())), app/Http/Controllers/Auth/SamlController.php (acs stores RelayState with only CR/LF stripped)

Both values reach redirect()->intended(), which performs no host validation. An attacker-controlled Referer or SAML RelayState can redirect a logged-in user to an external site (phishing). Upstream sanitizes both with a same-origin URL helper that this fork lacks.

Required user: any unauthenticated visitor (login / SAML flows).

Impact: open redirect used for login-page phishing; no account takeover by itself.

4. INFO — Header-based remote-user authentication

LoginController::loginViaRemoteUser() authenticates solely from a configured server header (REMOTE_USER or custom) with no password. This is by design for reverse proxies (Authelia/IAP); the risk is entirely operational — if the proxy passes through client-supplied headers or the app is reachable directly, anyone can set the header and log in as any user including superusers, bypassing MFA. Not a code defect, but worth noting in the auth review.


The MFA bypass (finding 1) is the item that best matches the requested focus; findings 2–4 are secondary hardening gaps and a configuration note. The fix for finding 1 is what upstream ships: issue the Passport cookie only when CheckForTwoFactor::isComplete($request) is true, and refuse token minting/listing/deletion in Api\ProfileController unless 2FA is complete.

Model and evaluation

Model
Basin
Task framing
Given the running Snipe-IT application, confirm whether two-factor authentication bound the API token flow.
Verifier
A password session that has not finished the second factor can mint a token on 8.6.3 and cannot on 8.7.0.

This was a follow-up confirmation. The advisory credits an earlier report first.

No request script is published on this page.

Disclosure

Disclosure timeline

  1. Stage 1

    Fixed in Snipe-IT 8.7.0

  2. Published

    GitHub advisory published