GHSA-Q537-8FR5-CW35

Vulnerability from github – Published: 2026-03-25 20:23 – Updated: 2026-03-27 21:36
VLAI
Summary
Activitypub-Federation has SSRF via 0.0.0.0 bypass in activitypub-federation-rust v4_is_invalid()
Details

Summary

The v4_is_invalid() function in activitypub-federation-rust (src/utils.rs) does not check for Ipv4Addr::UNSPECIFIED (0.0.0.0). An unauthenticated attacker controlling a remote domain can point it to 0.0.0.0, bypass the SSRF protection introduced by the fix for CVE-2025-25194 (GHSA-7723-35v7-qcxw), and reach localhost services on the target server.

Details

File: src/utils.rs in activitypub-federation-rust Function: v4_is_invalid(v4: Ipv4Addr) -> bool

The function checks is_private(), is_loopback(), is_link_local(), is_multicast(), and is_documentation() — but omits is_unspecified(). On Linux, macOS, and Windows, TCP connections to 0.0.0.0 are routed to localhost (127.0.0.1).

Additionally, ::ffff:0.0.0.0 (IPv4-mapped IPv6) also bypasses because v6_is_invalid() calls to_ipv4_mapped().is_some_and(v4_is_invalid), inheriting the same gap. Notably, v6_is_invalid() already includes is_unspecified() for native IPv6, making this an asymmetric oversight.

Independent secondary finding — DNS Rebinding TOCTOU: is_invalid_ip() resolves DNS via lookup_host() for validation, but reqwest resolves DNS again for the actual connection. With TTL=0 DNS responses, an attacker can return a legitimate IP for the first resolution (passes check) and 127.0.0.1 for the second (reqwest connects to localhost). CVSS for rebinding alone: 4.8 (AC:H).

PoC

1. Logic Proof (reproduced from source):

fn v4_is_invalid(v4: Ipv4Addr) -> bool {
    v4.is_private()
        || v4.is_loopback()
        || v4.is_link_local()
        || v4.is_multicast()
        || v4.is_documentation()
    // BUG: Missing || v4.is_unspecified()
}

assert_eq!(v4_is_invalid(Ipv4Addr::UNSPECIFIED), false);  // 0.0.0.0 PASSES validation
assert_eq!(v4_is_invalid(Ipv4Addr::LOCALHOST), true);     // 127.0.0.1 correctly blocked

2. OS Routing Verification:

$ connect(0.0.0.0:80) → ConnectionRefused

ConnectionRefused proves the OS routed to localhost (port 80 not listening). Any service on 0.0.0.0:PORT is reachable.

3. Attack Chain:

  1. Attacker configures DNS: evil.com A → 0.0.0.0
    1. Attacker sends ActivityPub activity referencing https://evil.com/actor
    1. Library calls verify_url_valid() → is_invalid_ip() → resolves to 0.0.0.0
    1. v4_is_invalid(0.0.0.0) returns false (BYPASS)
    1. reqwest connects to 0.0.0.0 → reaches localhost services

Impact

  • Direct: Bypasses the SSRF protection layer for all ActivityPub federation traffic
    • Downstream: 6+ dependent projects affected including Lemmy (13.7k stars), hatsu, gill, ties, fediscus, fediverse-axum
    • Attacker can: Access cloud instance metadata (169.254.169.254 via rebinding), reach internal services on localhost, port scan internal infrastructure

Suggested Fix

fn v4_is_invalid(v4: Ipv4Addr) -> bool {
    v4.is_private()
        || v4.is_loopback()
        || v4.is_link_local()
        || v4.is_multicast()
        || v4.is_documentation()
        || v4.is_unspecified()    // ADD: blocks 0.0.0.0
        || v4.is_broadcast()      // ADD: blocks 255.255.255.255
}

For DNS rebinding TOCTOU, pin the resolved IP:

let resolved_ip = lookup_host((domain, 80)).await?;
// validate resolved_ip...
let client = reqwest::Client::builder()
    .resolve(domain, resolved_ip)  // pin resolution
    .build()?;
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "activitypub_federation"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.7.0-beta.9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33693"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-25T20:23:38Z",
    "nvd_published_at": "2026-03-27T01:16:18Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nThe `v4_is_invalid()` function in `activitypub-federation-rust` (`src/utils.rs`) does not check for `Ipv4Addr::UNSPECIFIED` (0.0.0.0). An unauthenticated attacker controlling a remote domain can point it to 0.0.0.0, bypass the SSRF protection introduced by the fix for CVE-2025-25194 (GHSA-7723-35v7-qcxw), and reach localhost services on the target server.\n\n### Details\n\n**File:** `src/utils.rs` in `activitypub-federation-rust`\n**Function:** `v4_is_invalid(v4: Ipv4Addr) -\u003e bool`\n\nThe function checks `is_private()`, `is_loopback()`, `is_link_local()`, `is_multicast()`, and `is_documentation()` \u2014 but omits `is_unspecified()`. On Linux, macOS, and Windows, TCP connections to 0.0.0.0 are routed to localhost (127.0.0.1).\n\nAdditionally, `::ffff:0.0.0.0` (IPv4-mapped IPv6) also bypasses because `v6_is_invalid()` calls `to_ipv4_mapped().is_some_and(v4_is_invalid)`, inheriting the same gap. Notably, `v6_is_invalid()` already includes `is_unspecified()` for native IPv6, making this an asymmetric oversight.\n\n**Independent secondary finding \u2014 DNS Rebinding TOCTOU:**\n`is_invalid_ip()` resolves DNS via `lookup_host()` for validation, but `reqwest` resolves DNS again for the actual connection. With TTL=0 DNS responses, an attacker can return a legitimate IP for the first resolution (passes check) and 127.0.0.1 for the second (reqwest connects to localhost). CVSS for rebinding alone: 4.8 (AC:H).\n\n### PoC\n\n**1. Logic Proof (reproduced from source):**\n\n```rust\nfn v4_is_invalid(v4: Ipv4Addr) -\u003e bool {\n    v4.is_private()\n        || v4.is_loopback()\n        || v4.is_link_local()\n        || v4.is_multicast()\n        || v4.is_documentation()\n    // BUG: Missing || v4.is_unspecified()\n}\n\nassert_eq!(v4_is_invalid(Ipv4Addr::UNSPECIFIED), false);  // 0.0.0.0 PASSES validation\nassert_eq!(v4_is_invalid(Ipv4Addr::LOCALHOST), true);     // 127.0.0.1 correctly blocked\n```\n\n**2. OS Routing Verification:**\n\n```\n$ connect(0.0.0.0:80) \u2192 ConnectionRefused\n```\n\nConnectionRefused proves the OS routed to localhost (port 80 not listening). Any service on 0.0.0.0:PORT is reachable.\n\n**3. Attack Chain:**\n\n1. Attacker configures DNS: `evil.com A \u2192 0.0.0.0`\n2. 2. Attacker sends ActivityPub activity referencing `https://evil.com/actor`\n3. 3. Library calls `verify_url_valid()` \u2192 `is_invalid_ip()` \u2192 resolves to 0.0.0.0\n4. 4. `v4_is_invalid(0.0.0.0)` returns `false` (BYPASS)\n5. 5. `reqwest` connects to 0.0.0.0 \u2192 reaches localhost services\n### Impact\n\n- **Direct:** Bypasses the SSRF protection layer for all ActivityPub federation traffic\n- - **Downstream:** 6+ dependent projects affected including Lemmy (13.7k stars), hatsu, gill, ties, fediscus, fediverse-axum\n- - **Attacker can:** Access cloud instance metadata (169.254.169.254 via rebinding), reach internal services on localhost, port scan internal infrastructure\n### Suggested Fix\n\n```rust\nfn v4_is_invalid(v4: Ipv4Addr) -\u003e bool {\n    v4.is_private()\n        || v4.is_loopback()\n        || v4.is_link_local()\n        || v4.is_multicast()\n        || v4.is_documentation()\n        || v4.is_unspecified()    // ADD: blocks 0.0.0.0\n        || v4.is_broadcast()      // ADD: blocks 255.255.255.255\n}\n```\n\nFor DNS rebinding TOCTOU, pin the resolved IP:\n\n```rust\nlet resolved_ip = lookup_host((domain, 80)).await?;\n// validate resolved_ip...\nlet client = reqwest::Client::builder()\n    .resolve(domain, resolved_ip)  // pin resolution\n    .build()?;\n```",
  "id": "GHSA-q537-8fr5-cw35",
  "modified": "2026-03-27T21:36:54Z",
  "published": "2026-03-25T20:23:38Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/LemmyNet/lemmy/security/advisories/GHSA-q537-8fr5-cw35"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33693"
    },
    {
      "type": "WEB",
      "url": "https://github.com/LemmyNet/activitypub-federation-rust/commit/4ae8532b17bc35755240b7f55d4a5b7665351599"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/LemmyNet/activitypub-federation-rust"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-7723-35v7-qcxw"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Activitypub-Federation has SSRF via 0.0.0.0 bypass in activitypub-federation-rust v4_is_invalid()"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…