1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
local _G = require "_G"
local mysql = require "mysql"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"
description = [[
Audits MySQL database server security configuration against parts of
the CIS MySQL v1.0.2 benchmark (the engine can be used for other MySQL
audits by creating appropriate audit files).
]]
---
-- @usage
-- nmap -p 3306 --script mysql-audit --script-args "mysql-audit.username='root', \
-- mysql-audit.password='foobar',mysql-audit.filename='nselib/data/mysql-cis.audit'"
--
-- @args mysql-audit.username the username with which to connect to the database
-- @args mysql-audit.password the password with which to connect to the database
-- @args mysql-audit.filename the name of the file containing the audit rulebase, "mysql-cis.audit" by default
--
-- @output
-- PORT STATE SERVICE
-- 3306/tcp open mysql
-- | mysql-audit:
-- | CIS MySQL Benchmarks v1.0.2
-- | 3.1: Skip symbolic links => PASS
-- | 3.2: Logs not on system partition => PASS
-- | 3.2: Logs not on database partition => PASS
-- | 4.1: Supported version of MySQL => REVIEW
-- | Version: 5.1.54-1ubuntu4
-- | 4.4: Remove test database => PASS
-- | 4.5: Change admin account name => FAIL
-- | 4.7: Verify Secure Password Hashes => PASS
-- | 4.9: Wildcards in user hostname => FAIL
-- | The following users were found with wildcards in hostname
-- | root
-- | super
-- | super2
-- | 4.10: No blank passwords => PASS
-- | 4.11: Anonymous account => PASS
-- | 5.1: Access to mysql database => REVIEW
-- | Verify the following users that have access to the MySQL database
-- | user host
-- | root localhost
-- | root patrik-11
-- | root 127.0.0.1
-- | debian-sys-maint localhost
-- | root %
-- | super %
-- | 5.2: Do not grant FILE privileges to non Admin users => REVIEW
-- | The following users were found having the FILE privilege
-- | super
-- | super2
-- | 5.3: Do not grant PROCESS privileges to non Admin users => REVIEW
-- | The following users were found having the PROCESS privilege
-- | super
-- | 5.4: Do not grant SUPER privileges to non Admin users => REVIEW
-- | The following users were found having the SUPER privilege
-- | super
-- | 5.5: Do not grant SHUTDOWN privileges to non Admin users => REVIEW
-- | The following users were found having the SHUTDOWN privilege
-- | super
-- | 5.6: Do not grant CREATE USER privileges to non Admin users => REVIEW
-- | The following users were found having the CREATE USER privilege
-- | super
-- | 5.7: Do not grant RELOAD privileges to non Admin users => REVIEW
-- | The following users were found having the RELOAD privilege
-- | super
-- | 5.8: Do not grant GRANT privileges to non Admin users => PASS
-- | 6.2: Disable Load data local => FAIL
-- | 6.3: Disable old password hashing => PASS
-- | 6.4: Safe show database => FAIL
-- | 6.5: Secure auth => FAIL
-- | 6.6: Grant tables => FAIL
-- | 6.7: Skip merge => FAIL
-- | 6.8: Skip networking => FAIL
-- | 6.9: Safe user create => FAIL
-- | 6.10: Skip symbolic links => FAIL
-- |
-- |_ The audit was performed using the db-account: root
-- Version 0.1
-- Created 05/29/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
portrule = shortport.port_or_service(3306, "mysql")
local TEMPLATE_NAME, ADMIN_ACCOUNTS = "", ""
local function fail (err) return stdnse.format_output(false, err) end
local function loadAuditRulebase( filename )
local rules = {}
local env = setmetatable({
test = function(t) table.insert(rules, t) end;
}, {__index = _G})
filename = nmap.fetchfile("nselib/data/" .. filename) or filename
stdnse.debug(1, "Loading rules from: %s", filename)
local file, err = loadfile(filename, "t", env)
if ( not(file) ) then
return false, fail(("Failed to load rulebase:\n%s"):format(err))
end
file()
TEMPLATE_NAME = env.TEMPLATE_NAME
ADMIN_ACCOUNTS = env.ADMIN_ACCOUNTS
return true, rules
end
action = function( host, port )
local username = stdnse.get_script_args("mysql-audit.username")
local password = stdnse.get_script_args("mysql-audit.password")
local filename = stdnse.get_script_args("mysql-audit.filename") or "mysql-cis.audit"
if ( not(username) ) then
return fail("No username was supplied (see mysql-audit.username)")
end
local status, tests = loadAuditRulebase( filename )
if( not(status) ) then return tests end
local socket = nmap.new_socket()
status = socket:connect(host, port)
local response
status, response = mysql.receiveGreeting( socket )
if ( not(status) ) then return response end
status, response = mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
if ( not(status) ) then return fail("Failed to authenticate") end
local results = {}
for _, test in ipairs(tests) do
local queries = ( "string" == type(test.sql) ) and { test.sql } or test.sql
local rowstab = {}
for _, query in ipairs(queries) do
local row
status, row = mysql.sqlQuery( socket, query )
if ( not(status) ) then
table.insert( results, { ("%s: ERROR: Failed to execute SQL statement"):format(test.id) } )
else
table.insert(rowstab, row)
end
end
if ( #rowstab > 0 ) then
local result_part = {}
local res = test.check(rowstab)
local status, data = res.status, res.result
status = ( res.review and "REVIEW" ) or (status and "PASS" or "FAIL")
table.insert( result_part, ("%s: %s => %s"):format(test.id, test.desc, status) )
if ( data ) then
table.insert(result_part, { data } )
end
table.insert( results, result_part )
end
end
socket:close()
results.name = TEMPLATE_NAME
table.insert(results, "")
table.insert(results, {name = "Additional information", ("The audit was performed using the db-account: %s"):format(username),
("The following admin accounts were excluded from the audit: %s"):format(table.concat(ADMIN_ACCOUNTS, ","))
})
return stdnse.format_output(true, { results })
end
|