diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/mysql-audit.nse | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/scripts/mysql-audit.nse b/scripts/mysql-audit.nse new file mode 100644 index 0000000..32a1482 --- /dev/null +++ b/scripts/mysql-audit.nse @@ -0,0 +1,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 |