|
2 | 2 |
|
3 | 3 | import * as ipaddr from "ipaddr.js"; |
4 | 4 | import CIDR from "ip-cidr"; |
| 5 | +import * as net from "node:net"; // Added for net.isIPv6 check |
5 | 6 | const got = require("got").default; |
6 | 7 | const DEBUG = false; |
7 | 8 | const DSSRF_MAKE_REQUEST = process.env.DSSRF_MAKE_REQUEST; |
@@ -232,11 +233,32 @@ export function is_ip_internal(ip: string): boolean { |
232 | 233 |
|
233 | 234 | if (parsed.kind() === "ipv6") { |
234 | 235 | const range = parsed.range(); |
235 | | - return ( |
236 | | - range === "loopback" || |
237 | | - range === "linkLocal" || |
238 | | - range === "uniqueLocal" |
239 | | - ); |
| 236 | + if (range !== "unicast") { |
| 237 | + // Check for IPv4-mapped or IPv4-compatible addresses |
| 238 | + if (range === "ipv4Mapped" || range === "rfc6145") { |
| 239 | + try { |
| 240 | + // @ts-ignore |
| 241 | + const ipv4 = parsed.toIPv4Address(); |
| 242 | + return is_ip_internal(ipv4.toString()); |
| 243 | + } catch { |
| 244 | + return true; |
| 245 | + } |
| 246 | + } |
| 247 | + return true; |
| 248 | + } |
| 249 | + |
| 250 | + // Additional manual blocks for IPv6 |
| 251 | + const extraBadRanges = [ |
| 252 | + ipaddr.parseCIDR("64:ff9b:1::/48"), |
| 253 | + ipaddr.parseCIDR("5f00::/8"), |
| 254 | + ipaddr.parseCIDR("3fff::/20"), |
| 255 | + ipaddr.parseCIDR("fec0::/10"), |
| 256 | + ]; |
| 257 | + |
| 258 | + for (const [range, bits] of extraBadRanges) { |
| 259 | + // @ts-ignore |
| 260 | + if (parsed.match(range, bits)) return true; |
| 261 | + } |
240 | 262 | } |
241 | 263 |
|
242 | 264 | return false; |
@@ -491,7 +513,7 @@ export async function is_url_safe(url: string): Promise<boolean> { |
491 | 513 | if (!is_proto_safe(schema)) return false; |
492 | 514 |
|
493 | 515 | const parsed = new URL(u); |
494 | | - const hostname = parsed.hostname; |
| 516 | + const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); |
495 | 517 |
|
496 | 518 | // IPv4 validation |
497 | 519 | if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) { |
@@ -521,6 +543,6 @@ export async function is_url_safe(url: string): Promise<boolean> { |
521 | 543 |
|
522 | 544 |
|
523 | 545 | /// FIXME(): The debug version do not match is_url_safe, We'wll fix it later but for now keep it as is. |
524 | | -export async function is_url_safe_debug(url: string): Promise<boolean> { try { console.log("STEP 1 input:", url); let u = normalize_unicode(url); console.log("STEP 2 unicode:", u); u = replace_backslash_with_slash_in_string(u); console.log("STEP 3 slashes:", u); u = replace_two_slashes_url_to_normal_url(u); console.log("STEP 4 normalize slashes:", u); u = remove_at_symbol_in_string(u); console.log("STEP 5 remove @:", u); const schema = normalize_schema(u); if (!is_proto_safe(schema)) return false; console.log("STEP 6 schema:", schema); if (!is_proto_safe(u)) { console.log("STEP 7 proto unsafe"); return false; } console.log("STEP 7 proto safe"); const parsed = new URL(u); const hostname = parsed.hostname; console.log("STEP 8 hostname:", hostname); if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) { try { normalize_ipv4(hostname); } catch { console.log("STEP 9 ipv4 invalid"); return false; } } if (is_ipv6(hostname)) { if (is_ip_internal(hostname)) { console.log("STEP 10 ipv6 internal"); return false; } } const isInternal = await is_hostname_resolve_to_internal_ip(hostname); console.log("STEP 11 internal?", isInternal); if (isInternal) { return false; } const redirectSafe = await is_redirect_safe(u); console.log("STEP 12 redirect safe?", redirectSafe); if (!redirectSafe) { return false; } console.log("STEP 13 final: true"); return true; } catch (e) { console.log("ERROR:", e); return false; } } |
| 546 | +export async function is_url_safe_debug(url: string): Promise<boolean> { try { console.log("STEP 1 input:", url); let u = normalize_unicode(url); console.log("STEP 2 unicode:", u); u = replace_backslash_with_slash_in_string(u); console.log("STEP 3 slashes:", u); u = replace_two_slashes_url_to_normal_url(u); console.log("STEP 4 normalize slashes:", u); u = remove_at_symbol_in_string(u); console.log("STEP 5 remove @:", u); const schema = normalize_schema(u); if (!is_proto_safe(schema)) return false; console.log("STEP 6 schema:", schema); if (!is_proto_safe(u)) { console.log("STEP 7 proto unsafe"); return false; } console.log("STEP 7 proto safe"); const parsed = new URL(u); const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); console.log("STEP 8 hostname:", hostname); if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) { try { normalize_ipv4(hostname); } catch { console.log("STEP 9 ipv4 invalid"); return false; } } if (is_ipv6(hostname)) { if (is_ip_internal(hostname)) { console.log("STEP 10 ipv6 internal"); return false; } } const isInternal = await is_hostname_resolve_to_internal_ip(hostname); console.log("STEP 11 internal?", isInternal); if (isInternal) { return false; } const redirectSafe = await is_redirect_safe(u); console.log("STEP 12 redirect safe?", redirectSafe); if (!redirectSafe) { return false; } console.log("STEP 13 final: true"); return true; } catch (e) { console.log("ERROR:", e); return false; } } |
525 | 547 |
|
526 | 548 |
|
0 commit comments