description = [[ Queries Microsoft SQL Server (ms-sql) for a list of databases a user has access to. 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 mssql-brute or mssql-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. ]] 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 ms-sql-brute and ms-sql-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 -- | ms-sql-hasdbaccess: -- | webshop_reader -- | dbname owner -- | hr sa -- | finance sa -- | webshop sa -- | lordvader -- | dbname owner -- | testdb CQURE-NET\Administr -- |_ webshop sa -- 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 and rs) 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