Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
ZeroDays CVE-2026-60104
High CVE-2026-60104 CVSS 8.7 Bitwarden Server C#

Bitwarden Admin Auth Request Authorization Bypass

CVE-2026-60104: Bitwarden Server authorization bypass lets a low-privileged org member steal another user's vault key and take over their account.

Offensive360 Research Team
Affects: < 2026.6.0
Source Code View Patch

Overview

CVE-2026-60104 is a broken object-level authorization (BOLA) vulnerability in the Bitwarden Server’s Trusted Device Encryption (TDE) admin authentication-request flow. Prior to version 2026.6.0, the POST /auth-requests/admin-request endpoint accepted a target user email in the request body without verifying that the email belonged to the authenticated caller. Any low-privileged member of a Bitwarden organization could craft a request naming an arbitrary victim within the same organization, binding that request to an attacker-controlled asymmetric key pair. Once an organization administrator approved the request — a routine administrative action that admins are trained to perform — the resulting victim vault key and a victim-scoped access token were written to an endpoint that is readable without authentication, completing a full account takeover without any interaction from the victim themselves.

The severity of this flaw is compounded by the nature of what Bitwarden protects. Vault keys are the root secret material from which all stored credentials are derived. Disclosure of a vault key, even in encrypted form wrapped under an attacker-controlled public key, reduces the confidentiality of every password, note, and cryptographic secret the victim has ever stored. The CVSS 8.7 HIGH score reflects the combination of network exploitability, low attack complexity once an organization membership is established, and the critical confidentiality and integrity impact against a high-value target.

The vulnerability was identified through source-code analysis of the TDE authentication-request workflow. Organizations running self-hosted Bitwarden Server instances that had not applied the 2026.6.0 patch were exposed as long as they had at least one multi-member organization with the admin-auth-request feature in use. Bitwarden Cloud was patched by the vendor; self-hosted operators who had not upgraded remained vulnerable until they deployed the corrected release.

Technical Analysis

Trusted Device Encryption allows a user to register a new device by submitting an authentication request that includes a device-bound public key. An administrator then approves the request, at which point the server encrypts the user’s vault key under the submitted public key and exposes it at a polling endpoint. The design intent is that only the legitimate account owner can submit such a request for their own account.

The root cause is a missing identity check in the admin-request handler. The endpoint authenticated the caller via a bearer token — confirming someone with valid organization credentials was speaking — but it performed no assertion that the email field in the request body matched the identity of that authenticated caller. Conceptually, the vulnerable logic resembled:

// VULNERABLE — pre-2026.6.0 pattern (simplified for illustration)
[HttpPost("admin-request")]
[Authorize]
public async Task<IActionResult> CreateAdminAuthRequest(
    [FromBody] AdminAuthRequestCreateRequestModel model)
{
    // Caller is authenticated as *some* org member, but their identity
    // is never compared against model.Email.
    var targetUser = await _userRepository.GetByEmailAsync(model.Email);
    if (targetUser == null)
    {
        return NotFound();
    }

    var authRequest = new AuthRequest
    {
        UserId        = targetUser.Id,       // victim's user ID
        PublicKey     = model.PublicKey,     // attacker-controlled key
        RequestIpAddress = HttpContext.GetIpAddress(),
        Type          = AuthRequestType.AdminApproval,
    };

    await _authRequestRepository.CreateAsync(authRequest);

    // Notify org admins — admin approves believing this is legitimate
    await _authRequestService.SendAdminAuthRequestEmailsAsync(authRequest);

    return Ok(new AuthRequestResponseModel(authRequest));
}

The attacker generates a local RSA or EC key pair, retains the private key, and submits the public key paired with the victim’s email. When an admin approves the request, the server retrieves the victim’s symmetric vault key, encrypts it under the attacker-supplied public key, and stores the encrypted blob on the auth-request record. The approval response — including the encrypted vault key and a victim-scoped access token — is then surfaced at GET /auth-requests/{id}/response, an endpoint designed to be polled by a device that may not yet be authenticated. Because the unauthenticated polling endpoint requires only knowledge of the request ID (a UUID that the attacker already possesses from the creation response), the attacker can retrieve the encrypted vault key and decrypt it locally with their retained private key. The access token included in the response grants direct API access scoped to the victim’s account.

The attack chain is therefore: (1) join or compromise any low-privilege account in a target organization, (2) POST an admin-request naming the victim and supplying an attacker-controlled public key, (3) wait for an admin to approve, (4) poll the unauthenticated response endpoint with the known request ID, (5) decrypt the vault key locally.

Impact

An attacker who successfully exploits this vulnerability achieves complete account takeover of any targeted user within their organization. Concretely:

  • Full vault decryption: The recovered vault key allows offline decryption of every secret in the victim’s vault — passwords, TOTP seeds, SSH keys, secure notes, and credit card data.
  • Persistent access: The victim-scoped access token provides live API access that persists until the victim or an administrator revokes sessions.
  • Lateral movement: Credentials harvested from the victim’s vault can be used to pivot into other systems, including those outside the Bitwarden organization boundary.
  • Privilege escalation: If the victim is an organization administrator or an IT privileged account, the attacker effectively inherits those privileges across all connected systems.

The CVSS vector reflects network attack surface (AV:N), low complexity (AC:L), low required privileges (PR:L — only organization membership is needed), no user interaction from the victim (UI:N), and high confidentiality and integrity impact (C:H/I:H). The scope is changed (S:C) because the compromise extends beyond the Bitwarden application boundary to every system whose credentials are stored in the vault.

How to Fix It

The correct fix, as implemented in the patch commit, is to assert that the email address supplied in the request body matches the identity of the authenticated caller before creating the auth request. The server already has the caller’s verified identity from the bearer token context:

// FIXED — post-2026.6.0 pattern (simplified for illustration)
[HttpPost("admin-request")]
[Authorize]
public async Task<IActionResult> CreateAdminAuthRequest(
    [FromBody] AdminAuthRequestCreateRequestModel model)
{
    // Resolve the authenticated caller's identity from the token.
    var callerUserId = _userService.GetProperUserId(User)
        ?? throw new UnauthorizedAccessException();

    var caller = await _userRepository.GetByIdAsync(callerUserId);

    // Enforce that the target email belongs to the authenticated caller.
    if (!string.Equals(caller.Email, model.Email,
            StringComparison.OrdinalIgnoreCase))
    {
        throw new BadRequestException(
            "Email does not match the authenticated user.");
    }

    var authRequest = new AuthRequest
    {
        UserId        = caller.Id,
        PublicKey     = model.PublicKey,
        RequestIpAddress = HttpContext.GetIpAddress(),
        Type          = AuthRequestType.AdminApproval,
    };

    await _authRequestRepository.CreateAsync(authRequest);
    await _authRequestService.SendAdminAuthRequestEmailsAsync(authRequest);

    return Ok(new AuthRequestResponseModel(authRequest));
}

Upgrade steps for self-hosted operators:

# Docker Compose — pull and restart with the patched image
docker compose pull
docker compose up -d

# Verify the running image tag reflects 2026.6.0 or later
docker inspect bitwarden-api | grep -i image

All self-hosted operators must upgrade to Bitwarden Server 2026.6.0 or later. There is no configuration-level workaround that eliminates the risk; the only complete remediation is the code-level authorization check introduced in the patch. After upgrading, administrators should audit auth-request logs for anomalous entries — specifically requests where the submitting user’s email differs from the target email — to determine whether exploitation occurred prior to patching.

Our Take

This vulnerability exemplifies one of the most persistent and consequential failure modes in API security: trusting caller-supplied identity data instead of deriving identity exclusively from the authenticated session. The pattern — authenticate the caller, then use an unvalidated body field to determine whose data to operate on — appears repeatedly across enterprise applications, particularly in endpoints added during feature development where the original authentication scaffold was reused without a corresponding authorization review.

What makes this instance particularly instructive is the trust amplification built into the feature’s design. The TDE admin-request flow is explicitly intended to involve administrative approval, which creates a social engineering surface: an attacker doesn’t need to bypass any technical control on the approval side, because admins are conditioned to approve these requests as a normal operational task. The vulnerability weaponizes a legitimate administrative workflow.

For development teams, the lesson is that authentication and authorization are distinct concerns and must be enforced independently at every sensitive operation. Endpoints that accept an identity claim in the request body — an email, a user ID, a username — must validate that claim against the session identity before acting on it. This check should be a mandatory code-review gate for any endpoint handling privileged data operations.

Detection with SAST

This vulnerability class maps to CWE-639: Authorization Bypass Through User-Controlled Key and CWE-285: Improper Authorization. SAST detection targets the pattern of an authenticated endpoint that reads a user-identifying field from the request body and uses it as a direct lookup key for sensitive data retrieval without a subsequent equality check against the authenticated principal.

Offensive360’s SAST engine flags this pattern through a combination of data-flow and semantic analysis:

  1. Taint source identification: Request body fields typed as email addresses or user identifiers (model.Email, model.UserId, request.TargetEmail) are marked as attacker-controlled taint sources.
  2. Sink identification: Repository calls that use those tainted values to retrieve user records (GetByEmailAsync, GetByIdAsync) feeding into data-modification or sensitive-disclosure operations are marked as sinks.
  3. Authorization check detection: The engine looks for an equality assertion between the tainted sink result and the authenticated principal (typically resolved through HttpContext.User, ClaimsPrincipal, or a _userService.GetProperUserId() call) on the data-flow path between source and sink.
  4. Violation reporting: When no such assertion exists on any feasible path from source to sink within an [Authorize]-decorated controller action, the engine raises a BOLA/IDOR finding at HIGH severity under the CWE-639 rule category.

Static analysis alone cannot fully cover runtime authorization logic that depends on organizational membership hierarchies or dynamic role evaluation, which is why Offensive360 pairs SAST findings with DAST probes that replay authentication-request flows with mismatched identity tokens against staging environments.

References

#authorization-bypass #broken-access-control #account-takeover #trusted-device-encryption

Detect this vulnerability class in your codebase

Offensive360 SAST scans your source code for CVE-2026-60104-class vulnerabilities and thousands of other patterns — across 60+ languages.