Skip to content

Security: unbounded Prometheus label cardinality on public redirect => memory DoS + PII in unauthenticated /metrics #129

Description

@stephane-segning

Summary

The public redirect service records attacker-controlled values as unbounded Prometheus label sets, allowing an unauthenticated memory-exhaustion DoS, and exposes PII (client IPs, user agents, per-slug traffic) on an unauthenticated /metrics endpoint. IP attribution in the metrics is also spoofable via X-Forwarded-For.

Evidence

  • crates/vym-fyi-server-redirect/src/handlers/short_link.rs:23-28redirect_slug_requests_total uses the raw slug as a label ("slug" => slug.clone()). The adjacent slug_len bucketing (line 22 comment) only bounds slug_len, not the full slug label. Note this counter is incremented for every request, including non-existent slugs.
  • crates/vym-fyi-model/src/services/axum_metrics.rs:53-60http_requests_by_ip_total labels include path (= the slug on the redirect server), user_agent, and client_ip.
  • crates/vym-fyi-model/src/services/axum_metrics.rs:99-110extract_ip trusts the first X-Forwarded-For value unconditionally; there is no trusted-proxy configuration.
  • /metrics is registered with no auth on both servers (crates/vym-fyi-server-redirect/src/main.rs:41-43, crates/vym-fyi-server-crud/src/main.rs:46-49).
  • The metrics crate recorder (via axum-prometheus) stores every unique name+label-set in memory and never evicts.

Exploitation scenarios

  1. Memory-exhaustion DoS (public, unauthenticated): attacker issues GET /<random> with a unique User-Agent and unique spoofed X-Forwarded-For per request → millions of in-memory time series → OOM / restart loop.
  2. PII disclosure: anyone reaching /metrics reads every client IP, user agent, and per-slug hit counts (link-usage disclosure).
  3. Spoofing/framing: attacker sets X-Forwarded-For: <victim-ip> → victim's IP is attributed to attacker traffic in dashboards/alerts.

Severity: HIGH.

Suggested fix

  • Drop the slug label from redirect_slug_requests_total (keep only slug_len).
  • In record_ip_metrics: bucket/truncate path; truncate or hash user_agent; never store raw client_ip as a label (or bound it via a fixed-size hash).
  • Trust X-Forwarded-For only when the peer is a configured trusted proxy (fall back to the socket address).
  • Require auth (or network-scoped access) for /metrics.

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

    bugSomething isn't workingsecuritySecurity findings and hardening

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions