
Bitwarden Server v2026.9.2 - 높음 - IDOR - 호출자가 쓸 수 없는 컬렉션에 복호화 가능한 보관 항목을 심을 수 있음
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · bitwarden-cipher-create-acl
분석 문서: https://abraxaslabs.tech/research/bitwarden-cipher-create-acl
Bitwarden Server v2026.9.2 - Bitwarden
Entity Framework 경로(MariaDB, Postgres, SQLite)에서의 조직 cipher 생성은 컬렉션 연결 전에 호출자 id를 버린다. 확인된 멤버는 자신이 쓰기 권한이 없는 컬렉션에 조직 키 항목을 심을 수 있다. 컬렉션 멤버들은 이를 일반 공유 로그인으로 동기화한다.
| ID | 아직 CVE 없음 |
| CWE | CWE-863 / CWE-639 |
| CVSS | High: 7.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L |
| 제품 | Bitwarden Server |
| 영향 받음 | v2026.9.2 (9ee4e0eb) lite EF (MariaDB / Postgres / SQLite). MSSQL/Dapper는 @UserId를 유지한다. |
| 인증 | 확인된 조직 멤버 |
| 라이선스 | GNU Affero GPL v3.0 |
| 랩 | 127.0.0.1 전용 |
공격자는 공유 컬렉션에 보관 항목을 추가한다. 이미 그 안에 있는 비밀을 읽거나 교체하지는 않는다.
확인된 조직 멤버는 이미 조직 키를 가지고 있으므로, 새 항목은 해당 컬렉션을 볼 수 있는 모든 사람에게 복호화된다. Bitwarden은 이를 회사 보관함 내부의 일반 공유 로그인, 노트, 카드 또는 SSH 키로 표시한다.
Okta, GitHub, VPN, Payroll)처럼 이름 붙인 로그인을 심고 URI가 공격자가 제어하는 호스트를 가리키게 한다. 컬렉션 멤버는 Bitwarden에서 이를 열어 공격자 페이지에 도달하거나, 유사 사이트에 자동 채우기를 한다.new AWS key, bastion SSH key, DB password after change)을 심는다. 누군가 이를 프로덕션이나 터미널에 복사한다.Production을 읽기만 할 수 있는 계약자는 lite EF에서 여전히 새 항목을 넣을 수 있다.대상 컬렉션에 접근 권한이 없으면, POST /api/ciphers/create는 쓰기가 이미 커밋된 후 404를 반환할 수 있다. 피해자는 여전히 동기화 시 해당 항목을 받는다.
2026년 물결 이후 bitwarden/server v2026.9.2 (9ee4e0eb)를 고정했다: admin-request TDE (CVE-2026-60104), SSO ExternalId 잘림 (CVE-2026-101878), 조직 가져오기 빈 컬렉션 (CVE-2026-43638). 이들은 모두 해결되었다. 생성 시의 컬렉션 ACL은 아니었다.
CiphersController.PostCreate는 조직 멤버십만 확인한 뒤, OrganizationId가 설정되면 skipPermissionCheck: true를 전달한다. 이어서 SaveDetailsAsync는 "이 컬렉션이 조직에 존재한다"를 유일한 컬렉션 검사로 취급한다.
Lite MariaDB/Postgres/SQLite는 EF를 사용한다. CreateAsyncReturnCipher는 조직 id가 존재하기 때문에 cipher.UserId = null을 설정한 뒤, 그 null을 UpdateCollectionsAsync로 넘긴다. Null 사용자 id는 조직 내 모든 컬렉션을 의미한다. SQL Server Dapper는 Cipher_UpdateCollections(ReadOnly = 0)에 대해 @UserId를 유지한다. 그 경로가 반례이다.
잘못 기록된 우회 경로들: 클라우드 POST /organizations를 셀프 호스트 생성으로 취급한 것(이는 [NotSelfHostedOnly]이며, lite는 라이선스 파일을 요구하므로 랩에서는 register 이후에 org 행을 시드함); 호출자가 읽을 수 없는 컬렉션에 심고 HTTP 404를 미스로 읽은 것(CollectionCipher 행은 이미 커밋됨); MSSQL lite가 이를 보여줄 것이라 가정한 것(Dapper는 호출자 id를 드롭하지 않음); BW_ENABLE_ADMIN을 꺼둔 채로 두어 lite가 User / OrganizationDomain을 마이그레이션하지 못하게 한 것(이 이미지에서 admin이 EF 마이그레이터임); /identity/connect/token에서 Bitwarden-Client-Version을 생략하고 password grant 400을 지켜본 것. 리버스 셸. 쇼극.
cd lab
./run.sh
오직 http://127.0.0.1:18160만 대상으로 합니다. 이미지 ghcr.io/bitwarden/lite:2026.9.2와 MariaDB를 사용합니다.
SUCCESS BITWARDEN-CIPHER-CREATE-ACL create-http=404 collectioncipher=1 victim-sync-has-cipher=yes cipher=<id> negative-http=404 readonly-http=200 BITWARDEN-CIPHER-CREATE-ACL-WITNESS
CollectionCipher가 커밋된 후 접근 권한이 없는 plant는 404를 반환합니다. 피해자의 GET /api/ciphers와 /api/sync 모두 새 id를 포함합니다. B에 대한 읽기 전용 요청은 200을 반환하며 여전히 plant합니다. 외부 조직 컬렉션은 조인 행 없이 404로 유지됩니다.
저장된 UserId 열을 null로 만든 후에도 CipherDetails_CreateWithCollections가 여전히 @UserId를 전달하는 것과 같은 방식으로, 저장하는 사용자 id를 EF UpdateCollectionsAsync에 전달합니다. 멤버 생성 경로에서 null 사용자를 "모든 컬렉션"으로 취급하지 마십시오. 조직 내 존재는 쓰기 접근 권한이 아닙니다.
CiphersController.cs (Post, PostCreate)CipherService.cs (SaveDetailsAsync)CipherRepository.cs (CreateAsyncReturnCipher, UpdateCollectionsAsync)