diff --git a/backend/src/app/util/ssrf.clj b/backend/src/app/util/ssrf.clj index 76f4e2db4e..a49c317441 100644 --- a/backend/src/app/util/ssrf.clj +++ b/backend/src/app/util/ssrf.clj @@ -10,7 +10,7 @@ The blocklist covers the standard JVM InetAddress classifications plus explicit ranges: IPv6 ULA, IPv4-mapped loopback, cloud metadata, operator-supplied CIDRs and the IPv6 transition mechanisms NAT64, 6to4 - and Teredo (whose embedded IPv4 is also re-checked)." + and Teredo." (:require [app.common.exceptions :as ex] [app.common.logging :as l] @@ -141,21 +141,6 @@ (and (= b0 0x20) (= b1 0x01) (= b2 0x00) (= b3 0x00)) :teredo :else nil))) -(defn- transition-embedded-ipv4 - "Extract the IPv4 address embedded in an IPv6 transition mechanism address, - as a 4-byte array. Returns nil when the address is not a transition address. - Teredo embeds the client IPv4 XOR-inverted in the last 4 bytes." - ^bytes [^bytes bs] - (when (= (alength bs) 16) - (case (transition-prefix bs) - :nat64 (byte-array [(aget bs 12) (aget bs 13) (aget bs 14) (aget bs 15)]) - :6to4 (byte-array [(aget bs 2) (aget bs 3) (aget bs 4) (aget bs 5)]) - :teredo (byte-array [(bit-xor (bit-and (aget bs 12) 0xFF) 0xFF) - (bit-xor (bit-and (aget bs 13) 0xFF) 0xFF) - (bit-xor (bit-and (aget bs 14) 0xFF) 0xFF) - (bit-xor (bit-and (aget bs 15) 0xFF) 0xFF)]) - nil))) - (defn- blocked-address? "Check if an InetAddress should be blocked. Returns true if blocked." [^InetAddress addr] @@ -175,18 +160,15 @@ ;; Cloud metadata IPs (exact match) (contains? cloud-metadata-ips (.getHostAddress addr)) - ;; Extra blocked CIDRs (IPv4 only) + ;; Extra blocked CIDRs (IPv4 only) and IPv6 transition mechanisms (let [bs (.getAddress addr)] (if (= (alength bs) 4) (or (some #(in-cidr4? bs %) extra-blocked-ranges) (some #(in-cidr4? bs %) extra-blocked-cidrs)) - ;; IPv6 transition mechanisms (NAT64/6to4/Teredo): the range is blocked - ;; outright and any embedded IPv4 is re-checked against this blocklist. - (boolean - (when (= (alength bs) 16) - (or (transition-prefix bs) - (when-let [embedded (transition-embedded-ipv4 bs)] - (blocked-address? (InetAddress/getByAddress embedded)))))))))) + ;; IPv6 transition mechanisms (NAT64/6to4/Teredo): the range is + ;; rejected outright. + (boolean (when (= (alength bs) 16) + (transition-prefix bs))))))) (defn resolve-host "Resolve a hostname to all InetAddress objects. Wraps InetAddress/getAllByName @@ -205,8 +187,8 @@ (loopback, link-local, site-local, multicast, any-local, cloud-metadata 169.254.169.254, IPv6 ULA fc00::/7, IPv6 transition mechanisms NAT64 64:ff9b::/96, 6to4 2002::/16 and Teredo - 2001:0000::/32 — including any IPv4 embedded in them —, - IPv4-mapped IPv6 of any blocked IPv4, plus operator-supplied CIDRs). + 2001:0000::/32, IPv4-mapped IPv6 of any blocked IPv4, + plus operator-supplied CIDRs). When the host is an IP literal (decimal/octal/hex/IPv6) it is normalized via `com.google.common.net.InetAddresses` before the check. diff --git a/backend/test/backend_tests/util_ssrf_test.clj b/backend/test/backend_tests/util_ssrf_test.clj index fe479df936..c8c4d6a4ea 100644 --- a/backend/test/backend_tests/util_ssrf_test.clj +++ b/backend/test/backend_tests/util_ssrf_test.clj @@ -94,15 +94,10 @@ (t/is (false? (ssrf/safe-url? "http://[2002:c0a8:101::1]/foo")))) (t/deftest validate-url-blocks-teredo-encoded-addresses - ;; Teredo server prefix 2001:0000::/32, embedded client IPv4 is XOR-inverted + ;; Teredo server prefix 2001:0000::/32 (t/is (false? (ssrf/safe-url? "http://[2001:0000:4136:e378:8000:63bf:3fff:fdd2]/foo")))) -(t/deftest validate-url-extra-cidrs-apply-to-transition-addresses - ;; 2002:c633:6407:: embeds 198.51.100.7, covered by the operator CIDR below - (binding [ssrf/extra-blocked-cidrs #{(ssrf/parse-cidr "198.51.100.0/24")}] - (t/is (false? (ssrf/safe-url? "http://[2002:c633:6407::1]/foo"))))) - (t/deftest validate-url-blocks-encoded-loopback ;; Decimal encoding of 127.0.0.1 = 2130706433 ;; InetAddress normalizes this to 127.0.0.1