| CVSS 3.1: | 5.8
(Medium) CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N
| ||||||||||||||||
| Software: | Vaultwarden | ||||||||||||||||
| Published: | 19.05.2026 | ||||||||||||||||
| CVE: | CVE-2026-47160 ↗ | ||||||||||||||||
| Credits: | Reimar Fritz | ||||||||||||||||
| External Advisory: | Check out the external advisory ↗ |
The IP validation for the icon fetching endpoint /icons/{domain}/icon.png can be bypassed, resulting in a basic
Server-side request forgery (SSRF) vulnerability. The http_request_block_non_global_ips protection checks IPs using
Rust’s IpAddr::from_str(), which does not parse decimal, hex, or octal IP representations. However, the underlying
DNS/HTTP resolver (glibc/Hickory) resolves them.
This SSRF allows an attacker to perform HTTP/HTTPS requests from the Vaultwarden server to arbitrary (internal) IPs, but only responds with parsed favicon images (not raw response bodies), limiting this to a blind internal network/port discovery via timing and response-size differences.
Vaultwarden runs as two Docker containers:
Root cause (two-stage bypass):
Pre-connect filter bypass (src/http_client.rs:62-69): should_block_address() uses Rust’s IpAddr::from_str()
which only recognizes dotted-decimal notation. Decimal/hex/octal representations are not parsed as IPs and pass
through unchecked.
Post-resolve check bypass (src/http_client.rs:229): The post_resolve() function checks resolved IPs, but it is
only called from the custom DNS resolver (CustomDnsResolver::resolve_domain). When the URL host is a numeric
literal, the Rust url crate (WHATWG URL Standard compliant) parses it directly as an IPv4 address. Reqwest then
connects to the IP without invoking the custom DNS resolver at all, post_resolve() is never called.
Bypass payloads that successfully fetched from internal IPs:
2130706433 → 127.0.0.1 (decimal integer)0x7f000001 → 127.0.0.1 (hexadecimal)0177.0.0.1 → 127.0.0.1 (octal-dotted)Basic PoC:
From the logs:
[2026-04-02 09:28:04.648][request][INFO] GET /icons/2130706433/icon.png
[2026-04-02 09:28:04.651][response][INFO] (icon_internal) GET /icons/<domain>/icon.png => 200 OK
[2026-04-02 09:28:08.371][request][INFO] GET /icons/127.0.0.1/icon.png
[2026-04-02 09:28:08.371][vaultwarden::api::icons][WARN] Blocked address: 127.0.0.1
[2026-04-02 09:28:08.371][response][INFO] (icon_internal) GET /icons/<domain>/icon.png => 200 OK
The same applies to other resources, that are only internally available. An exemplary, additional nginx container on a Docker internal: true network, serving a favicon with no published ports, only reachable from the vaultwarden container, not from nginx or the outside, yet the decimal IP bypass (2886991875 for 172.20.0.3) successfully fetches the favicon through the icon endpoint.
From the logs:
[2026-04-02 09:40:43.433][request][INFO] GET /icons/172.19.0.4/icon.png
[2026-04-02 09:40:43.433][vaultwarden::api::icons][WARN] Blocked address: 172.19.0.4
[2026-04-02 09:40:43.433][response][INFO] (icon_internal) GET /icons/<domain>/icon.png => 200 OK
[2026-04-02 09:40:44.983][request][INFO] GET /icons/2886926340/icon.png
[2026-04-02 09:40:44.985][response][INFO] (icon_internal) GET /icons/<domain>/icon.png => 200 OK
An unauthenticated attacker can:
In unserem Hacking Cult Bootcamp durchläufst du in 4 Wochen einen strukturierten Security-Check. Von Grundlagenwissen, um zu prüfen, wo dein Team wirklich steht, bis hin zum konkreten Schwachstellen-Scan deiner Webanwendung. Und das komplett kostenlos!
Gerolfinger Str. 106
85049 Ingolstadt
+49 (841) 9937 3456
ops[LOESCH-MICH-SPAMSCHUTZ]@[LOESCH-MICH-AUCH]hackingcult.de