Skip to content

Commit

Permalink
Land #19054, Add NText column parsing to MSSQL
Browse files Browse the repository at this point in the history
  • Loading branch information
adfoster-r7 authored Apr 19, 2024
2 parents 7e25088 + d357484 commit 376bdef
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 24 deletions.
91 changes: 91 additions & 0 deletions documentation/modules/auxiliary/scanner/mssql/mssql_hashdump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## Description

The `mssql_hashdump` module queries an MSSQL instance or session and returns hashed user:pass pairs. These pairs can be decripted via or `hashcat`.

## Available Options

```
msf6 auxiliary(scanner/mssql/mssql_hashdump) > options
Module options (auxiliary/scanner/mssql/mssql_hashdump):
Name Current Setting Required Description
---- --------------- -------- -----------
USE_WINDOWS_AUTHENT false yes Use windows authentication (requires DOMAIN option set)
Used when making a new connection via RHOSTS:
Name Current Setting Required Description
---- --------------- -------- -----------
DATABASE MSSQL no The database to authenticate against
PASSWORD no The password for the specified username
RHOSTS no The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 1433 no The target port (TCP)
THREADS 1 yes The number of concurrent threads (max one per host)
USERNAME MSSQL no The username to authenticate as
Used when connecting via an existing SESSION:
Name Current Setting Required Description
---- --------------- -------- -----------
SESSION no The session to run this module on
```

## Scenarios

With a session:
```
msf6 auxiliary(scanner/mssql/mssql_hashdump) > sessions
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 mssql MSSQL sa @ 127.0.0.1:1433 127.0.0.1:52307 -> 127.0.0.1:1433 (127.0.0.1)
msf6 auxiliary(scanner/mssql/mssql_hashdump) > run session=-1
[*] Using existing session 1
[*] Instance Name: "758549b9f69e"
[+] Saving mssql12 = sa:0x0200F433830BDBA809805FE53E59E7A1AACF9AC21241881F76B9B95EDC713FD01C8E692705409A5C0F8A46DDB1707A283BA9307D6B3C664BB9F7652758B70262C88F629DBC7E
[+] Saving mssql12 = ##MS_PolicyEventProcessingLogin##:0x02003F137BFF990AE7D0B89DA15EEDF4B962E200A9AAECE6AC7E4786176A08C4D278C0E9B203795F972CB508FD17827A755AF4284A9891F01C502EEBB5ECFABD7FA6CD3603E2
[+] Saving mssql12 = ##MS_PolicyTsqlExecutionLogin##:0x0200DA9B84641F740A6423EC34F1B354FB81D9DF53456A7A7A8CCB794B295896C0CD19718C2C9537D3A7E82C41350F1549E2E2B99D819345DCABF1855AF2F83FA6CDC3EF8F96
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/mssql/mssql_hashdump) > run RPORT=1433 RHOSTS=127.0.0.1 USERNAME=sa PASSWORD=yourStrong(!)Password
[*] 127.0.0.1:1433 - Instance Name: "758549b9f69e"
[+] 127.0.0.1:1433 - Saving mssql12 = sa:0x0200F433830BDBA809805FE53E59E7A1AACF9AC21241881F76B9B95EDC713FD01C8E692705409A5C0F8A46DDB1707A283BA9307D6B3C664BB9F7652758B70262C88F629DBC7E
[+] 127.0.0.1:1433 - Saving mssql12 = ##MS_PolicyEventProcessingLogin##:0x02003F137BFF990AE7D0B89DA15EEDF4B962E200A9AAECE6AC7E4786176A08C4D278C0E9B203795F972CB508FD17827A755AF4284A9891F01C502EEBB5ECFABD7FA6CD3603E2
[+] 127.0.0.1:1433 - Saving mssql12 = ##MS_PolicyTsqlExecutionLogin##:0x0200DA9B84641F740A6423EC34F1B354FB81D9DF53456A7A7A8CCB794B295896C0CD19718C2C9537D3A7E82C41350F1549E2E2B99D819345DCABF1855AF2F83FA6CDC3EF8F96
[*] 127.0.0.1:1433 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```

Directly querying a machine:
```
msf6 auxiliary(scanner/mssql/mssql_hashdump) > run RPORT=1433 RHOSTS=127.0.0.1 USERNAME=sa PASSWORD=yourStrong(!)Password
[*] 127.0.0.1:1433 - Instance Name: "758549b9f69e"
[+] 127.0.0.1:1433 - Saving mssql12 = sa:0x0200F433830BDBA809805FE53E59E7A1AACF9AC21241881F76B9B95EDC713FD01C8E692705409A5C0F8A46DDB1707A283BA9307D6B3C664BB9F7652758B70262C88F629DBC7E
[+] 127.0.0.1:1433 - Saving mssql12 = ##MS_PolicyEventProcessingLogin##:0x02003F137BFF990AE7D0B89DA15EEDF4B962E200A9AAECE6AC7E4786176A08C4D278C0E9B203795F972CB508FD17827A755AF4284A9891F01C502EEBB5ECFABD7FA6CD3603E2
[+] 127.0.0.1:1433 - Saving mssql12 = ##MS_PolicyTsqlExecutionLogin##:0x0200DA9B84641F740A6423EC34F1B354FB81D9DF53456A7A7A8CCB794B295896C0CD19718C2C9537D3A7E82C41350F1549E2E2B99D819345DCABF1855AF2F83FA6CDC3EF8F96
[*] 127.0.0.1:1433 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```

Different MSSQL Versions have different hash formats. For example:

MSSQL (2000): 0x01002702560500000000000000000000000000000000000000008db43dd9b1972a636ad0c7d4b8c515cb8ce46578
MSSQL (2005): 0x010018102152f8f28c8499d8ef263c53f8be369d799f931b2fbe
MSSQL (2012 and later): 0x02000102030434ea1b17802fd95ea6316bd61d2c94622ca3812793e8fb1672487b5c904a45a31b2ab4a78890d563d2fcf5663e46fe797d71550494be50cf4915d3f4d55ec375

To decrypt:
Save into a `passwords.txt` file
Run with hashcat, based on the MSSQL Version:
`hashcat --force -m 131 ./hashes.txt ./passwords.txt` (MSSQL 2000)
`hashcat --force -m 132 ./hashes.txt ./passwords.txt` (MSSQL 2005)
`hashcat --force -m 1731 ./hashes.txt ./passwords.txt` (MSSQL 2012 and later)
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 376bdef

Please sign in to comment.