-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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 NText column parsing to MSSQL #19054
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
@@ -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", '') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's a directive for null terminated strings as an alternative to this
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unless this is needed for utf16 support? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is, but I'm not positive. Do you have any suggestions for verifying this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Circling back - this had some unexpected behavior. ntext doesn't just end in null, but separates characters with it, and unpack with Z was removing characters to the right of null values |
||
|
||
when 104 | ||
col[:id] = :bitn | ||
col[:int_size] = data.slice!(0, 1).unpack('C')[0] | ||
|
@@ -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] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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);"})} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll probably want this to use the convention of the other PR, but can fix separately |
||
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") } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker; I don't think this follows the format that's expected for module documentation: #19054 (comment)