Skip to content

[P2] [security] Pin the validated address to close DNS rebinding on UnifiedPush dispatch #39

Description

@AndreaDiazCorreia

Labels: security, enhancement, priority:medium

Severity: MEDIUM (latent while UnifiedPush is disabled)
Area: push/unifiedpush

Files:

  • src/push/endpoint_guard.rs (validate_endpoint)
  • src/push/unifiedpush.rs (send_to_token)

Problem

endpoint_guard::validate_endpoint resolves a domain host and refuses the
endpoint if any resolved address is non-public. That closes the straightforward
SSRF path from #4, but it leaves a time-of-check / time-of-use gap:

guard:    lookup_host("evil.example")  -> 93.184.216.34   (public, accepted)
reqwest:  connects, resolves again     -> 169.254.169.254 (internal)

An attacker who controls the authoritative DNS for a name can serve a public
address to the validation lookup and an internal one to the connection, with a
TTL short enough that no cache absorbs the difference. Winning the race is
harder than the IP-literal bypass #4 closed, but it is a well-known technique
and the guard should not be described as complete until it is closed.

The limitation is documented in the module docs and in docs/unifiedpush.md
rather than left implicit.

Proposal

Resolve once and connect to the address that was validated, so no second
resolution can occur:

The dedicated client this originally called for already exists:
UnifiedPushService::build_client() was added alongside #4 to refuse redirects,
which were a separate first-hop bypass of the same guard. The remaining work is
just to give that client a pinned resolver.

  • Implement reqwest::dns::Resolve backed by the addresses validate_endpoint
    already checked, and wire it into build_client via
    ClientBuilder::dns_resolver.
  • Keep FCM on the shared client (src/main.rs:69-76). It talks to a fixed Google
    endpoint and gains nothing from a pinned resolver.
  • Preserve the existing timeouts (2 s connect, 5 s total) and the no-redirect
    policy already set on that client.

Alternative considered and rejected: rewriting the URL to the validated IP and
setting a Host header. That breaks TLS SNI and certificate validation, which
trades one security property for another.

Acceptance criteria

  • A domain whose resolution changes between validation and connection cannot reach a non-public address.
  • FCM dispatch behaviour and timeouts are unchanged.
  • The no-redirect policy on the UnifiedPush client survives the change, with its regression test still passing.
  • The residual-risk paragraphs in src/push/endpoint_guard.rs and docs/unifiedpush.md are removed or rewritten to match the new behaviour.
  • Tests cover a resolver that returns a public address once and an internal one afterwards.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions