
Vaultwarden 1.37.3 Standard-X-Real-IP trusted-local umgeht Login-Ratenlimits bei Docker NAT
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · vaultwarden-ip-header-spoof
Vaultwarden 1.37.3 - dani-garcia
Standardmäßig behandelt IP_HEADER=X-Real-IP zusammen mit IP_HEADER_TRUSTED_PROXIES=local jeden nicht-globalen TCP-Peer als Reverse-Proxy. Das NAT des veröffentlichten Ports im Standard-Docker ist RFC1918. Ein vom Client gelieferter X-Real-IP-Header wird zum Schlüssel für das Rate-Limiting. Eindeutige Werte teilen sich nie einen Burst.
| ID | noch keine CVE |
| CWE | CWE-307 / CWE-290 |
| CVSS | Hoch: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Produkt | Vaultwarden |
| Betroffen | 1.37.3 (eb212e23). Image vaultwarden/server:1.37.3. Docker veröffentlichter Port / userland-proxy / Desktop-NAT oder jeder RFC1918-Peer. |
| Auth | unauthentifiziert |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Der Angreifer entfernt den IP-basierten Brute-Force-Schutz. Er sendet bei jedem Versuch einen frischen X-Real-IP-Header. Der Governor ist eine DashMap, die auf diese Adresse geschlüsselt ist, sodass Burst und Zeitfenster nie auslösen.
POST /identity/connect/token Password-Grant. Der Standard-Burst beträgt 10 pro 60 Sekunden pro IP. Eindeutige Header setzen den Bucket zurück./api/two-factor/send-email-login, POST /admin sowie unauth hint / prelogin / delete-recover / auth-request / Send access schlüsseln alle auf ClientIp.docker run -p 80:80 und Compose 80:80 machen den Peer zu einer Bridge- oder Desktop-NAT-Adresse. trusted=local ist !is_global(peer). Das passt.Ein Reverse-Proxy, der X-Real-IP durch den echten Client ersetzt, und ein Host-Bind, dessen Peer eine globale Adresse ist, halten den Governor beide ehrlich. Das Loch ist der Standard plus ein veröffentlichter Port ohne Überschreibung.
Ich habe dani-garcia/vaultwarden 1.37.3 (eb212e23) nach den GHSAs von 1.35.x / 1.36.0 / 1.37.0 festgenagelt. GHSA-c5rv setzte send-email-login auf LIMITER_LOGIN. Der Rest ist, wie ClientIp gewählt wird.
config.rs setzt ip_header standardmäßig auf X-Real-IP und ip_header_trusted_proxies auf local. auth.rs ip_header_is_trusted behandelt local als !is_global(remote). ratelimit.rs schlüsselt LIMITER_LOGIN auf diese IpAddr. Docker-Bridge, Desktop-VM-NAT und Loopback sind alle nicht-global.
Ich habe das Standard-vaultwarden/server:1.37.3 auf Loopback 127.0.0.1:18170:80 ohne Overwrite-Proxy gestartet, plus eine zweite Instanz auf 18171 mit IP_HEADER=none. [email protected] registriert. Password-Grant mit falschem Passwort und ohne Header wurde bei Anfrage 11 mit 429 beantwortet (Burst 10). Derselbe Grant mit X-Real-IP: 198.51.100.{n} pro Versuch eindeutig blieb vierzehn Mal bei HTTP 400, null 429er. Auf der none-Instanz wurde mit eindeutigen 203.0.113.{n} weiterhin bei 11 mit 429 geantwortet.
SUCCESS VAULTWARDEN-IP-HEADER-SPOOF nohdr-429-at=11 spoof-400=14 spoof-429=0 none-429-at=11 VAULTWARDEN-IP-HEADER-SPOOF-WITNESS
Bereits dokumentierte Irrwege: Das 1.37.3-Image war nicht lokal, sodass das erste Compose auf einen Pull wartete; das erste /alive war eine leere Antwort, während Rocket band, dann 200 beim zweiten Warten; Dummy-Register-JSON (masterPasswordHash + ein kurzer key) reichte aus, kein Long-Key-Retry. Eine Reverse Shell. Theater. Das Orakel ist 429 ohne den Header und 400 mit einem eindeutigen.
cd lab
./run.sh
Ziel nur http://127.0.0.1:18170 (Standard) und http://127.0.0.1:18171 (IP_HEADER=none). Image vaultwarden/server:1.37.3.
IP_HEADER standardmäßig auf none setzen oder IP_HEADER_TRUSTED_PROXIES standardmäßig auf eine leere Liste / explizite Proxy-CIDR setzen. local als „jeder RFC1918-Peer" ist der Fall des veröffentlichten Ports. Die Existenz eines Forwarding-Headers ist kein Beweis für einen Proxy.
src/config.rs (ip_header, ip_header_trusted_proxies)src/auth.rs (ip_header_is_trusted, ClientIp)src/ratelimit.rs