Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/mysql/mysql_enum.rb
19534 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Auxiliary::Report
8
include Msf::Exploit::Remote::MYSQL
9
include Msf::OptionalSession::MySQL
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'MySQL Enumeration Module',
16
'Description' => %q{
17
This module allows for simple enumeration of MySQL Database Server
18
provided proper credentials to connect remotely.
19
},
20
'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ],
21
'License' => MSF_LICENSE,
22
'References' => [
23
[ 'URL', 'https://cisecurity.org/benchmarks.html' ]
24
],
25
'Notes' => {
26
'Stability' => [CRASH_SAFE],
27
'SideEffects' => [IOC_IN_LOGS],
28
'Reliability' => []
29
}
30
)
31
)
32
end
33
34
def report_cred(opts)
35
service_data = {
36
address: opts[:ip],
37
port: opts[:port],
38
service_name: opts[:service_name],
39
protocol: 'tcp',
40
workspace_id: myworkspace_id
41
}
42
43
credential_data = {
44
origin_type: :service,
45
module_fullname: fullname,
46
username: opts[:user],
47
private_data: opts[:password],
48
private_type: :nonreplayable_hash,
49
jtr_format: 'mysql,mysql-sha1'
50
}.merge(service_data)
51
52
login_data = {
53
core: create_credential(credential_data),
54
status: Metasploit::Model::Login::Status::UNTRIED,
55
proof: opts[:proof]
56
}.merge(service_data)
57
58
create_credential_login(login_data)
59
end
60
61
def run
62
# If we have a session make use of it
63
if session
64
print_status("Using existing session #{session.sid}")
65
self.mysql_conn = session.client
66
self.sock = session.client.socket
67
else
68
# otherwise fallback to attempting to login
69
return unless mysql_login_datastore
70
end
71
72
print_status('Running MySQL Enumerator...')
73
print_status('Enumerating Parameters')
74
#-------------------------------------------------------
75
# getting all variables
76
vparm = {}
77
res = mysql_query('show variables') || []
78
res.each do |row|
79
# print_status(" | #{row.join(" | ")} |")
80
vparm[row[0]] = row[1]
81
end
82
83
#-------------------------------------------------------
84
# MySQL Version
85
print_status("\tMySQL Version: #{vparm['version']}")
86
print_status("\tCompiled for the following OS: #{vparm['version_compile_os']}")
87
print_status("\tArchitecture: #{vparm['version_compile_machine']}")
88
print_status("\tServer Hostname: #{vparm['hostname']}")
89
print_status("\tData Directory: #{vparm['datadir']}")
90
91
if vparm['log'] == 'OFF'
92
print_status("\tLogging of queries and logins: OFF")
93
else
94
print_status("\tLogging of queries and logins: ON")
95
print_status("\tLog Files Location: #{vparm['log_bin']}")
96
end
97
98
print_status("\tOld Password Hashing Algorithm #{vparm['old_passwords']}")
99
print_status("\tLoading of local files: #{vparm['local_infile']}")
100
print_status("\tDeny logins with old Pre-4.1 Passwords: #{vparm['secure_auth']}")
101
print_status("\tSkipping of GRANT TABLE: #{vparm['skip_grant_tables']}") if vparm['skip_grant_tables']
102
print_status("\tAllow Use of symlinks for Database Files: #{vparm['have_symlink']}")
103
print_status("\tAllow Table Merge: #{vparm['have_merge_engine']}")
104
print_status("\tRestrict DB Enumeration by Privilege: #{vparm['safe_show_database']}") if vparm['safe_show_database']
105
106
if vparm['have_openssl'] == 'YES'
107
print_status("\tSSL Connections: Enabled")
108
print_status("\tSSL CA Certificate: #{vparm['ssl_ca']}")
109
print_status("\tSSL Key: #{vparm['ssl_key']}")
110
print_status("\tSSL Certificate: #{vparm['ssl_cert']}")
111
else
112
print_status("\tSSL Connection: #{vparm['have_openssl']}")
113
end
114
115
#-------------------------------------------------------
116
# Database selection
117
query = 'use mysql'
118
mysql_query(query)
119
120
# Starting from MySQL 5.7, the 'password' column was changed to 'authentication_string'.
121
if vparm['version'][0..2].to_f > 5.6
122
password_field = 'authentication_string'
123
else
124
password_field = 'password'
125
end
126
127
# rubocop:disable Style/ZeroLengthPredicate
128
# MySQL query response does not support `.empty?`
129
130
# Account Enumeration
131
# Enumerate all accounts with their password hashes
132
print_status('Enumerating Accounts:')
133
query = "select user, host, #{password_field} from mysql.user"
134
res = mysql_query(query)
135
if res && (res.size > 0)
136
print_status("\tList of Accounts with Password Hashes:")
137
res.each do |row|
138
print_good("\t\tUser: #{row[0]} Host: #{row[1]} Password Hash: #{row[2]}")
139
report_cred(
140
ip: mysql_conn.peerhost,
141
port: mysql_conn.peerport,
142
user: row[0],
143
password: row[2],
144
service_name: 'mysql',
145
proof: row.inspect
146
)
147
end
148
end
149
150
# Only list accounts that can log in with SSL if SSL is enabled
151
if vparm['have_openssl'] == 'YES'
152
query = %|select user, host, ssl_type from mysql.user where
153
(ssl_type = 'ANY') or
154
(ssl_type = 'X509') or
155
(ssl_type = 'SPECIFIED')|
156
res = mysql_query(query)
157
if res && res.size > 0
158
print_status("\tThe following users can login using SSL:")
159
res.each do |row|
160
print_status("\t\tUser: #{row[0]} Host: #{row[1]} SSLType: #{row[2]}")
161
end
162
end
163
end
164
query = "select user, host from mysql.user where Grant_priv = 'Y'"
165
res = mysql_query(query)
166
if res && (res.size > 0)
167
print_status("\tThe following users have GRANT Privilege:")
168
res.each do |row|
169
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
170
end
171
end
172
173
query = "select user, host from mysql.user where Create_user_priv = 'Y'"
174
res = mysql_query(query)
175
if res && (res.size > 0)
176
print_status("\tThe following users have CREATE USER Privilege:")
177
res.each do |row|
178
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
179
end
180
end
181
query = "select user, host from mysql.user where Reload_priv = 'Y'"
182
res = mysql_query(query)
183
if res && (res.size > 0)
184
print_status("\tThe following users have RELOAD Privilege:")
185
res.each do |row|
186
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
187
end
188
end
189
query = "select user, host from mysql.user where Shutdown_priv = 'Y'"
190
res = mysql_query(query)
191
if res && (res.size > 0)
192
print_status("\tThe following users have SHUTDOWN Privilege:")
193
res.each do |row|
194
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
195
end
196
end
197
query = "select user, host from mysql.user where Super_priv = 'Y'"
198
res = mysql_query(query)
199
if res && (res.size > 0)
200
print_status("\tThe following users have SUPER Privilege:")
201
res.each do |row|
202
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
203
end
204
end
205
query = "select user, host from mysql.user where FILE_priv = 'Y'"
206
res = mysql_query(query)
207
if res && (res.size > 0)
208
print_status("\tThe following users have FILE Privilege:")
209
res.each do |row|
210
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
211
end
212
end
213
query = "select user, host from mysql.user where Process_priv = 'Y'"
214
res = mysql_query(query)
215
if res && (res.size > 0)
216
print_status("\tThe following users have PROCESS Privilege:")
217
res.each do |row|
218
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
219
end
220
end
221
queryinmysql = %| select user, host
222
from mysql.user where
223
(Select_priv = 'Y') or
224
(Insert_priv = 'Y') or
225
(Update_priv = 'Y') or
226
(Delete_priv = 'Y') or
227
(Create_priv = 'Y') or
228
(Drop_priv = 'Y')|
229
res = mysql_query(queryinmysql)
230
if res && (res.size > 0)
231
print_status("\tThe following accounts have privileges to the mysql database:")
232
res.each do |row|
233
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
234
end
235
end
236
237
# Anonymous Account Check
238
queryanom = "select user, host from mysql.user where user = ''"
239
res = mysql_query(queryanom)
240
if res && (res.size > 0)
241
print_status("\tAnonymous Accounts are Present:")
242
res.each do |row|
243
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
244
end
245
end
246
247
# Blank Password Check
248
queryblankpass = "select user, host, #{password_field} from mysql.user where length(#{password_field}) = 0 or #{password_field} is null"
249
res = mysql_query(queryblankpass)
250
if res && (res.size > 0)
251
print_status("\tThe following accounts have empty passwords:")
252
res.each do |row|
253
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
254
end
255
end
256
257
# Wildcard host
258
querywildcrd = 'select user, host from mysql.user where host = "%"'
259
res = mysql_query(querywildcrd)
260
if res && (res.size > 0)
261
print_status("\tThe following accounts are not restricted by source:")
262
res.each do |row|
263
print_status("\t\tUser: #{row[0]} Host: #{row[1]}")
264
end
265
end
266
267
mysql_logoff unless session
268
end
269
# rubocop:enable Style/ZeroLengthPredicate
270
end
271
272