
Vaultwarden 1.37.3: X-Real-IP padrão trusted-local contorna os limites de taxa de login no Docker NAT
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · vaultwarden-ip-header-spoof
Vaultwarden 1.37.3 - dani-garcia
O padrão IP_HEADER=X-Real-IP mais IP_HEADER_TRUSTED_PROXIES=local trata qualquer peer TCP não global como um proxy reverso. O NAT de porta publicada do Docker padrão é RFC1918. Um X-Real-IP fornecido pelo cliente torna-se a chave do rate-limit. Valores únicos nunca compartilham uma rajada.
| ID | sem CVE ainda |
| CWE | CWE-307 / CWE-290 |
| CVSS | Alto: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Produto | Vaultwarden |
| Afetado | 1.37.3 (eb212e23). Imagem vaultwarden/server:1.37.3. Docker published-port / userland-proxy / Desktop NAT, ou qualquer peer RFC1918. |
| Autenticação | não autenticado |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1 |
O atacante remove a proteção de força bruta baseada em IP. Ele envia um X-Real-IP novo a cada tentativa. O governor é um DashMap com chave nesse endereço, então a rajada e o período nunca disparam.
POST /identity/connect/token password-grant. A rajada padrão é 10 por 60 segundos por IP. Headers únicos reiniciam o bucket./api/two-factor/send-email-login, POST /admin, além de unauth hint / prelogin / delete-recover / auth-request / Send access, todos usam ClientIp como chave.docker run -p 80:80 padrão e compose 80:80 tornam o peer um endereço de bridge ou Desktop NAT. trusted=local é !is_global(peer). Isso corresponde.Um proxy reverso que substitui X-Real-IP pelo cliente real, e um bind de host cujo peer é um endereço global, ambos mantêm o governor honesto. A falha é o padrão mais uma porta publicada sem sobrescrita.
Eu fixei dani-garcia/vaultwarden 1.37.3 (eb212e23) após os GHSAs de 1.35.x / 1.36.0 / 1.37.0. GHSA-c5rv colocou send-email-login em LIMITER_LOGIN. O que sobrou é como ClientIp é escolhido.
config.rs define ip_header como X-Real-IP por padrão e ip_header_trusted_proxies como local. auth.rs ip_header_is_trusted trata local como !is_global(remote). ratelimit.rs usa esse IpAddr como chave de LIMITER_LOGIN. Docker bridge, Desktop VM NAT e loopback são todos não globais.
Eu levantei o vaultwarden/server:1.37.3 padrão em loopback 127.0.0.1:18170:80 sem overwrite-proxy, mais uma segunda instância em 18171 com IP_HEADER=none. Registrei [email protected]. Password-grant com uma senha errada e sem header retornou 429 na requisição 11 (rajada 10). O mesmo grant com X-Real-IP: 198.51.100.{n} único por tentativa permaneceu HTTP 400 catorze vezes, zero 429s. Na instância none, 203.0.113.{n} únicos ainda retornaram 429 na 11.
SUCCESS VAULTWARDEN-IP-HEADER-SPOOF nohdr-429-at=11 spoof-400=14 spoof-429=0 none-429-at=11 VAULTWARDEN-IP-HEADER-SPOOF-WITNESS
Caminhos errados já registrados: a imagem 1.37.3 não era local, então o primeiro compose esperou por um pull; o primeiro /alive foi uma resposta vazia enquanto o Rocket fazia bind, depois 200 na espera 2; o JSON de registro dummy (masterPasswordHash + uma key curta) foi suficiente, sem retry de chave longa. Um reverse shell. Teatro. O oráculo é 429 sem o header e 400 com um único.
cd lab
./run.sh
Alvo apenas http://127.0.0.1:18170 (padrão) e http://127.0.0.1:18171 (IP_HEADER=none). Imagem vaultwarden/server:1.37.3.
Definir IP_HEADER como none por padrão, ou definir IP_HEADER_TRUSTED_PROXIES como uma lista vazia / CIDR de proxy explícito por padrão. local como "qualquer peer RFC1918" é o caso da porta publicada. A existência de um header de encaminhamento não é prova de um proxy.
src/config.rs (ip_header, ip_header_trusted_proxies)src/auth.rs (ip_header_is_trusted, ClientIp)src/ratelimit.rs