diff --git a/dist/helpers.js b/dist/helpers.js index f1b0fcc..61f9055 100644 --- a/dist/helpers.js +++ b/dist/helpers.js @@ -383,16 +383,23 @@ function is_proto_safe(url) { return true; return false; } +async function is_parsed_url_safe(parsed) { + if (!is_proto_safe(parsed.protocol)) + return false; + if (parsed.username !== "" || parsed.password !== "") + return false; + // WHATWG URL includes brackets in .hostname for IPv6 (e.g. "[::1]"); strip them + const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); + if (await is_hostname_resolve_to_internal_ip(hostname)) + return false; + return true; +} async function is_redirect_safe(url) { try { - let normalized = replace_backslash_with_slash_in_string(url); - normalized = remove_at_symbol_in_string(normalized); - let current = new URL(normalized); + let current = new URL(replace_backslash_with_slash_in_string(url)); const MAX_REDIRECTS = 5; for (let i = 0; i < MAX_REDIRECTS; i++) { - if (!is_proto_safe(current.protocol)) - return false; - if (await is_hostname_resolve_to_internal_ip(current.hostname)) + if (!await is_parsed_url_safe(current)) return false; const res = await got(current.toString(), { method: "HEAD", @@ -401,15 +408,9 @@ async function is_redirect_safe(url) { timeout: { request: 3000 } }); const loc = res.headers.location; - if (!loc) { + if (!loc) return true; - } - try { - current = new URL(loc, current.toString()); - } - catch { - return false; - } + current = new URL(loc, current.toString()); } return false; } @@ -427,30 +428,11 @@ async function is_url_safe(url) { let u = normalize_unicode(url); u = replace_backslash_with_slash_in_string(u); u = replace_two_slashes_url_to_normal_url(u); - u = remove_at_symbol_in_string(u); - const schema = normalize_schema(u); - if (!is_proto_safe(schema)) - return false; const parsed = new URL(u); - const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); - // IPv4 validation - if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) { - try { - normalize_ipv4(hostname); - } - catch { - return false; - } - } - if (is_ipv6(hostname)) { - if (is_ip_internal(hostname)) - return false; - } - if (await is_hostname_resolve_to_internal_ip(hostname)) + if (!await is_parsed_url_safe(parsed)) return false; if (process.env.DSSRF_CHECK_REDIRECTS === "1") { - const redirectSafe = await is_redirect_safe(u); - if (!redirectSafe) + if (!await is_redirect_safe(u)) return false; } return true; diff --git a/dist/utils.js b/dist/utils.js index 19b4f04..bdf12e5 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -1,4 +1,5 @@ "use strict"; +//! A module contain all utilities for SSRF mitigation Object.defineProperty(exports, "__esModule", { value: true }); exports.is_url_safe_debug = exports.is_url_safe = exports.replace_two_slashes_url_to_normal_url = exports.replace_backslash_with_slash_in_string = exports.remove_at_symbol_in_string = exports.octal_ip_to_normal_ip = exports.normalize_unicode = exports.normalize_schema = exports.normalize_ipv4 = exports.is_redirect_safe = exports.is_range_not_internal = exports.is_proto_safe = exports.is_hostname_resolve_to_internal_ip = exports.hex_ip_to_normal_ip = exports.decimal_ip_to_normal_ip = exports.bin_ip_to_normal_ip = exports.is_ipv6 = void 0; var helpers_1 = require("./helpers"); diff --git a/src/helpers.ts b/src/helpers.ts index 1e4d1e8..c3fd044 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -448,17 +448,22 @@ export function is_proto_safe(url: string): boolean { +async function is_parsed_url_safe(parsed: URL): Promise { + if (!is_proto_safe(parsed.protocol)) return false; + if (parsed.username !== "" || parsed.password !== "") return false; + // WHATWG URL includes brackets in .hostname for IPv6 (e.g. "[::1]"); strip them + const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); + if (await is_hostname_resolve_to_internal_ip(hostname)) return false; + return true; +} + export async function is_redirect_safe(url: string): Promise { try { - let normalized = replace_backslash_with_slash_in_string(url); - normalized = remove_at_symbol_in_string(normalized); - - let current = new URL(normalized); + let current = new URL(replace_backslash_with_slash_in_string(url)); const MAX_REDIRECTS = 5; for (let i = 0; i < MAX_REDIRECTS; i++) { - if (!is_proto_safe(current.protocol)) return false; - if (await is_hostname_resolve_to_internal_ip(current.hostname)) return false; + if (!await is_parsed_url_safe(current)) return false; const res = await got(current.toString(), { method: "HEAD", @@ -468,15 +473,9 @@ export async function is_redirect_safe(url: string): Promise { }); const loc = res.headers.location; - if (!loc) { - return true; - } + if (!loc) return true; - try { - current = new URL(loc, current.toString()); - } catch { - return false; - } + current = new URL(loc, current.toString()); } return false; @@ -504,35 +503,15 @@ export function normalize_unicode(input: string): string { export async function is_url_safe(url: string): Promise { try { let u = normalize_unicode(url); - u = replace_backslash_with_slash_in_string(u); u = replace_two_slashes_url_to_normal_url(u); - u = remove_at_symbol_in_string(u); - - const schema = normalize_schema(u); - if (!is_proto_safe(schema)) return false; const parsed = new URL(u); - const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); - - // IPv4 validation - if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) { - try { - normalize_ipv4(hostname); - } catch { - return false; - } - } - - if (is_ipv6(hostname)) { - if (is_ip_internal(hostname)) return false; - } - if (await is_hostname_resolve_to_internal_ip(hostname)) return false; + if (!await is_parsed_url_safe(parsed)) return false; if (process.env.DSSRF_CHECK_REDIRECTS === "1") { - const redirectSafe = await is_redirect_safe(u); - if (!redirectSafe) return false; + if (!await is_redirect_safe(u)) return false; } return true; diff --git a/tsconfig.json b/tsconfig.json index 9251321..81efffd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,10 @@ "strict": true, "declaration": true, "outDir": "dist", - "esModuleInterop": true + "rootDir": "src", + "esModuleInterop": true, + "ignoreDeprecations": "6.0", + "types": ["node"] }, "include": ["src"] }