Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add mssql_version module #18907

Merged
merged 7 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion lib/msf/core/exploit/remote/mssql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ module Exploit::Remote::MSSQL

attr_accessor :mssql_client

ENCRYPT_OFF = 0x00 #Encryption is available but off.
ENCRYPT_ON = 0x01 #Encryption is available and on.
ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
ENCRYPT_REQ = 0x03 #Encryption is required.

#
# Creates an instance of a MSSQL exploit module.
#
Expand Down Expand Up @@ -48,11 +53,15 @@ def initialize(info = {})
register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase })
end

def set_session(client)
def set_mssql_session(client)
print_status("Using existing session #{session.sid}")
@mssql_client = client
end

def create_mssql_client
@mssql_client ||= Rex::Proto::MSSQL::Client.new(self, framework, datastore['RHOST'], datastore['RPORT'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we just need a standalone method for creating the mssql client instance here - since the only way to get access to one is via mssql_login - which we don't have creds for

end

#
# This method sends a UDP query packet to the server and
# parses out the reply packet into a hash
Expand Down
106 changes: 14 additions & 92 deletions lib/rex/proto/mssql/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,7 @@ def detect_platform_and_arch
#

def mssql_login(user='sa', pass='', db='', domain_name='')
disconnect if self.sock
connect
mssql_prelogin

prelogin_data = mssql_prelogin
if auth == Msf::Exploit::Remote::AuthOption::KERBEROS
idx = 0
pkt = ''
Expand Down Expand Up @@ -236,6 +233,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='')
info = {:errors => []}
info = mssql_parse_reply(resp, info)
self.initial_connection_info = info
self.initial_connection_info[:prelogin_data] = prelogin_data

return false if not info
return info[:login_ack] ? true : false
Expand Down Expand Up @@ -468,6 +466,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='')
info = {:errors => []}
info = mssql_parse_reply(resp, info)
self.initial_connection_info = info
self.initial_connection_info[:prelogin_data] = prelogin_data

return false if not info
info[:login_ack] ? true : false
Expand All @@ -477,85 +476,20 @@ def mssql_login(user='sa', pass='', db='', domain_name='')
#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")

# if manually set, we will honour
if tdsencryption == true
encryption = ENCRYPT_ON
else
encryption = ENCRYPT_NOT_SUP
end

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
disconnect if self.sock
connect

pkt = pkt_hdr.pack("CCnnCC") + pkt_data
pkt = mssql_prelogin_packet

resp = mssql_send_recv(pkt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we renamed mssql_get_version and changed it to just handle parsing, we'd be able to reuse it here below and avoid more of the copy/pasta:

def parse_pre_login_response(data)

It would also be unit-testable which would be nice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea, but can you explain what you're envisioning? Where would we reuse it, and what are you suggesting we restrict from the method?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can get rid of the first block of copy/pasta on lines

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
framework_module.print_error("Unable to parse encryption req " \
"during pre-login, this may not be a MSSQL server")
encryption_mode = ENCRYPT_NOT_SUP
end

and

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
framework_module.print_error("Unable to parse encryption req " \
"during pre-login, this may not be a MSSQL server")
encryption_mode = ENCRYPT_NOT_SUP
end


idx = 0
data = parse_prelogin_response(resp)

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
unless data[:encryption]
framework_module.print_error("Unable to parse encryption req " \
"during pre-login, this may not be a MSSQL server")
encryption_mode = ENCRYPT_NOT_SUP
data[:encryption] = ENCRYPT_NOT_SUP
end

##########################################################
Expand All @@ -573,7 +507,7 @@ def mssql_prelogin(enc_error=false)
#
##########################################################

if encryption_mode == ENCRYPT_REQ
if data[:encryption] == ENCRYPT_REQ
# restart prelogin process except that we tell SQL Server
# than we are now able to encrypt
disconnect if self.sock
Expand All @@ -586,27 +520,15 @@ def mssql_prelogin(enc_error=false)
"been enabled based on server response.")

resp = mssql_send_recv(pkt)
data = parse_prelogin_response(resp)

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
unless data[:encryption]
framework_module.print_error("Unable to parse encryption req " \
"during pre-login, this may not be a MSSQL server")
encryption_mode = ENCRYPT_NOT_SUP
data[:encryption] = ENCRYPT_NOT_SUP
end
end
encryption_mode
data
end

def mssql_ssl_send_recv(req, tdsproxy, timeout=15, check_status=true)
Expand Down
82 changes: 82 additions & 0 deletions lib/rex/proto/mssql/client_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,88 @@ def mssql_print_reply(info)
end
end

def mssql_prelogin_packet
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")

# if manually set, we will honour
if tdsencryption == true
encryption = ENCRYPT_ON
else
encryption = ENCRYPT_NOT_SUP
end

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
pkt
end

def parse_prelogin_response(resp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be great to add tests for this as part of this PR so we can circle back to swapping this out with alternative implementations in the future 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already working on it, just pushed this in the meantime 👍

data = {}
if resp.length > 5 # minimum size for response specification
version_index = resp.slice(1, 2).unpack('n')[0]

major = resp.slice(version_index, 1).unpack('C')[0]
minor = resp.slice(version_index+1, 1).unpack('C')[0]
build = resp.slice(version_index+2, 2).unpack('n')[0]

enc_index = resp.slice(6, 2).unpack('n')[0]
data[:encryption] = resp.slice(enc_index, 1).unpack('C')[0]
end

if major && minor && build
data[:version] = "#{major}.#{minor}.#{build}"
end

return data
end

def mssql_send_recv(req, timeout=15, check_status = true)
sock.put(req)

Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module to work, valid administrative user credentials must be
def run
print_status("Running MS SQL Server Enumeration...")
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("Login was unsuccessful. Check your credentials.")
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_escalate_dbowner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def initialize(info = {})
def run
# Check connection and issue initial query
if session
set_session(session.client)
set_mssql_session(session.client)
else
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
if mssql_login_datastore
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_escalate_execute_as.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def initialize(info = {})

def run
if session
set_session(session.client)
set_mssql_session(session.client)
else
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
if mssql_login_datastore
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def initialize(info = {})

def run
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("Error with mssql_login call")
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_findandsampledata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ def sql_statement()
# CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING
begin
if session
set_session(session.client)
set_mssql_session(session.client)
else
print_line(" ")
print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...")
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_idf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def run

begin
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error('Login failed')
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def cmd_select(*args)

def run
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("Error with mssql_login call")
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/admin/mssql/mssql_sql_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def run

begin
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("#{datastore['RHOST']}:#{datastore['RPORT']} - Invalid SQL Server credentials")
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/scanner/mssql/mssql_hashdump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def initialize

def run_host(ip)
if session
set_session(session.client)
set_mssql_session(session.client)
elsif !mssql_login(datastore['USERNAME'], datastore['PASSWORD'])
info = self.mssql_client.initial_connection_info
if info[:errors] && !info[:errors].empty?
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/scanner/mssql/mssql_ping.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class MetasploitModule < Msf::Auxiliary
def initialize
super(
'Name' => 'MSSQL Ping Utility',
'Description' => 'This module simply queries the MSSQL instance for information.',
'Description' => 'This module simply queries the MSSQL Browser service for server information.',
'Author' => 'MC',
'License' => MSF_LICENSE
)
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/scanner/mssql/mssql_schemadump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def initialize

def run_host(ip)
if session
set_session(session.client)
set_mssql_session(session.client)
else
unless mssql_login_datastore
print_error("#{datastore['RHOST']}:#{datastore['RPORT']} - Invalid SQL Server credentials")
Expand Down
Loading
Loading