require 'msf/core' module Msf ### # # This module exposes methods for querying a remote MSSQL service # ### module Exploit::Remote::MSSQL include Exploit::Remote::Udp include Exploit::Remote::Tcp # # Creates an instance of a MSSQL exploit module. # def initialize(info = {}) super # Register the options that all MSSQL exploits may make use of. register_options( [ Opt::RHOST, Opt::RPORT(1433), OptString.new('MSSQL_USER', [ false, 'The username to authenticate as', 'sa']), OptString.new('MSSQL_PASS', [ false, 'The password for the specified username', '']) ], Msf::Exploit::Remote::MSSQL) end # # This method sends a UDP query packet to the server and # parses out the reply packet into a hash # def mssql_ping(timeout=5) data = { } ping_sock = Rex::Socket::Udp.create( 'PeerHost' => rhost, 'PeerPort' => 1434, 'Context' => { 'Msf' => framework, 'MsfExploit' => self, }) ping_sock.put("\x02") resp, saddr, sport = ping_sock.recvfrom(65535, timeout) ping_sock.close return data if not resp return data if resp.length == 0 var = nil return mssql_ping_parse(resp) end # # Parse a 'ping' response and format as a hash # def mssql_ping_parse(data) res = {} var = nil idx = data.index('ServerName') return res if not idx data[idx, data.length-idx].split(';').each do |d| if (not var) var = d else if (var.length > 0) res[var] = d var = nil end end end return res end # # This method connects to the server over TCP and attempts # to authenticate with the supplied username and password # The global socket is used and left connected after auth # def mssql_login(user='sa', pass='') disconnect if self.sock connect p_hdr = "\x02\x00\x02\x00\x00\x00\x02\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00" p_pk2 = "\x30\x30\x30\x30\x30\x30\x61\x30\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x20\x18\x81\xb8\x2c\x08\x03"+ "\x01\x06\x0a\x09\x01\x01\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x73\x71\x75\x65\x6c\x64\x61"+ "\x20\x31\x2e\x30\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00" p_pk3 = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x04\x02\x00\x00\x4d\x53\x44"+ "\x42\x4c\x49\x42\x00\x00\x00\x07\x06\x00\x00"+ "\x00\x00\x0d\x11\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00" p_lang = "\x02\x01\x00\x47\x00\x00\x02\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"+ "\x00\x00\x00\x00\x00\x00\x30\x30\x30\x00\x00"+ "\x00\x03\x00\x00\x00" user = user.slice(0, 29) pass = pass.slice(0, 29) ulen = user.length.chr plen = pass.length.chr user << ("\x00" * (30-user.length)) pass << ("\x00" * (30-pass.length)) p_login = p_hdr + user + ulen + pass + plen p_login << p_pk2 + plen + pass + p_pk3 sock.put(p_login) sock.put(p_lang) resp = sock.get_once if (resp and resp.length > 10 and resp[8,1].unpack('C')[0] == 0xe3) return true end return false end def mssql_login_datastore mssql_login(datastore['MSSQL_USER'], datastore['MSSQL_PASS']) end def mssql_query(sql, doprint=false, opts={}) info = { :sql => sql } opts[:timeout] ||= 15 pkt = "\x01\x01" + [sql.length + 8].pack('n') + [rand(0x100)].pack('n') + [rand(0x100)].pack('C') + "\x00" + sql sock.put(pkt) resp = sock.get(opts[:timeout]) mssql_parse_reply(resp, info) mssql_print_reply(info) if doprint info end def mssql_xpcmdshell(cmd, doprint=false, opts={}) mssql_query("xp_cmdshell '#{cmd}'", doprint, opts) end def mssql_parse_header(header) type, status, size, chan, pkt, window = header.unpack('CCnnCC') return [status, size, pkt] end def mssql_print_reply(info) print_status("SQL Query: #{info[:sql]}") if(info[:done] and info[:done][:rows].to_i > 0) print_status("Row Count: #{info[:done][:rows]} (Status: #{info[:done][:status]} Command: #{info[:done][:cmd]})") end if(info[:errors] and not info[:errors].empty?) info[:errors].each do |err| print_error(err) end end if(info[:rows] and not info[:rows].empty?) tbl = Rex::Ui::Text::Table.new( 'Indent' => 1, 'Header' => "", 'Columns' => info[:colnames] ) info[:rows].each do |row| tbl << row end print_line(tbl.to_s) end end def mssql_parse_reply(resp, info) info[:errors] = [] data = "" status = 0 while status == 0 break if not resp status, size, pkt = mssql_parse_header(resp.slice!(0,8)) break if not (status and size and pkt) data << resp.slice!(0,(size-8)) end until data.empty? token = data.slice!(0,1).unpack('C')[0] case token when 0xa0 mssql_parse_column_name(data, info) when 0xa1 mssql_parse_column_info(data, info) when 0xd1 mssql_parse_row(data, info) when 0x79 mssql_parse_ret(data, info) when 0xfd, 0xfe, 0xff mssql_parse_done(data, info) when 0xaa mssql_parse_error(data, info) when nil break else # info[:errors] << "unsupported token: #{token}" end end info end def mssql_parse_column_name(data, info) len = data.slice!(0,2).unpack('v')[0] str = data.slice!(0,len) info[:colnames] ||= [] while(not str.empty?) len = str.slice!(0,1).unpack('C')[0] col = len == 0 ? 'NULL' : str.slice!(0,len) info[:colnames] << col end info end def mssql_parse_column_info(data, info) len = data.slice!(0,2).unpack('v')[0] str = data.slice!(0,len) info[:colinfos] ||= [] idx = -1 while(not str.empty?) idx += 1 ### int if( str[0,5] == "\x07\x00\x20\x00\x38" or str[0,5] == "\x07\x00\x08\x00\x38" or # int false ) str.slice!(0,5) info[:colinfos] << [:int, 4] next end ### smallint if( str[0,5] == "\x0d\x00\x09\x00\x26" or str[0,5] == "\x0d\x00\x21\x00\x26" or false ) str.slice!(0,5) info[:colinfos] << [:smallint, str.slice!(0,1).unpack('C')[0]] next end ### smallint2 if( str[0,5] == "\x06\x00\x08\x00\x34" or false ) str.slice!(0,5) info[:colinfos] << [:smallint2, 2] next end ### image if( str[0,9] == "\x14\x00\x21\x00\x22\x00\x10\x00\x00" or false ) str.slice!(0,9) len = str.slice!(0,2).unpack('v')[0] nam = str.slice!(0,len) info[:colinfos] << [:image, 0, nam] next end ### tinyint if( str[0,5] == "\x05\x00\x08\x00\x30" or false ) str.slice!(0,5) info[:colinfos] << [:tinyint, 1] next end ### longint if( str[0,6] == "\x19\x00\x20\x00\x6c\x11" or str[0,6] == "\x19\x00\x21\x00\x6c\x11" or false ) str.slice!(0,6) info[:colinfos] << [:long, str.slice!(0,2).unpack("v")[0]] next end ### hex if(str[0,5] == "\x04\x00\x20\x00\x25") str.slice!(0,5) info[:colinfos] << [:hex, str.slice!(0,1).unpack('C')[0]] next end ### string if( str[0,5] == "\x02\x00\x21\x00\x27" or # varchar str[0,5] == "\x02\x00\x08\x00\x27" or str[0,5] == "\x02\x00\x01\x00\x27" or str[0,5] == "\x12\x00\x08\x00\x27" or str[0,5] == "\x12\x00\x09\x00\x27" or str[0,5] == "\x04\x00\x09\x00\x25" or # varbinary str[0,5] == "\x04\x00\x21\x00\x25" or str[0,5] == "\x04\x00\x09\x00\x25" or false ) str.slice!(0,5) info[:colinfos] << [:string, str.slice!(0,1).unpack('C')[0]] next end ### char(x) if( str[0,5] == "\x01\x00\x08\x00\x2f" or str[0,5] == "\x02\x00\x09\x00\x27" or false ) str.slice!(0,5) info[:colinfos] << [:string, str.slice!(0,1).unpack("C")[0]] next end ### datetime if( str[0,5] == "\x0c\x00\x08\x00\x3d" or false ) str.slice!(0,5) info[:colinfos] << [:datetime, 8] next end ### datetime2 if( str[0,6] == "\x0f\x00\x21\x00\x6f\x08" or false ) str.slice!(0,6) info[:colinfos] << [:datetime2, 8] next end ### binary if( str[0,6] == "\x04\x00\x21\x00\x25\x06" or false ) str.slice!(0,6) info[:colinfos] << [:binary] next end info[:errors] << "unknown column type: #{info[:colnames][idx]} == #{str.unpack("H*")[0]}" info[:colinfos] << [:unknown, 6] break end info end def mssql_parse_row(data, info) info[:rows] ||= [] row = [] info[:colinfos].each do |col| case col[0] when :int row << data.slice!(0,4).unpack('V')[0] when :smallint len = data.slice!(0,1).unpack('C')[0] raw = data.slice!(0,len) row << raw.unpack('v')[0] when :smallint2 row << data.slice!(0,2).unpack('v')[0] when :tinyint row << data.slice!(0,1).unpack('C')[0] when :long # XXX: completely made up bignum parsing... len = data.slice!(0,1).unpack('C')[0] raw = data.slice!(0,len) sum = 0 bit = raw.unpack("C*").reverse bit.each_index do |idx| base = (256 ** (bit.length - idx - 1)) if (base > 0) sum += base * bit[idx] else sum += bit[idx] end end row << "?#{raw.unpack("H*")[0]}" when :hex len = data.slice!(0,1).unpack('C')[0] row << ((len > 0) ? data.slice!(0,len) : '') when :string len = data.slice!(0,1).unpack('C')[0] row << ((len > 0) ? data.slice!(0,len) : '') when :datetime # XXX: convert to unix time row << data.slice!(0,8).unpack("H*")[0] when :datetime2 # XXX: convert to unix time len = data.slice!(0,1).unpack('C')[0] row << data.slice!(0,len).unpack("H*")[0] when :image len = data.slice!(0,1).unpack('C')[0] row << ((len > 0) ? data.slice!(0,len).unpack("H*")[0] : '') when :binary len = data.slice!(0,1).unpack('C')[0] row << ((len > 0) ? data.slice!(0,len).unpack("H*")[0] : '') when :unknown len = data.slice!(0,1).unpack('C')[0] row << "????-" + data.slice!(0,len) if len end end info[:rows] << row info end def mssql_parse_ret(data, info) ret = data.slice!(0,4).unpack('N')[0] info[:ret] = ret info end def mssql_parse_done(data, info) status, cmd, rows = data.slice!(0,8).unpack('vvV') info[:done] = { :status => status, :cmd => cmd, :rows => rows } info end def mssql_parse_error(data, info) len = data.slice!(0,2).unpack('v')[0] buff = data.slice!(0,len) errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') emsg = buff.slice!(0,elen) info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" info end end end