Path: blob/master/modules/auxiliary/admin/mysql/mysql_enum.rb
19534 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Report7include Msf::Exploit::Remote::MYSQL8include Msf::OptionalSession::MySQL910def initialize(info = {})11super(12update_info(13info,14'Name' => 'MySQL Enumeration Module',15'Description' => %q{16This module allows for simple enumeration of MySQL Database Server17provided proper credentials to connect remotely.18},19'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ],20'License' => MSF_LICENSE,21'References' => [22[ 'URL', 'https://cisecurity.org/benchmarks.html' ]23],24'Notes' => {25'Stability' => [CRASH_SAFE],26'SideEffects' => [IOC_IN_LOGS],27'Reliability' => []28}29)30)31end3233def report_cred(opts)34service_data = {35address: opts[:ip],36port: opts[:port],37service_name: opts[:service_name],38protocol: 'tcp',39workspace_id: myworkspace_id40}4142credential_data = {43origin_type: :service,44module_fullname: fullname,45username: opts[:user],46private_data: opts[:password],47private_type: :nonreplayable_hash,48jtr_format: 'mysql,mysql-sha1'49}.merge(service_data)5051login_data = {52core: create_credential(credential_data),53status: Metasploit::Model::Login::Status::UNTRIED,54proof: opts[:proof]55}.merge(service_data)5657create_credential_login(login_data)58end5960def run61# If we have a session make use of it62if session63print_status("Using existing session #{session.sid}")64self.mysql_conn = session.client65self.sock = session.client.socket66else67# otherwise fallback to attempting to login68return unless mysql_login_datastore69end7071print_status('Running MySQL Enumerator...')72print_status('Enumerating Parameters')73#-------------------------------------------------------74# getting all variables75vparm = {}76res = mysql_query('show variables') || []77res.each do |row|78# print_status(" | #{row.join(" | ")} |")79vparm[row[0]] = row[1]80end8182#-------------------------------------------------------83# MySQL Version84print_status("\tMySQL Version: #{vparm['version']}")85print_status("\tCompiled for the following OS: #{vparm['version_compile_os']}")86print_status("\tArchitecture: #{vparm['version_compile_machine']}")87print_status("\tServer Hostname: #{vparm['hostname']}")88print_status("\tData Directory: #{vparm['datadir']}")8990if vparm['log'] == 'OFF'91print_status("\tLogging of queries and logins: OFF")92else93print_status("\tLogging of queries and logins: ON")94print_status("\tLog Files Location: #{vparm['log_bin']}")95end9697print_status("\tOld Password Hashing Algorithm #{vparm['old_passwords']}")98print_status("\tLoading of local files: #{vparm['local_infile']}")99print_status("\tDeny logins with old Pre-4.1 Passwords: #{vparm['secure_auth']}")100print_status("\tSkipping of GRANT TABLE: #{vparm['skip_grant_tables']}") if vparm['skip_grant_tables']101print_status("\tAllow Use of symlinks for Database Files: #{vparm['have_symlink']}")102print_status("\tAllow Table Merge: #{vparm['have_merge_engine']}")103print_status("\tRestrict DB Enumeration by Privilege: #{vparm['safe_show_database']}") if vparm['safe_show_database']104105if vparm['have_openssl'] == 'YES'106print_status("\tSSL Connections: Enabled")107print_status("\tSSL CA Certificate: #{vparm['ssl_ca']}")108print_status("\tSSL Key: #{vparm['ssl_key']}")109print_status("\tSSL Certificate: #{vparm['ssl_cert']}")110else111print_status("\tSSL Connection: #{vparm['have_openssl']}")112end113114#-------------------------------------------------------115# Database selection116query = 'use mysql'117mysql_query(query)118119# Starting from MySQL 5.7, the 'password' column was changed to 'authentication_string'.120if vparm['version'][0..2].to_f > 5.6121password_field = 'authentication_string'122else123password_field = 'password'124end125126# rubocop:disable Style/ZeroLengthPredicate127# MySQL query response does not support `.empty?`128129# Account Enumeration130# Enumerate all accounts with their password hashes131print_status('Enumerating Accounts:')132query = "select user, host, #{password_field} from mysql.user"133res = mysql_query(query)134if res && (res.size > 0)135print_status("\tList of Accounts with Password Hashes:")136res.each do |row|137print_good("\t\tUser: #{row[0]} Host: #{row[1]} Password Hash: #{row[2]}")138report_cred(139ip: mysql_conn.peerhost,140port: mysql_conn.peerport,141user: row[0],142password: row[2],143service_name: 'mysql',144proof: row.inspect145)146end147end148149# Only list accounts that can log in with SSL if SSL is enabled150if vparm['have_openssl'] == 'YES'151query = %|select user, host, ssl_type from mysql.user where152(ssl_type = 'ANY') or153(ssl_type = 'X509') or154(ssl_type = 'SPECIFIED')|155res = mysql_query(query)156if res && res.size > 0157print_status("\tThe following users can login using SSL:")158res.each do |row|159print_status("\t\tUser: #{row[0]} Host: #{row[1]} SSLType: #{row[2]}")160end161end162end163query = "select user, host from mysql.user where Grant_priv = 'Y'"164res = mysql_query(query)165if res && (res.size > 0)166print_status("\tThe following users have GRANT Privilege:")167res.each do |row|168print_status("\t\tUser: #{row[0]} Host: #{row[1]}")169end170end171172query = "select user, host from mysql.user where Create_user_priv = 'Y'"173res = mysql_query(query)174if res && (res.size > 0)175print_status("\tThe following users have CREATE USER Privilege:")176res.each do |row|177print_status("\t\tUser: #{row[0]} Host: #{row[1]}")178end179end180query = "select user, host from mysql.user where Reload_priv = 'Y'"181res = mysql_query(query)182if res && (res.size > 0)183print_status("\tThe following users have RELOAD Privilege:")184res.each do |row|185print_status("\t\tUser: #{row[0]} Host: #{row[1]}")186end187end188query = "select user, host from mysql.user where Shutdown_priv = 'Y'"189res = mysql_query(query)190if res && (res.size > 0)191print_status("\tThe following users have SHUTDOWN Privilege:")192res.each do |row|193print_status("\t\tUser: #{row[0]} Host: #{row[1]}")194end195end196query = "select user, host from mysql.user where Super_priv = 'Y'"197res = mysql_query(query)198if res && (res.size > 0)199print_status("\tThe following users have SUPER Privilege:")200res.each do |row|201print_status("\t\tUser: #{row[0]} Host: #{row[1]}")202end203end204query = "select user, host from mysql.user where FILE_priv = 'Y'"205res = mysql_query(query)206if res && (res.size > 0)207print_status("\tThe following users have FILE Privilege:")208res.each do |row|209print_status("\t\tUser: #{row[0]} Host: #{row[1]}")210end211end212query = "select user, host from mysql.user where Process_priv = 'Y'"213res = mysql_query(query)214if res && (res.size > 0)215print_status("\tThe following users have PROCESS Privilege:")216res.each do |row|217print_status("\t\tUser: #{row[0]} Host: #{row[1]}")218end219end220queryinmysql = %| select user, host221from mysql.user where222(Select_priv = 'Y') or223(Insert_priv = 'Y') or224(Update_priv = 'Y') or225(Delete_priv = 'Y') or226(Create_priv = 'Y') or227(Drop_priv = 'Y')|228res = mysql_query(queryinmysql)229if res && (res.size > 0)230print_status("\tThe following accounts have privileges to the mysql database:")231res.each do |row|232print_status("\t\tUser: #{row[0]} Host: #{row[1]}")233end234end235236# Anonymous Account Check237queryanom = "select user, host from mysql.user where user = ''"238res = mysql_query(queryanom)239if res && (res.size > 0)240print_status("\tAnonymous Accounts are Present:")241res.each do |row|242print_status("\t\tUser: #{row[0]} Host: #{row[1]}")243end244end245246# Blank Password Check247queryblankpass = "select user, host, #{password_field} from mysql.user where length(#{password_field}) = 0 or #{password_field} is null"248res = mysql_query(queryblankpass)249if res && (res.size > 0)250print_status("\tThe following accounts have empty passwords:")251res.each do |row|252print_status("\t\tUser: #{row[0]} Host: #{row[1]}")253end254end255256# Wildcard host257querywildcrd = 'select user, host from mysql.user where host = "%"'258res = mysql_query(querywildcrd)259if res && (res.size > 0)260print_status("\tThe following accounts are not restricted by source:")261res.each do |row|262print_status("\t\tUser: #{row[0]} Host: #{row[1]}")263end264end265266mysql_logoff unless session267end268# rubocop:enable Style/ZeroLengthPredicate269end270271272