1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 12:41:29 +00:00
Files
nmap/scripts/ms-sql-tables.nse
patrik 1d26975ede o [NSE] Added a library for Microsoft SQL Server and 7 new scripts. The new
scripts are:
  - ms-sql-brute.nse uses the unpwdb library to guess credentials for MSSQL
  - ms-sql-config retrieves various configuration details from the server		
  - ms-sql-empty-password checks if the sa account has an empty password
  - ms-sql-hasdbaccess lists database access per user
  - ms-sql-query add support for running custom queries against the database
  - ms-sql-tables lists databases, tables, columns and datatypes with optional
    keyword filtering
  - ms-sql-xp-cmdshell adds support for OS command execution to privileged
    users
  [Patrik]
2010-04-04 10:11:54 +00:00

251 lines
7.4 KiB
Lua

description = [[
Queries Microsoft SQL Server (MSSQL) for a list of tables per database.
]]
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"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-tables.maxdb Limits the amount of databases that are
-- processed and returned (default 5). If set to zero or less
-- all databases are processed.
--
-- @args mssql-tables.maxtables Limits the amount of tables returned
-- (default 5). If set to zero or less all tables are returned.
--
-- @args mssql-tables.keywords If set shows only tables or columns matching
-- the keywords
--
-- @output
-- PORT STATE SERVICE
-- 1433/tcp open ms-sql-s
-- | mssql-tables:
-- | webshop
-- | table column type length
-- | payments user_id int 4
-- | payments purchase_id int 4
-- | payments cardholder varchar 50
-- | payments cardtype varchar 50
-- | payments cardno varchar 50
-- | payments expiry varchar 50
-- | payments cvv varchar 4
-- | products id int 4
-- | products manu varchar 50
-- | products model varchar 50
-- | products productname varchar 100
-- | products price float 8
-- | products imagefile varchar 255
-- | products quantity int 4
-- | products keywords varchar 100
-- | products description text 16
-- | users id int 4
-- | users username varchar 50
-- | users password varchar 50
-- |_ users fullname varchar 100
--
--
-- The sysdatabase table should be accessible by more or less everyone
-- The script attempts to use the sa account over some n00b if it has
-- the password in the registry. If not the first account in the
-- registry is used.
--
-- Once we have a list of DBs we iterate over it and attempt to extract
-- table names. In order for this to succeed we need to have either
-- sysadmin privileges or an account with access to the db. So, for each
-- db we successfully enumerate tables from we mark as finnished, we then
-- iterate over our know user accounts until either we exhausted our users
-- or we found all tables in all dbs.
--
-- Oh, and exclude all MS default dbs from this excercise.
--
-- Version 0.1
-- Created 01/17/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
-- Revised 04/02/2010 - v0.2
-- - Added support for filters
-- - Changed output formatting of restrictions
-- - Added parameter information in output if parameters are using their
-- defaults.
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, dbs, tables, helper
local username = nmap.registry.args['mssql.username']
local password = nmap.registry.args['mssql.password'] or ""
local output = {}
local exclude_dbs = { "'master'", "'tempdb'", "'model'", "'msdb'" }
local db_query
local done_dbs = {}
local creds = {}
local db_limit, tbl_limit
local DB_COUNT = nmap.registry.args["mssql-tables.maxdb"] and tonumber(nmap.registry.args["mssql-tables.maxdb"]) or 5
local TABLE_COUNT = nmap.registry.args["mssql-tables.maxtables"] and tonumber(nmap.registry.args["mssql-tables.maxtables"]) or 2
local keywords_filter = ""
if ( DB_COUNT <= 0 ) then
db_limit = ""
else
db_limit = string.format( "TOP %d", DB_COUNT )
end
if (TABLE_COUNT <= 0 ) then
tbl_limit = ""
else
tbl_limit = string.format( "TOP %d", TABLE_COUNT )
end
-- Build the keyword filter
if ( nmap.registry.args['mssql-tables.keywords'] ) then
local keywords = nmap.registry.args['mssql-tables.keywords']
local tmp_tbl = {}
if( type(keywords) == 'string' ) then
keywords = { keywords }
end
for _, v in ipairs(keywords) do
table.insert(tmp_tbl, ("'%s'"):format(v))
end
keywords_filter = (" AND ( so.name IN (%s) or sc.name IN (%s) ) "):format(
stdnse.strjoin(",", tmp_tbl),
stdnse.strjoin(",", tmp_tbl)
)
end
db_query = ("SELECT %s name from master..sysdatabases WHERE name NOT IN (%s)"):format(db_limit, stdnse.strjoin(",", exclude_dbs))
if ( username ) then
creds[username] = password
elseif ( not(username) and nmap.registry.mssqlusers ) then
-- do we have a sysadmin?
if ( nmap.registry.mssqlusers.sa ) then
creds["sa"] = nmap.registry.mssqlusers.sa
else
creds = nmap.registry.mssqlusers
end
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
status, dbs = helper:Query( db_query )
if ( status ) then
-- all done?
if ( #done_dbs == #dbs.rows ) then
break
end
for k, v in pairs(dbs.rows) do
if ( not( table_contains( done_dbs, v[1] ) ) ) then
query = [[ SELECT so.name 'table', sc.name 'column', st.name 'type', sc.length
FROM %s..syscolumns sc, %s..sysobjects so, %s..systypes st
WHERE so.id = sc.id AND sc.xtype=st.xtype AND
so.id IN (SELECT %s id FROM %s..sysobjects WHERE xtype='U') %s ORDER BY so.name, sc.name, st.name]]
query = query:format( v[1], v[1], v[1], tbl_limit, v[1], keywords_filter)
status, tables = helper:Query( query )
if ( not(status) ) then
stdnse.print_debug(tables)
else
local item = {}
item = mssql.Util.FormatOutputTable( tables, true )
if ( #item == 0 and keywords_filter ~= "" ) then
table.insert(item, "Filter returned no matches")
end
item.name = v[1]
table.insert(output, item)
table.insert(done_dbs, v[1])
end
end
end
end
helper:Disconnect()
end
local pos = 1
local restrict_tbl = {}
if ( nmap.registry.args['mssql-tables.keywords'] ) then
tmp = nmap.registry.args['mssql-tables.keywords']
if ( type(tmp) == 'table' ) then
tmp = stdnse.strjoin(',', tmp)
end
table.insert(restrict_tbl, 1, ("Filter: %s"):format(tmp))
pos = pos + 1
else
table.insert(restrict_tbl, 1, "No filter (see mssql-tables.keywords)")
end
if ( DB_COUNT > 0 ) then
local tmp = ("Output restricted to %d databases"):format(DB_COUNT)
if ( not(nmap.registry.args['mssql-tables.maxdb']) ) then
tmp = tmp .. " (see mssql-tables.maxdb)"
end
table.insert(restrict_tbl, 1, tmp)
pos = pos + 1
end
if ( TABLE_COUNT > 0 ) then
local tmp = ("Output restricted to %d tables"):format(TABLE_COUNT)
if ( not(nmap.registry.args['mssql-tables.maxtables']) ) then
tmp = tmp .. " (see mssql-tables.maxtables)"
end
table.insert(restrict_tbl, 1, tmp)
pos = pos + 1
end
if ( 1 < pos and #output > 0) then
restrict_tbl.name = "Restrictions"
table.insert(output, "")
table.insert(output, restrict_tbl)
end
output = stdnse.format_output( true, output )
return output
end