mirror of
https://github.com/freemkv/freemkv-keysources.git
synced 2026-10-07 18:13:11 +00:00
block 6to4/Teredo tunnels in the SSRF guard
The IPv6 SSRF guard already re-checked IPv4-mapped/-compatible addresses, but 6to4 (2002::/16) and Teredo (2001:0000::/32) also tunnel an embedded IPv4 and were passed through untouched, letting an attacker wrap 127.0.0.1 or a cloud metadata address in either encapsulation to bypass the block list. Decode both forms and re-check the embedded IPv4 through is_blocked_ip. Add a test covering 6to4/Teredo-wrapped loopback and metadata addresses, plus a 6to4-wrapped public IP that must still be allowed.
This commit is contained in:
+39
-2
@@ -56,13 +56,24 @@ fn is_blocked_ip(ip: &IpAddr) -> bool {
|
||||
|| v4.octets()[0] >= 240
|
||||
}
|
||||
IpAddr::V6(v6) => {
|
||||
let seg = v6.segments();
|
||||
// 6to4 (2002::/16) embeds an IPv4 in segments[1..3]; Teredo
|
||||
// (2001:0000::/32) embeds the client IPv4 in the last two segments,
|
||||
// each XOR 0xffff. Both must be re-checked as their embedded IPv4.
|
||||
let sixtofour = (seg[0] == 0x2002)
|
||||
.then(|| std::net::Ipv4Addr::from(((seg[1] as u32) << 16) | (seg[2] as u32)));
|
||||
let teredo = (seg[0] == 0x2001 && seg[1] == 0x0000).then(|| {
|
||||
std::net::Ipv4Addr::from(
|
||||
(((seg[6] ^ 0xffff) as u32) << 16) | ((seg[7] ^ 0xffff) as u32),
|
||||
)
|
||||
});
|
||||
v6.is_loopback()
|
||||
|| v6.is_unspecified()
|
||||
|| v6.is_multicast()
|
||||
// Unique-local fc00::/7.
|
||||
|| (v6.segments()[0] & 0xfe00) == 0xfc00
|
||||
|| (seg[0] & 0xfe00) == 0xfc00
|
||||
// Link-local fe80::/10.
|
||||
|| (v6.segments()[0] & 0xffc0) == 0xfe80
|
||||
|| (seg[0] & 0xffc0) == 0xfe80
|
||||
// IPv4-mapped (::ffff:x.x.x.x) and IPv4-compatible (::x.x.x.x,
|
||||
// deprecated by RFC 4291 §2.5.5.1) — to_ipv4() returns Some for
|
||||
// both forms; re-check the embedded address as IPv4.
|
||||
@@ -70,6 +81,8 @@ fn is_blocked_ip(ip: &IpAddr) -> bool {
|
||||
.to_ipv4()
|
||||
.map(|m| is_blocked_ip(&IpAddr::V4(m)))
|
||||
== Some(true)
|
||||
|| sixtofour.is_some_and(|v4| is_blocked_ip(&IpAddr::V4(v4)))
|
||||
|| teredo.is_some_and(|v4| is_blocked_ip(&IpAddr::V4(v4)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,6 +661,30 @@ mod tests {
|
||||
))));
|
||||
}
|
||||
|
||||
// 6to4 (2002::/16) and Teredo (2001:0000::/32) tunnel an IPv4 inside an
|
||||
// IPv6 address; the guard must decode and re-check that embedded IPv4 or an
|
||||
// internal target slips through the tunnel.
|
||||
#[test]
|
||||
fn ssrf_guard_blocks_embedded_ipv4_via_6to4_and_teredo() {
|
||||
// 6to4 for 127.0.0.1: 2002:7f00:0001:: (embedded in segments[1..3]).
|
||||
assert!(is_blocked_ip(&IpAddr::V6(Ipv6Addr::new(
|
||||
0x2002, 0x7f00, 0x0001, 0, 0, 0, 0, 0
|
||||
))));
|
||||
// 6to4 for 169.254.169.254 (cloud metadata): 2002:a9fe:a9fe::.
|
||||
assert!(is_blocked_ip(&IpAddr::V6(Ipv6Addr::new(
|
||||
0x2002, 0xa9fe, 0xa9fe, 0, 0, 0, 0, 0
|
||||
))));
|
||||
// Teredo for 127.0.0.1: client IPv4 lives in the last two segments XOR
|
||||
// 0xffff, so 0x7f00^0xffff=0x80ff and 0x0001^0xffff=0xfffe.
|
||||
assert!(is_blocked_ip(&IpAddr::V6(Ipv6Addr::new(
|
||||
0x2001, 0x0000, 0, 0, 0, 0, 0x80ff, 0xfffe
|
||||
))));
|
||||
// A 6to4 wrapping a PUBLIC IPv4 (8.8.8.8 → 2002:0808:0808::) is allowed.
|
||||
assert!(!is_blocked_ip(&IpAddr::V6(Ipv6Addr::new(
|
||||
0x2002, 0x0808, 0x0808, 0, 0, 0, 0, 0
|
||||
))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ssrf_guard_allows_public_ips() {
|
||||
assert!(!is_blocked_ip(&IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8))));
|
||||
|
||||
Reference in New Issue
Block a user