1use std::net::IpAddr;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26#[non_exhaustive]
27pub enum SsrfPolicy {
28 #[default]
29 PublicHttpsOnly,
30 AllowInternal,
31}
32
33impl SsrfPolicy {
34 pub fn allows_internal(self) -> bool {
36 matches!(self, Self::AllowInternal)
37 }
38}
39
40pub fn is_ssrf_blocked_ip(ip: &IpAddr) -> bool {
61 match ip {
62 IpAddr::V4(v4) => {
63 v4.is_private()
64 || v4.is_loopback()
65 || v4.is_link_local()
66 || v4.is_broadcast()
67 || v4.is_multicast()
68 || v4.is_unspecified()
69 || v4.octets()[0] == 0
70 || (v4.octets()[0] == 100
74 && (v4.octets()[1] >= 64 && v4.octets()[1] <= 127))
75 || (v4.octets()[0] == 198
80 && (v4.octets()[1] == 18 || v4.octets()[1] == 19))
81 || v4.octets()[0] >= 240
86 }
87 IpAddr::V6(v6) => {
88 v6.is_loopback()
89 || v6.is_multicast()
90 || v6.is_unspecified()
91 || (v6.segments()[0] & 0xfe00) == 0xfc00
93 || (v6.segments()[0] & 0xffc0) == 0xfe80
95 || (v6.segments()[0] & 0xffc0) == 0xfec0
98 || v6
101 .to_ipv4_mapped()
102 .map(|v4| {
103 v4.is_private()
104 || v4.is_loopback()
105 || v4.is_link_local()
106 || v4.is_broadcast()
107 || v4.is_multicast()
108 || v4.is_unspecified()
109 || v4.octets()[0] == 0
110 || (v4.octets()[0] == 100
111 && (v4.octets()[1] >= 64 && v4.octets()[1] <= 127))
112 || (v4.octets()[0] == 198
113 && (v4.octets()[1] == 18 || v4.octets()[1] == 19))
114 || v4.octets()[0] >= 240
115 })
116 .unwrap_or(false)
117 || nat64_embedded_blocked(v6)
122 || v6.segments()[0] == 0x2002
127 || (v6.segments()[0] == 0x2001 && v6.segments()[1] == 0x0000)
130 }
131 }
132}
133
134fn nat64_embedded_blocked(v6: &std::net::Ipv6Addr) -> bool {
137 let s = v6.segments();
138 if s[..6] != [0x0064, 0xff9b, 0, 0, 0, 0] {
140 return false;
141 }
142 let v4 = std::net::Ipv4Addr::new((s[6] >> 8) as u8, s[6] as u8, (s[7] >> 8) as u8, s[7] as u8);
143 is_ssrf_blocked_ip(&IpAddr::V4(v4))
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use std::net::Ipv4Addr;
150
151 fn v4(s: &str) -> IpAddr {
152 IpAddr::V4(s.parse::<Ipv4Addr>().expect("valid ipv4")) }
154
155 fn v6(s: &str) -> IpAddr {
156 IpAddr::V6(s.parse().expect("valid ipv6")) }
158
159 #[test]
162 fn blocks_rfc1918_10() {
163 assert!(is_ssrf_blocked_ip(&v4("10.0.0.1")));
164 assert!(is_ssrf_blocked_ip(&v4("10.255.255.255")));
165 }
166
167 #[test]
168 fn blocks_rfc1918_172_16() {
169 assert!(is_ssrf_blocked_ip(&v4("172.16.1.10")));
170 assert!(is_ssrf_blocked_ip(&v4("172.31.255.254")));
171 }
172
173 #[test]
174 fn blocks_rfc1918_192_168() {
175 assert!(is_ssrf_blocked_ip(&v4("192.168.1.1")));
176 assert!(is_ssrf_blocked_ip(&v4("192.168.0.0")));
177 }
178
179 #[test]
180 fn blocks_loopback_v4() {
181 assert!(is_ssrf_blocked_ip(&v4("127.0.0.1")));
182 assert!(is_ssrf_blocked_ip(&v4("127.255.255.254")));
183 }
184
185 #[test]
186 fn blocks_link_local_v4() {
187 assert!(is_ssrf_blocked_ip(&v4("169.254.169.254")));
189 assert!(is_ssrf_blocked_ip(&v4("169.254.1.1")));
190 }
191
192 #[test]
193 fn blocks_broadcast_v4() {
194 assert!(is_ssrf_blocked_ip(&v4("255.255.255.255")));
195 }
196
197 #[test]
198 fn blocks_multicast_v4() {
199 assert!(is_ssrf_blocked_ip(&v4("224.0.0.1")));
200 assert!(is_ssrf_blocked_ip(&v4("239.255.255.255")));
201 }
202
203 #[test]
204 fn blocks_unspecified_v4() {
205 assert!(is_ssrf_blocked_ip(&v4("0.0.0.0")));
206 }
207
208 #[test]
209 fn blocks_zero_octet_v4() {
210 assert!(is_ssrf_blocked_ip(&v4("0.1.2.3")));
212 assert!(is_ssrf_blocked_ip(&v4("0.255.255.255")));
213 }
214
215 #[test]
216 fn blocks_cgn_v4() {
217 assert!(is_ssrf_blocked_ip(&v4("100.64.0.0")));
219 assert!(is_ssrf_blocked_ip(&v4("100.100.100.100")));
220 assert!(is_ssrf_blocked_ip(&v4("100.127.255.255")));
221 assert!(!is_ssrf_blocked_ip(&v4("100.63.255.255")));
223 assert!(!is_ssrf_blocked_ip(&v4("100.128.0.0")));
224 }
225
226 #[test]
227 fn blocks_benchmark_v4() {
228 assert!(is_ssrf_blocked_ip(&v4("198.18.0.0")));
230 assert!(is_ssrf_blocked_ip(&v4("198.18.255.255")));
231 assert!(is_ssrf_blocked_ip(&v4("198.19.255.255")));
232 assert!(!is_ssrf_blocked_ip(&v4("198.17.255.255")));
234 assert!(!is_ssrf_blocked_ip(&v4("198.20.0.0")));
235 }
236
237 #[test]
238 fn blocks_reserved_v4() {
239 assert!(is_ssrf_blocked_ip(&v4("240.0.0.0")));
241 assert!(is_ssrf_blocked_ip(&v4("241.1.2.3")));
242 assert!(is_ssrf_blocked_ip(&v4("250.100.200.50")));
243 assert!(is_ssrf_blocked_ip(&v4("255.255.255.255")));
245 assert!(is_ssrf_blocked_ip(&v4("239.255.255.255")));
248 assert!(is_ssrf_blocked_ip(&v4("100.100.100.100")));
250 }
251
252 #[test]
255 fn allows_public_dns_v4() {
256 assert!(!is_ssrf_blocked_ip(&v4("8.8.8.8")));
257 assert!(!is_ssrf_blocked_ip(&v4("1.1.1.1")));
258 }
259
260 #[test]
261 fn allows_public_edge_v4() {
262 assert!(!is_ssrf_blocked_ip(&v4("172.15.255.255")));
264 assert!(!is_ssrf_blocked_ip(&v4("172.32.0.0")));
265 }
266
267 #[test]
270 fn blocks_loopback_v6() {
271 assert!(is_ssrf_blocked_ip(&v6("::1")));
272 }
273
274 #[test]
275 fn blocks_unspecified_v6() {
276 assert!(is_ssrf_blocked_ip(&v6("::")));
277 }
278
279 #[test]
280 fn blocks_multicast_v6() {
281 assert!(is_ssrf_blocked_ip(&v6("ff02::1")));
282 assert!(is_ssrf_blocked_ip(&v6("ff00::1")));
283 }
284
285 #[test]
286 fn blocks_ula_fc_v6() {
287 assert!(is_ssrf_blocked_ip(&v6("fc00::1")));
288 assert!(is_ssrf_blocked_ip(&v6("fc00:1234:abcd::1")));
289 }
290
291 #[test]
292 fn blocks_ula_fd_v6() {
293 assert!(is_ssrf_blocked_ip(&v6("fd00::1")));
294 assert!(is_ssrf_blocked_ip(&v6("fd12:3456:789a::1")));
295 }
296
297 #[test]
298 fn blocks_link_local_v6() {
299 assert!(is_ssrf_blocked_ip(&v6("fe80::1")));
300 assert!(is_ssrf_blocked_ip(&v6("febf:ffff::1")));
302 }
304
305 #[test]
306 fn blocks_site_local_v6() {
307 assert!(is_ssrf_blocked_ip(&v6("fec0::1")));
310 assert!(is_ssrf_blocked_ip(&v6("feff:ffff::1")));
311 assert!(is_ssrf_blocked_ip(&v6("febf::1")));
313 assert!(is_ssrf_blocked_ip(&v6("ff00::1")));
315 }
316
317 #[test]
320 fn allows_public_dns_v6() {
321 assert!(!is_ssrf_blocked_ip(&v6("2001:4860:4860::8888")));
322 }
323
324 #[test]
325 fn allows_public_documentation_v6() {
326 assert!(!is_ssrf_blocked_ip(&v6("2001:db8::1")));
327 }
328
329 #[test]
332 fn blocks_nat64_embedding_private_ipv4() {
333 assert!(is_ssrf_blocked_ip(&v6("64:ff9b::a00:1")));
335 assert!(is_ssrf_blocked_ip(&v6("64:ff9b::7f00:1")));
337 }
338
339 #[test]
340 fn allows_nat64_embedding_public_ipv4() {
341 assert!(!is_ssrf_blocked_ip(&v6("64:ff9b::808:808")));
343 }
344
345 #[test]
346 fn blocks_6to4_and_teredo() {
347 assert!(is_ssrf_blocked_ip(&v6("2002:0a00:0001::1"))); assert!(is_ssrf_blocked_ip(&v6("2002:0808:0808::1"))); assert!(is_ssrf_blocked_ip(&v6(
350 "2001:0000:4136:e378:8000:63bf:3fff:fdd2"
351 ))); }
353
354 #[test]
357 fn v4_mapped_private_loopback_linklocal_blocked() {
358 assert!(is_ssrf_blocked_ip(&v6("::ffff:10.0.0.1")));
359 assert!(is_ssrf_blocked_ip(&v6("::ffff:127.0.0.1")));
360 assert!(is_ssrf_blocked_ip(&v6("::ffff:169.254.1.1")));
361 }
362
363 #[test]
364 fn v4_mapped_multicast_zero_octet_blocked() {
365 assert!(is_ssrf_blocked_ip(&v6("::ffff:224.0.0.1")));
366 assert!(is_ssrf_blocked_ip(&v6("::ffff:0.1.2.3")));
367 }
368
369 #[test]
370 fn v4_mapped_cgnat_corners() {
371 assert!(!is_ssrf_blocked_ip(&v6("::ffff:100.63.0.1")));
373 assert!(is_ssrf_blocked_ip(&v6("::ffff:100.64.0.1")));
374 assert!(is_ssrf_blocked_ip(&v6("::ffff:100.127.0.1")));
375 assert!(!is_ssrf_blocked_ip(&v6("::ffff:100.128.0.1")));
376 }
377
378 #[test]
379 fn v4_mapped_benchmark_pair_blocked() {
380 assert!(is_ssrf_blocked_ip(&v6("::ffff:198.18.0.1")));
381 assert!(is_ssrf_blocked_ip(&v6("::ffff:198.19.255.254")));
382 }
383
384 #[test]
385 fn v4_mapped_reserved_blocked() {
386 assert!(is_ssrf_blocked_ip(&v6("::ffff:240.0.0.1")));
387 }
388
389 #[test]
390 fn v4_mapped_public_stay_unblocked() {
391 assert!(!is_ssrf_blocked_ip(&v6("::ffff:8.8.8.8")));
392 assert!(!is_ssrf_blocked_ip(&v6("::ffff:8.64.0.1")));
393 assert!(!is_ssrf_blocked_ip(&v6("::ffff:199.18.0.1")));
395 assert!(!is_ssrf_blocked_ip(&v6("::ffff:223.255.255.255")));
397 }
398
399 #[test]
402 fn ssrf_policy_default_is_public_https_only() {
403 assert_eq!(SsrfPolicy::default(), SsrfPolicy::PublicHttpsOnly);
404 }
405
406 #[test]
407 fn ssrf_policy_allows_internal() {
408 assert!(!SsrfPolicy::PublicHttpsOnly.allows_internal());
409 assert!(SsrfPolicy::AllowInternal.allows_internal());
410 }
411}