description = [[ Queries Microsoft SQL Server (MSSQL) for a list of databases a user has access to. ]] author = "Patrik Karlsson" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"auth", "discovery","safe"} require 'shortport' require 'stdnse' require 'mssql' dependencies = {"ms-sql-brute", "ms-sql-empty-password"} --- -- @args mssql.username specifies the username to use to connect to -- the server. This option overrides any accounts found by -- the mssql-brute and mssql-empty-password scripts. -- -- @args mssql.password specifies the password to use to connect to -- the server. This option overrides any accounts found by -- the mssql-brute and mssql-empty-password scripts. -- -- @args mssql-hasdbaccess.limit limits the amount of databases per-user -- that are returned (default 5). If set to zero or less all -- databases the user has access to are returned. -- -- @output -- PORT STATE SERVICE -- 1433/tcp open ms-sql-s -- | mssql-hasdbaccess: -- | webshop_reader -- | dbname owner -- | hr sa -- | finance sa -- | webshop sa -- | lordvader -- | dbname owner -- | testdb CQURE-NET\Administr -- |_ webshop sa -- -- The script needs an account with the sysadmin server role to work. -- It needs to be fed credentials through the script arguments or from -- the scripts mssq-brute or mssq-empty-password. -- -- When run, the script iterates over the credentials and attempts to run -- the command until either all credentials are exhausted or until the -- command is executed. -- -- Version 0.1 -- Created 01/17/2010 - v0.1 - created by Patrik Karlsson portrule = shortport.port_or_service(1433, "ms-sql-s") local function table_contains( tbl, val ) for k,v in pairs(tbl) do if ( v == val ) then return true end end return false end action = function( host, port ) local status, result, helper, rs local username = nmap.registry.args['mssql.username'] local password = nmap.registry.args['mssql.password'] or "" local creds local query, limit local output = {} local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" } local RS_LIMIT = nmap.registry.args["mssql-hasdbaccess.limit"] and tonumber(nmap.registry.args["mssql-hasdbaccess.limit"]) or 5 if ( RS_LIMIT <= 0 ) then limit = "" else limit = string.format( "TOP %d", RS_LIMIT ) end local query = { [[CREATE table #hasaccess(dbname varchar(255), owner varchar(255), DboOnly bit, ReadOnly bit, SingelUser bit, Detached bit, Suspect bit, Offline bit, InLoad bit, EmergencyMode bit, StandBy bit, [ShutDown] bit, InRecovery bit, NotRecovered bit )]], "INSERT INTO #hasaccess EXEC sp_MShasdbaccess", ("SELECT %s dbname, owner FROM #hasaccess WHERE dbname NOT IN(%s)"):format(limit, stdnse.strjoin(",", exclude_dbs)), "DROP TABLE #hasaccess" } if ( username ) then creds = {} creds[username] = password elseif ( not(username) and nmap.registry.mssqlusers ) then -- do we have a sysadmin? creds = nmap.registry.mssqlusers end -- If we don't have valid creds, simply fail silently if ( not(creds) ) then return end for username, password in pairs( creds ) do helper = mssql.Helper:new() status, result = helper:Connect(host, port) if ( not(status) ) then return " \n\n" .. result end status, result = helper:Login( username, password, nil, host.ip ) if ( not(status) ) then stdnse.print_debug("ERROR: %s", result) break end for _, q in pairs(query) do status, result = helper:Query( q ) if ( status ) then -- Only the SELECT statement should produce output if ( #result.rows > 0 ) then rs = result end end end helper:Disconnect() if ( status ) then result = mssql.Util.FormatOutputTable( rs, true ) result.name = username if ( RS_LIMIT > 0 ) then result.name = result.name .. (" (Showing %d first results)"):format(RS_LIMIT) end table.insert( output, result ) end end return stdnse.format_output( true, output ) end