domain: webmention source is fetched without a resolved-address SSRF check #52

Closed
opened 2026-07-03 01:41:44 +00:00 by rosa · 1 comment
Owner

Found during a code review of the domain crate.

Location: crates/domain/src/services.rs:1054 (receive_webmention)
Severity: Security — SSRF

Problem

receive_webmention passes the raw source URL to the verify job with no global-address check beyond WebmentionTarget::parse, which only rejects the literal localhost and non-global IP literals and never resolves the hostname. There is no connect-level resolved-IP guard in the hardened client, so a source whose DNS resolves to a private address is fetched.

Failure scenario

An attacker POSTs a webmention with source=http://attacker.example/ whose A record points at 169.254.169.254, plus a valid public target. The verify job GETs the source and reaches an internal address.

Suggested fix

Validate the resolved IP at connect time (reject non-global addresses after DNS resolution) in the outbound client, not just the URL literal.

Found during a code review of the `domain` crate. **Location:** `crates/domain/src/services.rs:1054` (`receive_webmention`) **Severity:** Security — SSRF ## Problem `receive_webmention` passes the raw source URL to the verify job with no global-address check beyond `WebmentionTarget::parse`, which only rejects the literal localhost and non-global IP literals and never resolves the hostname. There is no connect-level resolved-IP guard in the hardened client, so a source whose DNS resolves to a private address is fetched. ## Failure scenario An attacker POSTs a webmention with `source=http://attacker.example/` whose A record points at `169.254.169.254`, plus a valid public target. The verify job GETs the source and reaches an internal address. ## Suggested fix Validate the resolved IP at connect time (reject non-global addresses after DNS resolution) in the outbound client, not just the URL literal.
Author
Owner

Triage: confirmed against current main. hardened_client in crates/infra/src/jobs/webmention.rs re-validates each redirect hop with WebmentionTarget::parse, which checks the URL literal only — nothing guards the resolved IP, so a hostname whose A record points at a private address is fetched. Routing to a human because the fix needs care: resolve-then-check has a DNS-rebinding TOCTOU, so the validated IP must be pinned for the actual connection (e.g. a custom resolver that filters non-global addresses at connect time).

Triage: confirmed against current `main`. `hardened_client` in `crates/infra/src/jobs/webmention.rs` re-validates each redirect hop with `WebmentionTarget::parse`, which checks the URL literal only — nothing guards the resolved IP, so a hostname whose A record points at a private address is fetched. Routing to a human because the fix needs care: resolve-then-check has a DNS-rebinding TOCTOU, so the validated IP must be pinned for the actual connection (e.g. a custom resolver that filters non-global addresses at connect time).
rosa closed this issue 2026-07-04 17:59:03 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rosa/vernier#52
No description provided.