From 403cef4a00a31853da0334fafe3a452905df8779 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Fri, 17 Apr 2026 15:46:46 -0400 Subject: [PATCH] Improve GHSA-2x8m-83vc-6wv4 --- .../2026/04/GHSA-2x8m-83vc-6wv4/GHSA-2x8m-83vc-6wv4.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/advisories/github-reviewed/2026/04/GHSA-2x8m-83vc-6wv4/GHSA-2x8m-83vc-6wv4.json b/advisories/github-reviewed/2026/04/GHSA-2x8m-83vc-6wv4/GHSA-2x8m-83vc-6wv4.json index 2c053853a0076..40cf432c460ab 100644 --- a/advisories/github-reviewed/2026/04/GHSA-2x8m-83vc-6wv4/GHSA-2x8m-83vc-6wv4.json +++ b/advisories/github-reviewed/2026/04/GHSA-2x8m-83vc-6wv4/GHSA-2x8m-83vc-6wv4.json @@ -1,11 +1,11 @@ { "schema_version": "1.4.0", "id": "GHSA-2x8m-83vc-6wv4", - "modified": "2026-04-16T21:51:00Z", + "modified": "2026-04-16T21:51:02Z", "published": "2026-04-16T21:51:00Z", "aliases": [], "summary": "Flowise: SSRF Protection Bypass (TOCTOU & Default Insecure)", - "details": "### Summary\nThe core security wrappers (secureAxiosRequest and secureFetch) intended to prevent Server-Side Request Forgery (SSRF) contain multiple logic flaws. These flaws allow attackers to bypass the allow/deny lists via DNS Rebinding (Time-of-Check Time-of-Use) or by exploiting the default configuration which fails to enforce any deny list.\n\n\n### Details\nThe flaws exist in packages/components/src/httpSecurity.ts.\n\nDefault Insecure: If process.env.HTTP_DENY_LIST is undefined, checkDenyList returns immediately, allowing all requests (including localhost).\n\nDNS Rebinding (TOCTOU): The function performs a DNS lookup (dns.lookup) to validate the IP, and then the HTTP client performs a new lookup to connect. An attacker can serve a valid IP first, then switch to an internal IP (e.g., 127.0.0.1) for the second lookup.\n\n\n### PoC\nnsure HTTP_DENY_LIST is unset (default behavior).\n\nUse any node utilizing secureFetch to access http://127.0.0.1.\n\nResult: Request succeeds.\n\nScenario 2: DNS Rebinding\n\nAttacker controls domain attacker.com and a custom DNS server.\n\nConfigure DNS to return 1.1.1.1 (Safe IP) with TTL=0 for the first query.\n\nConfigure DNS to return 127.0.0.1 (Blocked IP) for subsequent queries.\n\nFlowise validates attacker.com -> 1.1.1.1 (Allowed).\n\nFlowise fetches attacker.com -> 127.0.0.1 (Bypass).\n\nRun the following for manual verification \n\n\"// PoC for httpSecurity.ts Bypasses\nimport * as dns from 'dns/promises';\n\n// Mocking the checkDenyList logic from Flowise\nasync function checkDenyList(url: string) {\n const deniedIPs = ['127.0.0.1', '0.0.0.0']; // Simplified deny list logic\n\n if (!process.env.HTTP_DENY_LIST) {\n console.log(\"⚠️ HTTP_DENY_LIST not set. Returning allowed.\");\n return; // Vulnerability 1: Default Insecure\n }\n\n const { hostname } = new URL(url);\n const { address } = await dns.lookup(hostname);\n\n if (deniedIPs.includes(address)) {\n throw new Error(`IP ${address} is denied`);\n }\n console.log(`✅ IP ${address} allowed check.`);\n}\n\nasync function runPoC() {\n console.log(\"--- Test 1: Default Configuration (Unset HTTP_DENY_LIST) ---\");\n // Ensure env var is unset\n delete process.env.HTTP_DENY_LIST;\n try {\n await checkDenyList('http://127.0.0.1');\n console.log(\"[PASS] Default config allowed localhost access.\");\n } catch (e) {\n console.log(\"[FAIL] Blocked:\", e.message);\n }\n\n console.log(\"\\n--- Test 2: 'private' Keyword Bypass (Logic Flaw) ---\");\n process.env.HTTP_DENY_LIST = 'private'; // User expects this to block localhost\n try {\n await checkDenyList('http://127.0.0.1');\n // In real Flowise code, 'private' is not expanded to IPs, so it only blocks the string \"private\"\n console.log(\"[PASS] 'private' keyword failed to block localhost (Mock simulation).\");\n } catch (e) {\n console.log(\"[FAIL] Blocked:\", e.message);\n }\n}\n\nrunPoC();\"\n\n\n### Impact\nConfidentiality: High (Access to internal services if protection is bypassed).\n\nIntegrity: Low/Medium (If internal services allow state changes via GET).\n\nAvailability: Low.", + "details": "### Summary\nThe core security wrappers (secureAxiosRequest and secureFetch) intended to prevent Server-Side Request Forgery (SSRF) contain multiple logic flaws. These flaws allow attackers to bypass the allow/deny lists via DNS Rebinding (Time-of-Check Time-of-Use) or by exploiting the default configuration which fails to enforce any deny list.\n\n\n### Details\nThe flaws exist in `packages/components/src/httpSecurity.ts`.\n\nDefault Insecure: If process.env.HTTP_DENY_LIST is undefined, checkDenyList returns immediately, allowing all requests (including localhost).\n\nDNS Rebinding (TOCTOU): The function performs a DNS lookup (dns.lookup) to validate the IP, and then the HTTP client performs a new lookup to connect. An attacker can serve a valid IP first, then switch to an internal IP (e.g., `127.0.0.1`) for the second lookup.\n\n\n### PoC\nEnsure `HTTP_DENY_LIST` is unset (default behavior).\n\nUse any node utilizing secureFetch to access `http://127.0.0.1`.\n\nResult: Request succeeds.\n\n#### Scenario 2: DNS Rebinding\n\nAttacker controls domain attacker.com and a custom DNS server.\n\nConfigure DNS to return `1.1.1.1` (Safe IP) with TTL=0 for the first query.\n\nConfigure DNS to return `127.0.0.1` (Blocked IP) for subsequent queries.\n\nFlowise validates `attacker.com` -> `1.1.1.1` (Allowed).\n\nFlowise fetches `attacker.com` -> `127.0.0.1` (Bypass).\n\nRun the following for manual verification \n\n```ts\n// PoC for httpSecurity.ts Bypasses\nimport * as dns from 'dns/promises';\n\n// Mocking the checkDenyList logic from Flowise\nasync function checkDenyList(url: string) {\n const deniedIPs = ['127.0.0.1', '0.0.0.0']; // Simplified deny list logic\n\n if (!process.env.HTTP_DENY_LIST) {\n console.log(\"⚠️ HTTP_DENY_LIST not set. Returning allowed.\");\n return; // Vulnerability 1: Default Insecure\n }\n\n const { hostname } = new URL(url);\n const { address } = await dns.lookup(hostname);\n\n if (deniedIPs.includes(address)) {\n throw new Error(`IP ${address} is denied`);\n }\n console.log(`✅ IP ${address} allowed check.`);\n}\n\nasync function runPoC() {\n console.log(\"--- Test 1: Default Configuration (Unset HTTP_DENY_LIST) ---\");\n // Ensure env var is unset\n delete process.env.HTTP_DENY_LIST;\n try {\n await checkDenyList('http://127.0.0.1');\n console.log(\"[PASS] Default config allowed localhost access.\");\n } catch (e) {\n console.log(\"[FAIL] Blocked:\", e.message);\n }\n\n console.log(\"\\n--- Test 2: 'private' Keyword Bypass (Logic Flaw) ---\");\n process.env.HTTP_DENY_LIST = 'private'; // User expects this to block localhost\n try {\n await checkDenyList('http://127.0.0.1');\n // In real Flowise code, 'private' is not expanded to IPs, so it only blocks the string \"private\"\n console.log(\"[PASS] 'private' keyword failed to block localhost (Mock simulation).\");\n } catch (e) {\n console.log(\"[FAIL] Blocked:\", e.message);\n }\n}\n\nrunPoC();\n```\n\n\n### Impact\nConfidentiality: High (Access to internal services if protection is bypassed).\n\nIntegrity: Low/Medium (If internal services allow state changes via GET).\n\nAvailability: Low.", "severity": [ { "type": "CVSS_V3",