From 051e4ffdd274707ff2c60acd7a421e78e7c466bf Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Fri, 27 Mar 2026 21:08:39 -0700 Subject: [PATCH] updated in response to ossf scorecard --- CHANGELOG.md | 2 +- .../src/validation/http_validation.rs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1b9832..2b5221a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. ## [v1.91.0] -- Added SSRF protection for credential validation: outbound HTTP requests now block connections to loopback, private, link-local, and other non-public IP addresses. Redirect targets are also validated. Use `--allow-internal-ips` to opt out when scanning internal infrastructure. +- Added SSRF protection for credential validation: outbound HTTP requests now block connections to loopback, private, link-local, and other non-public IP addresses. Redirects to non-public IP-literal addresses are also blocked. Use `--allow-internal-ips` to opt out when scanning internal infrastructure. - Consolidated JWT SSRF checks to use the shared `is_ssrf_safe_ip` function, covering additional reserved ranges (CGNAT, documentation, benchmarking, IPv6 unique-local). - Removed `ipnet` dependency from `kingfisher-scanner` (no longer needed). - Remediated current RustSec vulnerability findings by upgrading core dependencies including `gix`, `mysql_async`, `axum`, `indicatif`, `quick-xml`, and `console`. diff --git a/crates/kingfisher-scanner/src/validation/http_validation.rs b/crates/kingfisher-scanner/src/validation/http_validation.rs index a083fe3..40cbae0 100644 --- a/crates/kingfisher-scanner/src/validation/http_validation.rs +++ b/crates/kingfisher-scanner/src/validation/http_validation.rs @@ -401,6 +401,10 @@ pub fn is_ssrf_safe_ip(ip: &IpAddr) -> bool { match ip { IpAddr::V4(v4) => { let octets = v4.octets(); + // 0.0.0.0/8 — "This host on this network" (RFC 1122); not routable + if octets[0] == 0 { + return false; + } // Private ranges (RFC 1918) if octets[0] == 10 { return false; @@ -446,6 +450,12 @@ pub fn is_ssrf_safe_ip(ip: &IpAddr) -> bool { return is_ssrf_safe_ip(&IpAddr::V4(mapped)); } let segments = v6.segments(); + // IPv4-compatible IPv6 addresses (::/96, e.g., ::127.0.0.1) are + // deprecated (RFC 4291 §2.5.5.1) and can bypass IPv4-only checks. + // Reject the entire ::/96 range. + if segments[..6].iter().all(|&s| s == 0) { + return false; + } // Unique local (fc00::/7) if segments[0] & 0xfe00 == 0xfc00 { return false; @@ -507,6 +517,13 @@ mod tests { assert!(!is_ssrf_safe_ip(&IpAddr::V4(Ipv4Addr::UNSPECIFIED))); } + #[test] + fn rejects_ipv4_this_network() { + // 0.0.0.0/8 — "This host on this network" (RFC 1122) + assert!(!is_ssrf_safe_ip(&IpAddr::V4(Ipv4Addr::new(0, 0, 0, 1)))); + assert!(!is_ssrf_safe_ip(&IpAddr::V4(Ipv4Addr::new(0, 255, 255, 255)))); + } + #[test] fn rejects_ipv4_private_rfc1918() { // 10.0.0.0/8 @@ -598,6 +615,16 @@ mod tests { assert!(is_ssrf_safe_ip(&IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x0808, 0x0808)))); } + #[test] + fn rejects_ipv4_compatible_ipv6() { + // ::127.0.0.1 — deprecated IPv4-compatible IPv6 (loopback) + assert!(!is_ssrf_safe_ip(&IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x7f00, 0x0001)))); + // ::10.0.0.1 — deprecated IPv4-compatible IPv6 (private) + assert!(!is_ssrf_safe_ip(&IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x0a00, 0x0001)))); + // ::8.8.8.8 — even public IPv4 in ::/96 is rejected (deprecated range) + assert!(!is_ssrf_safe_ip(&IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x0808, 0x0808)))); + } + #[test] fn accepts_public_ipv4() { assert!(is_ssrf_safe_ip(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));