Skip to content

safe-bash curl: denyPrivateNetworks misses CGNAT 100.64/10 (Alibaba metadata 100.100.100.200), 198.18/15, multicast, 6to4 2002::/16, fec0::/10 — SSRF to cloud metadata and internal services #656

Description

@kamilio

Where: packages/safe-bash/src/commands/network/private-address.tsprivateIPv4() (lines 1-5) covers only 10/8, 127/8, 0/8, 169.254/16, 192.168/16, 172.16/12; the IPv6 branch (lines 10-24) covers only ::, mapped/translated/NAT64-well-known (64:ff9b::/96), fc00::/7 and fe80::/10. transport.ts:52-66 pins the resolved address and re-checks it with the same predicate, so literal, DNS-resolved and redirect hops all pass for the missing ranges.

Missing ranges:

  • IPv4: 100.64.0.0/10 (CGNAT — Alibaba Cloud ECS metadata 100.100.100.200, Tailscale, many k8s/VPC internal ranges), 198.18.0.0/15 (benchmarking), 224.0.0.0/4 (multicast), 240.0.0.0/4 (reserved), 255.255.255.255; optionally 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24.
  • IPv6: 64:ff9b:1::/48 (local-use NAT64), 2002::/16 (6to4 — embeds an IPv4 in hextets 1-2, e.g. 2002:a9fe:a9fe:: = 169.254.169.254), fec0::/10 (site-local), ff00::/8 (multicast), IPv4-compatible ::a.b.c.d / ::7f00:1.

PoC:

import { privateHostname } from ".../commands/network/private-address.js";
for (const h of ["100.100.100.200","100.64.0.1","198.18.0.1","224.0.0.1","240.0.0.1","255.255.255.255",
  "[2002:a9fe:a9fe::1]","[fec0::1]","[ff02::1]","[64:ff9b:1::a9fe:a9fe]","[::7f00:1]"]) console.log(h, privateHostname(h));

End to end via the Shell with createOriginAuthorizer("*", { denyPrivateNetworks: true }) + createNodeHttpTransport + a stub resolver mapping cgnat.example → 100.100.100.200, maxTimeMs: 2000:

curl -s http://100.100.100.200/latest/meta-data/     # literal
curl -s http://cgnat.example/latest/meta-data/       # DNS path
curl -s http://169.254.169.254/latest/meta-data/     # control

Measured: privateHostnamefalse for every address in the list above; true for 169.254.169.254, 127.0.0.1, [fd00::1]. curl literal → exit 28 after 2002 ms (connect timed out = the request was dialled); DNS path → exit 28, 2088 ms; control → CurlError 7 "Private network destination denied" in 1 ms. Re-verified independently (same predicate table).

Impact: (b) — on Alibaba Cloud, and on any deployment whose internal services live in 100.64/10 (Tailscale, CGNAT VPCs) or reachable via 6to4, the shipped "deny private" policy does not stop metadata-credential theft or internal-service SSRF. network/README.md advertises the option as covering loopback/link-local/RFC-1918/IPv6-local only, so this is a coverage gap in the only shipped egress policy rather than a contract break. Follow-up to closed #600/#619.

Fix: extend privateIPv4 with 100.64/10, 198.18/15, 224/4, 240/4, 255.255.255.255 (and the documentation ranges); in the IPv6 branch add 64:ff9b:1::/48, fec0::/10, ff00::/8, 2002::/16 (extract embedded IPv4 from hextets 1-2 and recheck) and IPv4-compatible ::a.b.c.d (hextets 0-5 zero → recheck hextets 6-7 as IPv4); document the complete list.

Found in security audit v3 (2026-09-07).

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions