Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/mssql/client_mixin.rb
Views: 11704
module Rex1module Proto2module MSSQL3# A base mixin of useful mssql methods for parsing structures etc4module ClientMixin5include Msf::Module::UI::Message6extend Forwardable7def_delegators :@framework_module, :print_prefix, :print_status, :print_error, :print_good, :print_warning, :print_line8# Encryption9ENCRYPT_OFF = 0x00 #Encryption is available but off.10ENCRYPT_ON = 0x01 #Encryption is available and on.11ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.12ENCRYPT_REQ = 0x03 #Encryption is required.1314# Packet Type15TYPE_SQL_BATCH = 1 # (Client) SQL command16TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused)17TYPE_RPC = 3 # (Client) RPC18TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters,19# Request Completion, Error and Info Messages, Attention Acknowledgement20TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention21TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data22TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager23TYPE_TDS7_LOGIN = 16 # (Client) Login24TYPE_SSPI_MESSAGE = 17 # (Client) Login25TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 72627# Status28STATUS_NORMAL = 0x0029STATUS_END_OF_MESSAGE = 0x0130STATUS_IGNORE_EVENT = 0x0231STATUS_RESETCONNECTION = 0x08 # TDS 7.1+32STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+3334# Mappings for ENVCHANGE types35# See the TDS Specification here: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/2b3eb7e5-d43d-4d1b-bf4d-76b9e3afc79136module ENVCHANGE37DATABASE = 138LANGUAGE = 239CHARACTER_SET = 340PACKET_SIZE = 441UNICODE_LOCAL_ID = 542UNICODE_COMPARISON_FLAGS = 643SQL_COLLATION = 744BEGIN_TRANSACTION = 845COMMIT_TRANSACTION = 946ROLLBACK_TRANSACTION = 1047ENLIST_DTC_TRANSACTION = 1148DEFECT_TRANSACTION = 1249REAL_TIME_LOG_SHIPPING = 1350PROMOTE_TRANSACTION = 1551TRANSACTION_MANAGER_ADDRESS = 1652TRANSACTION_ENDED = 1753COMPLETION_ACKNOWLEDGEMENT = 1854NAME_OF_USER_INSTANCE = 1955ROUTING_INFORMATION = 2056end5758def mssql_print_reply(info)59print_status("SQL Query: #{info[:sql]}")6061if info[:done] && info[:done][:rows].to_i > 062print_status("Row Count: #{info[:done][:rows]} (Status: #{info[:done][:status]} Command: #{info[:done][:cmd]})")63end6465if info[:errors] && !info[:errors].empty?66info[:errors].each do |err|67print_error(err)68end69end7071if info[:rows] && !info[:rows].empty?7273tbl = Rex::Text::Table.new(74'Indent' => 1,75'Header' => "Response",76'Columns' => info[:colnames],77'SortIndex' => -178)7980info[:rows].each do |row|81tbl << row.map{ |x| x.nil? ? 'nil' : x }82end8384print_line(tbl.to_s)85end86end8788def mssql_prelogin_packet89pkt = ""90pkt_hdr = ""91pkt_data_token = ""92pkt_data = ""939495pkt_hdr = [96TYPE_PRE_LOGIN_MESSAGE, #type97STATUS_END_OF_MESSAGE, #status980x0000, #length990x0000, # SPID1000x00, # PacketID1010x00 #Window102]103104version = [0x55010008, 0x0000].pack("Vv")105106# if manually set, we will honour107if tdsencryption == true108encryption = ENCRYPT_ON109else110encryption = ENCRYPT_NOT_SUP111end112113instoptdata = "MSSQLServer\0"114115threadid = "\0\0" + Rex::Text.rand_text(2)116117idx = 21 # size of pkt_data_token118pkt_data_token << [1190x00, # Token 0 type Version120idx , # VersionOffset121version.length, # VersionLength1221230x01, # Token 1 type Encryption124idx = idx + version.length, # EncryptionOffset1250x01, # EncryptionLength1261270x02, # Token 2 type InstOpt128idx = idx + 1, # InstOptOffset129instoptdata.length, # InstOptLength1301310x03, # Token 3 type Threadid132idx + instoptdata.length, # ThreadIdOffset1330x04, # ThreadIdLength1341350xFF136].pack('CnnCnnCnnCnnC')137138pkt_data << pkt_data_token139pkt_data << version140pkt_data << encryption141pkt_data << instoptdata142pkt_data << threadid143144pkt_hdr[2] = pkt_data.length + 8145146pkt = pkt_hdr.pack('CCnnCC') + pkt_data147pkt148end149150def parse_prelogin_response(resp)151data = {}152if resp.length > 5 # minimum size for response specification153version_index = resp.slice(1, 2).unpack('n')[0]154155major = resp.slice(version_index, 1).unpack('C')[0]156minor = resp.slice(version_index+1, 1).unpack('C')[0]157build = resp.slice(version_index+2, 2).unpack('n')[0]158159enc_index = resp.slice(6, 2).unpack('n')[0]160data[:encryption] = resp.slice(enc_index, 1).unpack('C')[0]161end162163if major && minor && build164data[:version] = "#{major}.#{minor}.#{build}"165end166167return data168end169170def mssql_send_recv(req, timeout=15, check_status = true)171sock.put(req)172173# Read the 8 byte header to get the length and status174# Read the length to get the data175# If the status is 0, read another header and more data176177done = false178resp = ""179180while(not done)181head = sock.get_once(8, timeout)182if !(head && head.length == 8)183return false184end185186# Is this the last buffer?187if head[1, 1] == "\x01" || !check_status188done = true189end190191# Grab this block's length192rlen = head[2, 2].unpack('n')[0] - 8193194while(rlen > 0)195buff = sock.get_once(rlen, timeout)196return if not buff197resp << buff198rlen -= buff.length199end200end201202resp203end204205#206# Encrypt a password according to the TDS protocol (encode)207#208def mssql_tds_encrypt(pass)209# Convert to unicode, swap 4 bits both ways, xor with 0xa5210Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")211end212213def mssql_xpcmdshell(cmd, doprint=false, opts={})214force_enable = false215begin216res = query("EXEC master..xp_cmdshell '#{cmd}'", false, opts)217if res[:errors] && !res[:errors].empty?218if res[:errors].join =~ /xp_cmdshell/219if force_enable220print_error("The xp_cmdshell procedure is not available and could not be enabled")221raise RuntimeError, "Failed to execute command"222else223print_status("The server may have xp_cmdshell disabled, trying to enable it...")224query(mssql_xpcmdshell_enable())225raise RuntimeError, "xp_cmdshell disabled"226end227end228end229230mssql_print_reply(res) if doprint231232return res233234rescue RuntimeError => e235if e.to_s =~ /xp_cmdshell disabled/236force_enable = true237retry238end239raise e240end241end242#243# Parse a raw TDS reply from the server244#245def mssql_parse_tds_reply(data, info)246info[:errors] ||= []247info[:colinfos] ||= []248info[:colnames] ||= []249250# Parse out the columns251cols = data.slice!(0, 2).unpack('v')[0]2520.upto(cols-1) do |col_idx|253col = {}254info[:colinfos][col_idx] = col255256col[:utype] = data.slice!(0, 2).unpack('v')[0]257col[:flags] = data.slice!(0, 2).unpack('v')[0]258col[:type] = data.slice!(0, 1).unpack('C')[0]259case col[:type]260when 48261col[:id] = :tinyint262263when 52264col[:id] = :smallint265266when 56267col[:id] = :rawint268269when 61270col[:id] = :datetime271272when 34273col[:id] = :image274col[:max_size] = data.slice!(0, 4).unpack('V')[0]275col[:value_length] = data.slice!(0, 2).unpack('v')[0]276col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '')277278when 109279col[:id] = :float280col[:value_length] = data.slice!(0, 1).unpack('C')[0]281282when 108283col[:id] = :numeric284col[:value_length] = data.slice!(0, 1).unpack('C')[0]285col[:precision] = data.slice!(0, 1).unpack('C')[0]286col[:scale] = data.slice!(0, 1).unpack('C')[0]287288when 60289col[:id] = :money290291when 110292col[:value_length] = data.slice!(0, 1).unpack('C')[0]293case col[:value_length]294when 8295col[:id] = :money296when 4297col[:id] = :smallmoney298else299col[:id] = :unknown300end301302when 111303col[:value_length] = data.slice!(0, 1).unpack('C')[0]304case col[:value_length]305when 4306col[:id] = :smalldatetime307when 8308col[:id] = :datetime309else310col[:id] = :unknown311end312313when 122314col[:id] = :smallmoney315316when 59317col[:id] = :float318319when 58320col[:id] = :smalldatetime321322when 36323col[:id] = :guid324col[:value_length] = data.slice!(0, 1).unpack('C')[0]325326when 38327col[:id] = :int328col[:int_size] = data.slice!(0, 1).unpack('C')[0]329330when 50331col[:id] = :bit332333when 99334col[:id] = :ntext335col[:max_size] = data.slice!(0, 4).unpack('V')[0]336col[:codepage] = data.slice!(0, 2).unpack('v')[0]337col[:cflags] = data.slice!(0, 2).unpack('v')[0]338col[:charset_id] = data.slice!(0, 1).unpack('C')[0]339col[:namelen] = data.slice!(0, 1).unpack('C')[0]340col[:table_name] = data.slice!(0, (col[:namelen] * 2) + 1).gsub("\x00", '')341342when 104343col[:id] = :bitn344col[:int_size] = data.slice!(0, 1).unpack('C')[0]345346when 127347col[:id] = :bigint348349when 165350col[:id] = :hex351col[:max_size] = data.slice!(0, 2).unpack('v')[0]352353when 173354col[:id] = :hex # binary(2)355col[:max_size] = data.slice!(0, 2).unpack('v')[0]356357when 231, 175, 167, 239358col[:id] = :string359col[:max_size] = data.slice!(0, 2).unpack('v')[0]360col[:codepage] = data.slice!(0, 2).unpack('v')[0]361col[:cflags] = data.slice!(0, 2).unpack('v')[0]362col[:charset_id] = data.slice!(0, 1).unpack('C')[0]363364else365col[:id] = :unknown366367# See https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/ce3183a6-9d89-47e8-a02f-de5a1a1303de for details about column types368info[:errors] << "Unsupported column type: #{col[:type]}. "369return info370end371372col[:msg_len] = data.slice!(0, 1).unpack('C')[0]373374if col[:msg_len] && col[:msg_len] > 0375col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '')376end377info[:colnames] << (col[:name] || 'NULL')378end379end380381#382# Parse individual tokens from a TDS reply383#384def mssql_parse_reply(data, info)385info[:errors] = []386return if not data387states = []388until data.empty? || info[:errors].any?389token = data.slice!(0, 1).unpack('C')[0]390case token391when 0x81392states << :mssql_parse_tds_reply393mssql_parse_tds_reply(data, info)394when 0xd1395states << :mssql_parse_tds_row396mssql_parse_tds_row(data, info)397when 0xe3398states << :mssql_parse_env399mssql_parse_env(data, info)400when 0x79401states << :mssql_parse_ret402mssql_parse_ret(data, info)403when 0xfd, 0xfe, 0xff404states << :mssql_parse_done405mssql_parse_done(data, info)406when 0xad407states << :mssql_parse_login_ack408mssql_parse_login_ack(data, info)409when 0xab410states << :mssql_parse_info411mssql_parse_info(data, info)412when 0xaa413states << :mssql_parse_error414mssql_parse_error(data, info)415when nil416break417else418info[:errors] << "unsupported token: #{token}. Previous states: #{states}"419break420end421end422info423end424425#426# Parse a single row of a TDS reply427#428def mssql_parse_tds_row(data, info)429info[:rows] ||= []430row = []431432info[:colinfos].each do |col|433434if(data.length == 0)435row << "<EMPTY>"436next437end438439case col[:id]440when :hex441str = ""442len = data.slice!(0, 2).unpack('v')[0]443if len > 0 && len < 65535444str << data.slice!(0, len)445end446row << str.unpack("H*")[0]447448when :guid449read_length = data.slice!(0, 1).unpack1('C')450if read_length == 0451row << nil452else453row << Rex::Text.to_guid(data.slice!(0, read_length))454end455456when :string457str = ""458len = data.slice!(0, 2).unpack('v')[0]459if len > 0 && len < 65535460str << data.slice!(0, len)461end462row << str.gsub("\x00", '')463464when :ntext465str = nil466ptrlen = data.slice!(0, 1).unpack("C")[0]467ptr = data.slice!(0, ptrlen)468unless ptrlen == 0469timestamp = data.slice!(0, 8)470datalen = data.slice!(0, 4).unpack("V")[0]471if datalen > 0 && datalen < 65535472str = data.slice!(0, datalen).gsub("\x00", '')473else474str = ''475end476end477row << str478479when :float480datalen = data.slice!(0, 1).unpack('C')[0]481case datalen482when 8483row << data.slice!(0, datalen).unpack('E')[0]484when 4485row << data.slice!(0, datalen).unpack('e')[0]486else487row << nil488end489490when :numeric491varlen = data.slice!(0, 1).unpack('C')[0]492if varlen == 0493row << nil494else495sign = data.slice!(0, 1).unpack('C')[0]496raw = data.slice!(0, varlen - 1)497value = ''498499case varlen500when 5501value = raw.unpack('L')[0]/(10**col[:scale]).to_f502when 9503value = raw.unpack('Q')[0]/(10**col[:scale]).to_f504when 13505chunks = raw.unpack('L3')506value = chunks[2] << 64 | chunks[1] << 32 | chunks[0]507value /= (10**col[:scale]).to_f508when 17509chunks = raw.unpack('L4')510value = chunks[3] << 96 | chunks[2] << 64 | chunks[1] << 32 | chunks[0]511value /= (10**col[:scale]).to_f512end513case sign514when 1515row << value516when 0517row << value * -1518end519end520521when :money522datalen = data.slice!(0, 1).unpack('C')[0]523if datalen == 0524row << nil525else526raw = data.slice!(0, datalen)527rev = raw.slice(4, 4) << raw.slice(0, 4)528row << rev.unpack('q')[0]/10000.0529end530531when :smallmoney532datalen = data.slice!(0, 1).unpack('C')[0]533if datalen == 0534row << nil535else536row << data.slice!(0, datalen).unpack('l')[0] / 10000.0537end538539when :smalldatetime540datalen = data.slice!(0, 1).unpack('C')[0]541if datalen == 0542row << nil543else544days = data.slice!(0, 2).unpack('S')[0]545minutes = data.slice!(0, 2).unpack('S')[0] / 1440.0546row << DateTime.new(1900, 1, 1) + days + minutes547end548549when :datetime550datalen = data.slice!(0, 1).unpack('C')[0]551if datalen == 0552row << nil553else554days = data.slice!(0, 4).unpack('l')[0]555minutes = data.slice!(0, 4).unpack('l')[0] / 1440.0556row << DateTime.new(1900, 1, 1) + days + minutes557end558559when :rawint560row << data.slice!(0, 4).unpack('V')[0]561562when :bigint563row << data.slice!(0, 8).unpack("H*")[0]564565when :smallint566row << data.slice!(0, 2).unpack("v")[0]567568when :smallint3569row << [data.slice!(0, 3)].pack("Z4").unpack("V")[0]570571when :tinyint572row << data.slice!(0, 1).unpack("C")[0]573574when :bitn575has_value = data.slice!(0, 1).unpack("C")[0]576if has_value == 0577row << nil578else579row << data.slice!(0, 1).unpack("C")[0]580end581582when :bit583row << data.slice!(0, 1).unpack("C")[0]584585when :image586str = ''587len = data.slice!(0, 1).unpack('C')[0]588str = data.slice!(0, len) if len && len > 0589row << str.unpack("H*")[0]590591when :int592len = data.slice!(0, 1).unpack("C")[0]593raw = data.slice!(0, len) if len && len > 0594595case len596when 0, 255597row << ''598when 1599row << raw.unpack("C")[0]600when 2601row << raw.unpack('v')[0]602when 4603row << raw.unpack('V')[0]604when 5605row << raw.unpack('V')[0] # XXX: missing high byte606when 8607row << raw.unpack('VV')[0] # XXX: missing high dword608else609info[:errors] << "invalid integer size: #{len} #{data[0, 16].unpack("H*")[0]}"610end611else612info[:errors] << "unknown column type: #{col.inspect}"613end614end615616info[:rows] << row617info618end619620#621# Parse a "ret" TDS token622#623def mssql_parse_ret(data, info)624ret = data.slice!(0, 4).unpack('N')[0]625info[:ret] = ret626info627end628629#630# Parse a "done" TDS token631#632def mssql_parse_done(data, info)633status, cmd, rows = data.slice!(0, 8).unpack('vvV')634info[:done] = { :status => status, :cmd => cmd, :rows => rows }635info636end637638#639# Parse an "error" TDS token640#641def mssql_parse_error(data, info)642len = data.slice!(0, 2).unpack('v')[0]643buff = data.slice!(0, len)644645errno, state, sev, elen = buff.slice!(0, 8).unpack('VCCv')646emsg = buff.slice!(0, elen * 2)647emsg.gsub!("\x00", '')648649info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"650info651end652653#654# Parse an "environment change" TDS token655#656def mssql_parse_env(data, info)657len = data.slice!(0, 2).unpack('v')[0]658buff = data.slice!(0, len)659type = buff.slice!(0, 1).unpack('C')[0]660661nval = ''662nlen = buff.slice!(0, 1).unpack('C')[0] || 0663nval = buff.slice!(0, nlen * 2).gsub("\x00", '') if nlen > 0664665oval = ''666olen = buff.slice!(0, 1).unpack('C')[0] || 0667oval = buff.slice!(0, olen * 2).gsub("\x00", '') if olen > 0668669info[:envs] ||= []670info[:envs] << { :type => type, :old => oval, :new => nval }671672self.current_database = nval if type == ENVCHANGE::DATABASE673674info675end676677#678# Parse an "information" TDS token679#680def mssql_parse_info(data, info)681len = data.slice!(0, 2).unpack('v')[0]682buff = data.slice!(0, len)683684errno, state, sev, elen = buff.slice!(0, 8).unpack('VCCv')685emsg = buff.slice!(0, elen * 2)686emsg.gsub!("\x00", '')687688info[:infos] ||= []689info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"690info691end692693#694# Parse a "login ack" TDS token695#696def mssql_parse_login_ack(data, info)697len = data.slice!(0, 2).unpack('v')[0]698_buff = data.slice!(0, len)699info[:login_ack] = true700end701702end703end704end705end706707708