use std::net::IpAddr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SsrfPolicy {
#[default]
PublicHttpsOnly,
AllowInternal,
}
impl SsrfPolicy {
pub fn allows_internal(self) -> bool {
matches!(self, Self::AllowInternal)
}
}
pub fn is_ssrf_blocked_ip(ip: &IpAddr) -> bool {
match ip {
IpAddr::V4(v4) => {
v4.is_private()
|| v4.is_loopback()
|| v4.is_link_local()
|| v4.is_broadcast()
|| v4.is_multicast()
|| v4.is_unspecified()
|| v4.octets()[0] == 0
|| (v4.octets()[0] == 100
&& (v4.octets()[1] >= 64 && v4.octets()[1] <= 127))
|| (v4.octets()[0] == 198
&& (v4.octets()[1] == 18 || v4.octets()[1] == 19))
|| v4.octets()[0] >= 240
}
IpAddr::V6(v6) => {
v6.is_loopback()
|| v6.is_multicast()
|| v6.is_unspecified()
|| (v6.segments()[0] & 0xfe00) == 0xfc00
|| (v6.segments()[0] & 0xffc0) == 0xfe80
|| (v6.segments()[0] & 0xffc0) == 0xfec0
|| v6
.to_ipv4_mapped()
.map(|v4| {
v4.is_private()
|| v4.is_loopback()
|| v4.is_link_local()
|| v4.is_broadcast()
|| v4.is_multicast()
|| v4.is_unspecified()
|| v4.octets()[0] == 0
|| (v4.octets()[0] == 100
&& (v4.octets()[1] >= 64 && v4.octets()[1] <= 127))
|| (v4.octets()[0] == 198
&& (v4.octets()[1] == 18 || v4.octets()[1] == 19))
|| v4.octets()[0] >= 240
})
.unwrap_or(false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
fn v4(s: &str) -> IpAddr {
IpAddr::V4(s.parse::<Ipv4Addr>().expect("valid ipv4")) }
fn v6(s: &str) -> IpAddr {
IpAddr::V6(s.parse().expect("valid ipv6")) }
#[test]
fn blocks_rfc1918_10() {
assert!(is_ssrf_blocked_ip(&v4("10.0.0.1")));
assert!(is_ssrf_blocked_ip(&v4("10.255.255.255")));
}
#[test]
fn blocks_rfc1918_172_16() {
assert!(is_ssrf_blocked_ip(&v4("172.16.1.10")));
assert!(is_ssrf_blocked_ip(&v4("172.31.255.254")));
}
#[test]
fn blocks_rfc1918_192_168() {
assert!(is_ssrf_blocked_ip(&v4("192.168.1.1")));
assert!(is_ssrf_blocked_ip(&v4("192.168.0.0")));
}
#[test]
fn blocks_loopback_v4() {
assert!(is_ssrf_blocked_ip(&v4("127.0.0.1")));
assert!(is_ssrf_blocked_ip(&v4("127.255.255.254")));
}
#[test]
fn blocks_link_local_v4() {
assert!(is_ssrf_blocked_ip(&v4("169.254.169.254")));
assert!(is_ssrf_blocked_ip(&v4("169.254.1.1")));
}
#[test]
fn blocks_broadcast_v4() {
assert!(is_ssrf_blocked_ip(&v4("255.255.255.255")));
}
#[test]
fn blocks_multicast_v4() {
assert!(is_ssrf_blocked_ip(&v4("224.0.0.1")));
assert!(is_ssrf_blocked_ip(&v4("239.255.255.255")));
}
#[test]
fn blocks_unspecified_v4() {
assert!(is_ssrf_blocked_ip(&v4("0.0.0.0")));
}
#[test]
fn blocks_zero_octet_v4() {
assert!(is_ssrf_blocked_ip(&v4("0.1.2.3")));
assert!(is_ssrf_blocked_ip(&v4("0.255.255.255")));
}
#[test]
fn blocks_cgn_v4() {
assert!(is_ssrf_blocked_ip(&v4("100.64.0.0")));
assert!(is_ssrf_blocked_ip(&v4("100.100.100.100")));
assert!(is_ssrf_blocked_ip(&v4("100.127.255.255")));
assert!(!is_ssrf_blocked_ip(&v4("100.63.255.255")));
assert!(!is_ssrf_blocked_ip(&v4("100.128.0.0")));
}
#[test]
fn blocks_benchmark_v4() {
assert!(is_ssrf_blocked_ip(&v4("198.18.0.0")));
assert!(is_ssrf_blocked_ip(&v4("198.18.255.255")));
assert!(is_ssrf_blocked_ip(&v4("198.19.255.255")));
assert!(!is_ssrf_blocked_ip(&v4("198.17.255.255")));
assert!(!is_ssrf_blocked_ip(&v4("198.20.0.0")));
}
#[test]
fn blocks_reserved_v4() {
assert!(is_ssrf_blocked_ip(&v4("240.0.0.0")));
assert!(is_ssrf_blocked_ip(&v4("241.1.2.3")));
assert!(is_ssrf_blocked_ip(&v4("250.100.200.50")));
assert!(is_ssrf_blocked_ip(&v4("255.255.255.255")));
assert!(is_ssrf_blocked_ip(&v4("239.255.255.255")));
assert!(is_ssrf_blocked_ip(&v4("100.100.100.100")));
}
#[test]
fn allows_public_dns_v4() {
assert!(!is_ssrf_blocked_ip(&v4("8.8.8.8")));
assert!(!is_ssrf_blocked_ip(&v4("1.1.1.1")));
}
#[test]
fn allows_public_edge_v4() {
assert!(!is_ssrf_blocked_ip(&v4("172.15.255.255")));
assert!(!is_ssrf_blocked_ip(&v4("172.32.0.0")));
}
#[test]
fn blocks_loopback_v6() {
assert!(is_ssrf_blocked_ip(&v6("::1")));
}
#[test]
fn blocks_unspecified_v6() {
assert!(is_ssrf_blocked_ip(&v6("::")));
}
#[test]
fn blocks_multicast_v6() {
assert!(is_ssrf_blocked_ip(&v6("ff02::1")));
assert!(is_ssrf_blocked_ip(&v6("ff00::1")));
}
#[test]
fn blocks_ula_fc_v6() {
assert!(is_ssrf_blocked_ip(&v6("fc00::1")));
assert!(is_ssrf_blocked_ip(&v6("fc00:1234:abcd::1")));
}
#[test]
fn blocks_ula_fd_v6() {
assert!(is_ssrf_blocked_ip(&v6("fd00::1")));
assert!(is_ssrf_blocked_ip(&v6("fd12:3456:789a::1")));
}
#[test]
fn blocks_link_local_v6() {
assert!(is_ssrf_blocked_ip(&v6("fe80::1")));
assert!(is_ssrf_blocked_ip(&v6("febf:ffff::1")));
}
#[test]
fn blocks_site_local_v6() {
assert!(is_ssrf_blocked_ip(&v6("fec0::1")));
assert!(is_ssrf_blocked_ip(&v6("feff:ffff::1")));
assert!(is_ssrf_blocked_ip(&v6("febf::1")));
assert!(is_ssrf_blocked_ip(&v6("ff00::1")));
}
#[test]
fn allows_public_dns_v6() {
assert!(!is_ssrf_blocked_ip(&v6("2001:4860:4860::8888")));
}
#[test]
fn allows_public_documentation_v6() {
assert!(!is_ssrf_blocked_ip(&v6("2001:db8::1")));
}
#[test]
fn ssrf_policy_default_is_public_https_only() {
assert_eq!(SsrfPolicy::default(), SsrfPolicy::PublicHttpsOnly);
}
#[test]
fn ssrf_policy_allows_internal() {
assert!(!SsrfPolicy::PublicHttpsOnly.allows_internal());
assert!(SsrfPolicy::AllowInternal.allows_internal());
}
}