fix(ssrf): unify ipv6 special-use blocking

This commit is contained in:
Peter Steinberger
2026-02-26 03:43:30 +01:00
parent 04d91d0319
commit 61b3246a7f
6 changed files with 17 additions and 10 deletions

View File

@@ -0,0 +1 @@
export const blockedIpv6MulticastLiterals = ["ff02::1", "ff05::1:3", "[ff02::1]"] as const;

View File

@@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest";
import { blockedIpv6MulticastLiterals } from "./ip-test-fixtures.js";
import {
extractEmbeddedIpv4FromIpv6,
isCanonicalDottedDecimalIPv4,
@@ -47,8 +48,9 @@ describe("shared ip helpers", () => {
it("treats blocked IPv6 classes as private/internal", () => {
expect(isPrivateOrLoopbackIpAddress("fec0::1")).toBe(true);
expect(isPrivateOrLoopbackIpAddress("ff02::1")).toBe(true);
expect(isPrivateOrLoopbackIpAddress("[ff05::1:3]")).toBe(true);
for (const literal of blockedIpv6MulticastLiterals) {
expect(isPrivateOrLoopbackIpAddress(literal)).toBe(true);
}
expect(isPrivateOrLoopbackIpAddress("2001:4860:4860::8888")).toBe(false);
});
});

View File

@@ -22,7 +22,7 @@ const PRIVATE_OR_LOOPBACK_IPV4_RANGES = new Set<Ipv4Range>([
"carrierGradeNat",
]);
const PRIVATE_OR_LOOPBACK_IPV6_RANGES = new Set<Ipv6Range>([
const BLOCKED_IPV6_SPECIAL_USE_RANGES = new Set<Ipv6Range>([
"unspecified",
"loopback",
"linkLocal",
@@ -228,11 +228,15 @@ export function isPrivateOrLoopbackIpAddress(raw: string | undefined): boolean {
if (isIpv4Address(normalized)) {
return PRIVATE_OR_LOOPBACK_IPV4_RANGES.has(normalized.range());
}
if (PRIVATE_OR_LOOPBACK_IPV6_RANGES.has(normalized.range())) {
return isBlockedSpecialUseIpv6Address(normalized);
}
export function isBlockedSpecialUseIpv6Address(address: ipaddr.IPv6): boolean {
if (BLOCKED_IPV6_SPECIAL_USE_RANGES.has(address.range())) {
return true;
}
// ipaddr.js does not classify deprecated site-local fec0::/10 as private.
return (normalized.parts[0] & 0xffc0) === 0xfec0;
return (address.parts[0] & 0xffc0) === 0xfec0;
}
export function isRfc1918Ipv4Address(raw: string | undefined): boolean {