diff --git a/lib/rex/proto/mssql/client_mixin.rb b/lib/rex/proto/mssql/client_mixin.rb index 153f582d59d2a..2c00c7ba869e1 100644 --- a/lib/rex/proto/mssql/client_mixin.rb +++ b/lib/rex/proto/mssql/client_mixin.rb @@ -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", '') + 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] diff --git a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb index cfac867a4f7ba..0b19e3cb63e1d 100644 --- a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb @@ -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 @@ -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 @@ -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 @@ -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, @@ -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 } @@ -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 @@ -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 diff --git a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb index bd4f5ad9ab8d5..234cb54990ed9 100644 --- a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb @@ -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, diff --git a/spec/acceptance/mssql_spec.rb b/spec/acceptance/mssql_spec.rb index 7fa4adc695c46..3c8a7c20b7afa 100644 --- a/spec/acceptance/mssql_spec.rb +++ b/spec/acceptance/mssql_spec.rb @@ -35,7 +35,7 @@ lines: { all: { required: [ - 'Instance Name:' + /Instance Name: "\w+"/, ] }, } @@ -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:' ] }, } @@ -78,9 +82,7 @@ lines: { all: { required: [ - # Default module query "Response", - # Result "Microsoft SQL Server", ] }, diff --git a/test/modules/post/test/mssql.rb b/test/modules/post/test/mssql.rb index 385b1e721ecf2..9619ce5b70f4e 100644 --- a/test/modules/post/test/mssql.rb +++ b/test/modules/post/test/mssql.rb @@ -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") }