
Vaultwarden 1.37.3 con X-Real-IP predeterminado trusted-local omite los límites de intentos de inicio de sesión en Docker NAT
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · vaultwarden-ip-header-spoof
Vaultwarden 1.37.3 - dani-garcia
El valor predeterminado IP_HEADER=X-Real-IP junto con IP_HEADER_TRUSTED_PROXIES=local trata cualquier peer TCP no global como un proxy inverso. El NAT de puerto publicado de Docker de serie es RFC1918. Un X-Real-IP proporcionado por el cliente se convierte en la clave del límite de tasa. Los valores únicos nunca comparten una ráfaga.
| ID | sin CVE todavía |
| 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 |
| Producto | Vaultwarden |
| Afectado | 1.37.3 (eb212e23). Imagen vaultwarden/server:1.37.3. Docker published-port / userland-proxy / Desktop NAT, o cualquier peer RFC1918. |
| Autenticación | no autenticado |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
El atacante elimina la protección contra fuerza bruta basada en IP. Envía un X-Real-IP nuevo en cada intento. El gobernador es un DashMap indexado por esa dirección, por lo que la ráfaga y el período nunca se activan.
POST /identity/connect/token. La ráfaga predeterminada es de 10 por cada 60 segundos por IP. Las cabeceras únicas reinician el cubo./api/two-factor/send-email-login, POST /admin, además de hint / prelogin / delete-recover / auth-request / acceso a Send sin autenticación, todos se indexan por ClientIp.docker run -p 80:80 de serie y el compose 80:80 hacen que el peer sea una dirección de bridge o de NAT de Desktop. trusted=local es !is_global(peer). Eso coincide.Un proxy inverso que reemplaza X-Real-IP con el cliente real, y un bind de host cuyo peer es una dirección global, ambos mantienen honesto al gobernador. El agujero es el valor predeterminado más un puerto publicado sin sobrescritura.
Fijé dani-garcia/vaultwarden 1.37.3 (eb212e23) tras los GHSAs de 1.35.x / 1.36.0 / 1.37.0. GHSA-c5rv puso send-email-login en LIMITER_LOGIN. Lo que queda es cómo se elige ClientIp.
config.rs establece por defecto ip_header a X-Real-IP y ip_header_trusted_proxies a local. auth.rs ip_header_is_trusted trata local como !is_global(remote). ratelimit.rs indexa LIMITER_LOGIN por ese IpAddr. El bridge de Docker, el NAT de la VM de Desktop y el loopback son todos no globales.
Levanté el vaultwarden/server:1.37.3 de serie en loopback 127.0.0.1:18170:80 sin overwrite-proxy, más una segunda instancia en 18171 con IP_HEADER=none. Registré [email protected]. La concesión de contraseña con una contraseña incorrecta y sin cabecera devolvió 429 en la petición 11 (ráfaga 10). La misma concesión con X-Real-IP: 198.51.100.{n} único por intento se mantuvo en HTTP 400 catorce veces, cero 429s. En la instancia none, 203.0.113.{n} únicos siguieron devolviendo 429 en la 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
Caminos equivocados ya registrados: la imagen 1.37.3 no era local, así que el primer compose esperó a un pull; el primer /alive fue una respuesta vacía mientras Rocket se enlazaba, luego 200 en la espera 2; el JSON de registro ficticio (masterPasswordHash + una key corta) fue suficiente, sin reintento de clave larga. Una reverse shell. Teatro. El oráculo es 429 sin la cabecera y 400 con una única.
cd lab
./run.sh
Objetivo solo http://127.0.0.1:18170 (predeterminado) y http://127.0.0.1:18171 (IP_HEADER=none). Imagen vaultwarden/server:1.37.3.
Establecer por defecto IP_HEADER a none, o establecer por defecto IP_HEADER_TRUSTED_PROXIES a una lista vacía / CIDR de proxy explícito. local como "cualquier peer RFC1918" es el caso del puerto publicado. La existencia de una cabecera de reenvío no es prueba de un proxy.
src/config.rs (ip_header, ip_header_trusted_proxies)src/auth.rs (ip_header_is_trusted, ClientIp)src/ratelimit.rs