mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 02:23:44 +00:00
fix(network): normalize SSRF IP parsing and monitor typing
This commit is contained in:
@@ -28,6 +28,7 @@ const PRIVATE_OR_LOOPBACK_IPV6_RANGES = new Set<Ipv6Range>([
|
||||
"linkLocal",
|
||||
"uniqueLocal",
|
||||
]);
|
||||
const RFC2544_BENCHMARK_PREFIX: [ipaddr.IPv4, number] = [ipaddr.IPv4.parse("198.18.0.0"), 15];
|
||||
|
||||
const EMBEDDED_IPV4_SENTINEL_RULES: Array<{
|
||||
matches: (parts: number[]) => boolean;
|
||||
@@ -79,6 +80,32 @@ function stripIpv6Brackets(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
function isNumericIpv4LiteralPart(value: string): boolean {
|
||||
return /^[0-9]+$/.test(value) || /^0x[0-9a-f]+$/i.test(value);
|
||||
}
|
||||
|
||||
function parseIpv6WithEmbeddedIpv4(raw: string): ipaddr.IPv6 | undefined {
|
||||
if (!raw.includes(":") || !raw.includes(".")) {
|
||||
return undefined;
|
||||
}
|
||||
const match = /^(.*:)([^:%]+(?:\.[^:%]+){3})(%[0-9A-Za-z]+)?$/i.exec(raw);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
const [, prefix, embeddedIpv4, zoneSuffix = ""] = match;
|
||||
if (!ipaddr.IPv4.isValidFourPartDecimal(embeddedIpv4)) {
|
||||
return undefined;
|
||||
}
|
||||
const octets = embeddedIpv4.split(".").map((part) => Number.parseInt(part, 10));
|
||||
const high = ((octets[0] << 8) | octets[1]).toString(16);
|
||||
const low = ((octets[2] << 8) | octets[3]).toString(16);
|
||||
const normalizedIpv6 = `${prefix}${high}:${low}${zoneSuffix}`;
|
||||
if (!ipaddr.IPv6.isValid(normalizedIpv6)) {
|
||||
return undefined;
|
||||
}
|
||||
return ipaddr.IPv6.parse(normalizedIpv6);
|
||||
}
|
||||
|
||||
export function isIpv4Address(address: ParsedIpAddress): address is ipaddr.IPv4 {
|
||||
return address.kind() === "ipv4";
|
||||
}
|
||||
@@ -115,7 +142,7 @@ export function parseCanonicalIpAddress(raw: string | undefined): ParsedIpAddres
|
||||
if (ipaddr.IPv6.isValid(normalized)) {
|
||||
return ipaddr.IPv6.parse(normalized);
|
||||
}
|
||||
return undefined;
|
||||
return parseIpv6WithEmbeddedIpv4(normalized);
|
||||
}
|
||||
|
||||
export function parseLooseIpAddress(raw: string | undefined): ParsedIpAddress | undefined {
|
||||
@@ -127,10 +154,10 @@ export function parseLooseIpAddress(raw: string | undefined): ParsedIpAddress |
|
||||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
if (!ipaddr.isValid(normalized)) {
|
||||
return undefined;
|
||||
if (ipaddr.isValid(normalized)) {
|
||||
return ipaddr.parse(normalized);
|
||||
}
|
||||
return ipaddr.parse(normalized);
|
||||
return parseIpv6WithEmbeddedIpv4(normalized);
|
||||
}
|
||||
|
||||
export function normalizeIpAddress(raw: string | undefined): string | undefined {
|
||||
@@ -163,7 +190,20 @@ export function isLegacyIpv4Literal(raw: string | undefined): boolean {
|
||||
if (!normalized || normalized.includes(":")) {
|
||||
return false;
|
||||
}
|
||||
return ipaddr.IPv4.isValid(normalized) && !ipaddr.IPv4.isValidFourPartDecimal(normalized);
|
||||
if (isCanonicalDottedDecimalIPv4(normalized)) {
|
||||
return false;
|
||||
}
|
||||
const parts = normalized.split(".");
|
||||
if (parts.length === 0 || parts.length > 4) {
|
||||
return false;
|
||||
}
|
||||
if (parts.some((part) => part.length === 0)) {
|
||||
return false;
|
||||
}
|
||||
if (!parts.every((part) => isNumericIpv4LiteralPart(part))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isLoopbackIpAddress(raw: string | undefined): boolean {
|
||||
@@ -208,7 +248,9 @@ export function isCarrierGradeNatIpv4Address(raw: string | undefined): boolean {
|
||||
}
|
||||
|
||||
export function isBlockedSpecialUseIpv4Address(address: ipaddr.IPv4): boolean {
|
||||
return BLOCKED_IPV4_SPECIAL_USE_RANGES.has(address.range());
|
||||
return (
|
||||
BLOCKED_IPV4_SPECIAL_USE_RANGES.has(address.range()) || address.match(RFC2544_BENCHMARK_PREFIX)
|
||||
);
|
||||
}
|
||||
|
||||
function decodeIpv4FromHextets(high: number, low: number): ipaddr.IPv4 {
|
||||
@@ -276,7 +318,13 @@ export function isIpInCidr(ip: string, cidr: string): boolean {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return comparableIp.match([comparableBase, prefixLength]);
|
||||
if (isIpv4Address(comparableIp) && isIpv4Address(comparableBase)) {
|
||||
return comparableIp.match([comparableBase, prefixLength]);
|
||||
}
|
||||
if (isIpv6Address(comparableIp) && isIpv6Address(comparableBase)) {
|
||||
return comparableIp.match([comparableBase, prefixLength]);
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user