Skip to content

Commit

Permalink
adds ntext parsing to mssql
Browse files Browse the repository at this point in the history
  • Loading branch information
zgoldman-r7 committed Apr 18, 2024
1 parent 2cf8ea3 commit 3e0b5eb
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 24 deletions.
27 changes: 26 additions & 1 deletion lib/rex/proto/mssql/client_mixin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def mssql_print_reply(info)
)

info[:rows].each do |row|
tbl << row
tbl << row.map{ |x| x.nil? ? 'nil' : x }
end

print_line(tbl.to_s)
Expand Down Expand Up @@ -206,6 +206,15 @@ def mssql_parse_tds_reply(data, info)
when 50
col[:id] = :bit

when 99
col[:id] = :ntext
col[:max_size] = data.slice!(0, 4).unpack('V')[0]
col[:codepage] = data.slice!(0, 2).unpack('v')[0]
col[:cflags] = data.slice!(0, 2).unpack('v')[0]
col[:charset_id] = data.slice!(0, 1).unpack('C')[0]
col[:namelen] = data.slice!(0, 1).unpack('C')[0]
col[:table_name] = data.slice!(0, (col[:namelen] * 2) + 1).gsub("\x00", '')

when 104
col[:id] = :bitn
col[:int_size] = data.slice!(0, 1).unpack('C')[0]
Expand Down Expand Up @@ -328,6 +337,22 @@ def mssql_parse_tds_row(data, info)
end
row << str.gsub("\x00", '')

when :ntext
str = nil
ptrlen = data.slice!(0, 1).unpack("C")[0]
ptr = data.slice!(0, ptrlen)
unless ptrlen == 0
timestamp = data.slice!(0, 8)
datalen = data.slice!(0, 4).unpack("V")[0]
if datalen > 0 && datalen < 65535
str = data.slice!(0, datalen).gsub("\x00", '')
else
str = ''
end
end
row << str


when :datetime
row << data.slice!(0, 8).unpack("H*")[0]

Expand Down
32 changes: 15 additions & 17 deletions modules/auxiliary/scanner/mssql/mssql_hashdump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ def run_host(ip)
if session
set_session(session.client)
elsif !mssql_login(datastore['USERNAME'], datastore['PASSWORD'])
print_error('Invalid SQL Server credentials')
info = self.mssql_client.initial_connection_info
if info[:errors] && !info[:errors].empty?
info[:errors].each do |err|
print_error(err)
end
end
return
end

Expand Down Expand Up @@ -79,7 +84,7 @@ def run_host(ip)

unless is_sysadmin == 0
mssql_hashes = mssql_hashdump(version_year)
unless mssql_hashes.nil?
unless mssql_hashes.nil? || mssql_hashes.empty?
report_hashes(mssql_hashes,version_year)
end
end
Expand All @@ -89,14 +94,12 @@ def run_host(ip)
# Stores the grabbed hashes as loot for later cracking
# The hash format is slightly different between 2k and 2k5/2k8
def report_hashes(mssql_hashes, version_year)

case version_year
when "2000"
hashtype = "mssql"

when "2005", "2008"
hashtype = "mssql05"
when "2012", "2014"
else
hashtype = "mssql12"
end

Expand All @@ -107,12 +110,6 @@ def report_hashes(mssql_hashes, version_year)
:proto => 'tcp'
)

tbl = Rex::Text::Table.new(
'Header' => 'MS SQL Server Hashes',
'Indent' => 1,
'Columns' => ['Username', 'Hash']
)

service_data = {
address: ::Rex::Socket.getaddress(mssql_client.peerhost,true),
port: mssql_client.peerport,
Expand All @@ -125,12 +122,15 @@ def report_hashes(mssql_hashes, version_year)
next if row[0].nil? or row[1].nil?
next if row[0].empty? or row[1].empty?

username = row[0]
upcase_hash = "0x#{row[1].upcase}"

credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_type: :nonreplayable_hash,
private_data: "0x#{row[1]}",
username: row[0],
private_data: upcase_hash,
username: username,
jtr_format: hashtype
}

Expand All @@ -146,8 +146,7 @@ def report_hashes(mssql_hashes, version_year)
login_data.merge!(service_data)
login = create_credential_login(login_data)

tbl << [row[0], row[1]]
print_good("Saving #{hashtype} = #{row[0]}:#{row[1]}")
print_good("Saving #{hashtype} = #{username}:#{upcase_hash}")
end
end

Expand All @@ -164,8 +163,7 @@ def mssql_hashdump(version_year)
case version_year
when "2000"
results = mssql_query(mssql_2k_password_hashes())[:rows]

when "2005", "2008", "2012", "2014"
else
results = mssql_query(mssql_2k5_password_hashes())[:rows]
end

Expand Down
6 changes: 5 additions & 1 deletion modules/auxiliary/scanner/mssql/mssql_schemadump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ def run_host(ip)

# Grab all the DB schema and save it as notes
mssql_schema = get_mssql_schema
return nil if mssql_schema.nil? or mssql_schema.empty?
if mssql_schema.nil? or mssql_schema.empty?
print_good output if datastore['DISPLAY_RESULTS']
print_warning('No schema information found')
return nil
end
mssql_schema.each do |db|
report_note(
:host => mssql_client.peerhost,
Expand Down
12 changes: 7 additions & 5 deletions spec/acceptance/mssql_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
lines: {
all: {
required: [
'Instance Name:'
/Instance Name: "\w+"/,
]
},
}
Expand Down Expand Up @@ -64,8 +64,12 @@
lines: {
all: {
required: [
'Instance Name:',
'Scanned 1 of 1 hosts (100% complete)'
/Instance Name: "\w+"/,
'Microsoft SQL Server Schema',
'Host:',
'Port:',
'Instance:',
'Version:'
]
},
}
Expand All @@ -78,9 +82,7 @@
lines: {
all: {
required: [
# Default module query
"Response",
# Result
"Microsoft SQL Server",
]
},
Expand Down
9 changes: 9 additions & 0 deletions test/modules/post/test/mssql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ def test_console_query
end
end

def test_datatypes
it "should support ntext TDS datatype" do
stdout = with_mocked_console(session) {|console| console.run_single(%{ query "select cast('foo' as ntext);"})}
ret = true
ret &&= stdout.buf.match?(/0 foo/)
ret
end
end

def test_console_help
it "should support the help command" do
stdout = with_mocked_console(session) { |console| console.run_single("help") }
Expand Down

0 comments on commit 3e0b5eb

Please sign in to comment.