528 lines
12 KiB
Ruby
528 lines
12 KiB
Ruby
require 'msf/core'
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This module exposes methods for querying a remote MSSQL service
|
|
#
|
|
###
|
|
module Exploit::Remote::MSSQL
|
|
|
|
include Exploit::Remote::Udp
|
|
include Exploit::Remote::Tcp
|
|
|
|
#
|
|
# 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('MSSQL_USER', [ false, 'The username to authenticate as', 'sa']),
|
|
OptString.new('MSSQL_PASS', [ false, 'The password for the specified username', ''])
|
|
], Msf::Exploit::Remote::MSSQL)
|
|
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
|
|
|
|
var = nil
|
|
|
|
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
|
|
|
|
data[idx, data.length-idx].split(';').each do |d|
|
|
if (not var)
|
|
var = d
|
|
else
|
|
if (var.length > 0)
|
|
res[var] = d
|
|
var = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
return res
|
|
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='')
|
|
|
|
disconnect if self.sock
|
|
connect
|
|
|
|
p_hdr =
|
|
"\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
|
|
|
p_pk2 =
|
|
"\x30\x30\x30\x30\x30\x30\x61\x30\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x20\x18\x81\xb8\x2c\x08\x03"+
|
|
"\x01\x06\x0a\x09\x01\x01\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x73\x71\x75\x65\x6c\x64\x61"+
|
|
"\x20\x31\x2e\x30\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00"
|
|
|
|
p_pk3 =
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x04\x02\x00\x00\x4d\x53\x44"+
|
|
"\x42\x4c\x49\x42\x00\x00\x00\x07\x06\x00\x00"+
|
|
"\x00\x00\x0d\x11\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00"
|
|
|
|
|
|
p_lang =
|
|
"\x02\x01\x00\x47\x00\x00\x02\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+
|
|
"\x00\x00\x00\x00\x00\x00\x30\x30\x30\x00\x00"+
|
|
"\x00\x03\x00\x00\x00"
|
|
|
|
user = user.slice(0, 29)
|
|
pass = pass.slice(0, 29)
|
|
|
|
ulen = user.length.chr
|
|
plen = pass.length.chr
|
|
|
|
user << ("\x00" * (30-user.length))
|
|
pass << ("\x00" * (30-pass.length))
|
|
|
|
p_login = p_hdr + user + ulen + pass + plen
|
|
p_login << p_pk2 + plen + pass + p_pk3
|
|
|
|
sock.put(p_login)
|
|
sock.put(p_lang)
|
|
resp = sock.get_once
|
|
|
|
if (resp and resp.length > 10 and resp[8,1].unpack('C')[0] == 0xe3)
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
def mssql_login_datastore
|
|
mssql_login(datastore['MSSQL_USER'], datastore['MSSQL_PASS'])
|
|
end
|
|
|
|
def mssql_query(sql, doprint=false)
|
|
info = { :sql => sql }
|
|
|
|
pkt = "\x01\x01\x00\x00\x01\x00" + sql
|
|
|
|
len = [pkt.length+2].pack('n')
|
|
pkt.insert(2, len)
|
|
|
|
sock.put(pkt)
|
|
resp = sock.get(timeout=15)
|
|
|
|
mssql_parse_reply(resp, info)
|
|
mssql_print_reply(info) if doprint
|
|
info
|
|
end
|
|
|
|
def mssql_xpcmdshell(cmd, doprint=false)
|
|
mssql_query("xp_cmdshell '#{cmd}'", doprint)
|
|
end
|
|
|
|
def mssql_parse_header(header)
|
|
type, status, size, chan, pkt, window = header.unpack('CCnnCC')
|
|
return [status, size, pkt]
|
|
end
|
|
|
|
|
|
def mssql_print_reply(info)
|
|
|
|
print_status("SQL Query: #{info[:sql]}")
|
|
|
|
if(info[:done] and 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] and not info[:errors].empty?)
|
|
info[:errors].each do |err|
|
|
print_error(err)
|
|
end
|
|
end
|
|
|
|
if(info[:rows] and not info[:rows].empty?)
|
|
|
|
tbl = Rex::Ui::Text::Table.new(
|
|
'Indent' => 1,
|
|
'Header' => "",
|
|
'Columns' => info[:colnames]
|
|
)
|
|
|
|
info[:rows].each do |row|
|
|
tbl << row
|
|
end
|
|
|
|
print_line(tbl.to_s)
|
|
end
|
|
end
|
|
|
|
def mssql_parse_reply(resp, info)
|
|
info[:errors] = []
|
|
|
|
data = ""
|
|
|
|
status = 0
|
|
while status == 0
|
|
break if not resp
|
|
status, size, pkt = mssql_parse_header(resp.slice!(0,8))
|
|
break if not (status and size and pkt)
|
|
data << resp.slice!(0,(size-8))
|
|
end
|
|
|
|
until data.empty?
|
|
token = data.slice!(0,1).unpack('C')[0]
|
|
case token
|
|
when 0xa0
|
|
mssql_parse_column_name(data, info)
|
|
when 0xa1
|
|
mssql_parse_column_info(data, info)
|
|
when 0xd1
|
|
mssql_parse_row(data, info)
|
|
when 0x79
|
|
mssql_parse_ret(data, info)
|
|
when 0xfd, 0xfe, 0xff
|
|
mssql_parse_done(data, info)
|
|
when 0xaa
|
|
mssql_parse_error(data, info)
|
|
when nil
|
|
break
|
|
else
|
|
# info[:errors] << "unsupported token: #{token}"
|
|
end
|
|
end
|
|
|
|
info
|
|
end
|
|
|
|
def mssql_parse_column_name(data, info)
|
|
len = data.slice!(0,2).unpack('v')[0]
|
|
str = data.slice!(0,len)
|
|
info[:colnames] ||= []
|
|
while(not str.empty?)
|
|
len = str.slice!(0,1).unpack('C')[0]
|
|
col = len == 0 ? 'NULL' : str.slice!(0,len)
|
|
info[:colnames] << col
|
|
end
|
|
info
|
|
end
|
|
|
|
def mssql_parse_column_info(data, info)
|
|
len = data.slice!(0,2).unpack('v')[0]
|
|
str = data.slice!(0,len)
|
|
|
|
info[:colinfos] ||= []
|
|
|
|
idx = -1
|
|
|
|
while(not str.empty?)
|
|
|
|
idx += 1
|
|
|
|
### int
|
|
if(
|
|
str[0,5] == "\x07\x00\x20\x00\x38" or
|
|
str[0,5] == "\x07\x00\x08\x00\x38" or # int
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:int, 4]
|
|
next
|
|
end
|
|
|
|
### smallint
|
|
if(
|
|
str[0,5] == "\x0d\x00\x09\x00\x26" or
|
|
str[0,5] == "\x0d\x00\x21\x00\x26" or
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:smallint, str.slice!(0,1).unpack('C')[0]]
|
|
next
|
|
end
|
|
|
|
### smallint2
|
|
if(
|
|
str[0,5] == "\x06\x00\x08\x00\x34" or
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:smallint2, 2]
|
|
next
|
|
end
|
|
|
|
### image
|
|
if(
|
|
str[0,9] == "\x14\x00\x21\x00\x22\x00\x10\x00\x00" or
|
|
false
|
|
)
|
|
str.slice!(0,9)
|
|
len = str.slice!(0,2).unpack('v')[0]
|
|
nam = str.slice!(0,len)
|
|
info[:colinfos] << [:image, 0, nam]
|
|
next
|
|
end
|
|
|
|
### tinyint
|
|
if(
|
|
str[0,5] == "\x05\x00\x08\x00\x30" or
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:tinyint, 1]
|
|
next
|
|
end
|
|
|
|
|
|
### longint
|
|
if(
|
|
str[0,6] == "\x19\x00\x20\x00\x6c\x11" or
|
|
str[0,6] == "\x19\x00\x21\x00\x6c\x11" or
|
|
false
|
|
)
|
|
str.slice!(0,6)
|
|
info[:colinfos] << [:long, str.slice!(0,2).unpack("v")[0]]
|
|
next
|
|
end
|
|
|
|
### hex
|
|
if(str[0,5] == "\x04\x00\x20\x00\x25")
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:hex, str.slice!(0,1).unpack('C')[0]]
|
|
next
|
|
end
|
|
|
|
### string
|
|
if(
|
|
str[0,5] == "\x02\x00\x21\x00\x27" or # varchar
|
|
str[0,5] == "\x02\x00\x08\x00\x27" or
|
|
str[0,5] == "\x12\x00\x08\x00\x27" or
|
|
str[0,5] == "\x12\x00\x09\x00\x27" or
|
|
str[0,5] == "\x04\x00\x09\x00\x25" or # varbinary
|
|
str[0,5] == "\x04\x00\x21\x00\x25" or
|
|
str[0,5] == "\x04\x00\x09\x00\x25" or
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:string, str.slice!(0,1).unpack('C')[0]]
|
|
next
|
|
end
|
|
|
|
### char(x)
|
|
if(
|
|
str[0,5] == "\x01\x00\x08\x00\x2f" or
|
|
str[0,5] == "\x02\x00\x09\x00\x27" or
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:string, str.slice!(0,1).unpack("C")[0]]
|
|
next
|
|
end
|
|
|
|
### datetime
|
|
if(
|
|
str[0,5] == "\x0c\x00\x08\x00\x3d" or
|
|
false
|
|
)
|
|
str.slice!(0,5)
|
|
info[:colinfos] << [:datetime, 8]
|
|
next
|
|
end
|
|
|
|
### datetime2
|
|
if(
|
|
str[0,6] == "\x0f\x00\x21\x00\x6f\x08" or
|
|
false
|
|
)
|
|
str.slice!(0,6)
|
|
info[:colinfos] << [:datetime2, 8]
|
|
next
|
|
end
|
|
|
|
### binary
|
|
if(
|
|
str[0,6] == "\x04\x00\x21\x00\x25\x06" or
|
|
false
|
|
)
|
|
str.slice!(0,6)
|
|
info[:colinfos] << [:binary]
|
|
next
|
|
end
|
|
|
|
info[:errors] << "unknown column type: #{info[:colnames][idx]} == #{str.unpack("H*")[0]}"
|
|
info[:colinfos] << [:unknown, 6]
|
|
break
|
|
end
|
|
info
|
|
end
|
|
def mssql_parse_row(data, info)
|
|
|
|
info[:rows] ||= []
|
|
row = []
|
|
|
|
info[:colinfos].each do |col|
|
|
case col[0]
|
|
when :int
|
|
row << data.slice!(0,4).unpack('V')[0]
|
|
|
|
when :smallint
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
raw = data.slice!(0,len)
|
|
row << raw.unpack('v')[0]
|
|
|
|
when :smallint2
|
|
row << data.slice!(0,2).unpack('v')[0]
|
|
|
|
when :tinyint
|
|
row << data.slice!(0,1).unpack('C')[0]
|
|
|
|
when :long
|
|
|
|
# XXX: completely made up bignum parsing...
|
|
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
raw = data.slice!(0,len)
|
|
sum = 0
|
|
bit = raw.unpack("C*").reverse
|
|
bit.each_index do |idx|
|
|
base = (256 ** (bit.length - idx - 1))
|
|
if (base > 0)
|
|
sum += base * bit[idx]
|
|
else
|
|
sum += bit[idx]
|
|
end
|
|
end
|
|
|
|
row << "?#{raw.unpack("H*")[0]}"
|
|
|
|
when :hex
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
row << (len > 0) ? data.slice!(0,len) : ''
|
|
|
|
when :string
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
row << (len > 0) ? data.slice!(0,len) : ''
|
|
|
|
when :datetime
|
|
# XXX: convert to unix time
|
|
row << data.slice!(0,8).unpack("H*")[0]
|
|
|
|
when :datetime2
|
|
# XXX: convert to unix time
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
row << data.slice!(0,len).unpack("H*")[0]
|
|
|
|
when :image
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
row << (len > 0) ? data.slice!(0,len).unpack("H*")[0] : ''
|
|
|
|
when :binary
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
row << (len > 0) ? data.slice!(0,len).unpack("H*")[0] : ''
|
|
|
|
when :unknown
|
|
len = data.slice!(0,1).unpack('C')[0]
|
|
row << "????-" + data.slice!(0,len) if len
|
|
end
|
|
end
|
|
info[:rows] << row
|
|
info
|
|
end
|
|
|
|
def mssql_parse_ret(data, info)
|
|
ret = data.slice!(0,4).unpack('N')[0]
|
|
info[:ret] = ret
|
|
info
|
|
end
|
|
|
|
def mssql_parse_done(data, info)
|
|
status, cmd, rows = data.slice!(0,8).unpack('vvV')
|
|
info[:done] = { :status => status, :cmd => cmd, :rows => rows }
|
|
info
|
|
end
|
|
|
|
def mssql_parse_error(data, info)
|
|
len = data.slice!(0,2).unpack('v')[0]
|
|
buff = data.slice!(0,len)
|
|
|
|
errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
|
|
emsg = buff.slice!(0,elen)
|
|
info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
|
|
info
|
|
end
|
|
|
|
end
|
|
end
|