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.

adsi.rb 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # -*- coding: binary -*-
  2. require 'rex/post/meterpreter'
  3. module Rex
  4. module Post
  5. module Meterpreter
  6. module Ui
  7. ###
  8. #
  9. # Extended API ADSI management user interface.
  10. #
  11. ###
  12. class Console::CommandDispatcher::Extapi::Adsi
  13. Klass = Console::CommandDispatcher::Extapi::Adsi
  14. include Console::CommandDispatcher
  15. # Zero indicates "no limit"
  16. DEFAULT_MAX_RESULTS = 0
  17. DEFAULT_PAGE_SIZE = 0
  18. #
  19. # List of supported commands.
  20. #
  21. def commands
  22. {
  23. 'adsi_user_enum' => 'Enumerate all users on the specified domain.',
  24. 'adsi_group_enum' => 'Enumerate all groups on the specified domain.',
  25. 'adsi_nested_group_user_enum' => 'Recursively enumerate users who are effectively members of the group specified.',
  26. 'adsi_computer_enum' => 'Enumerate all computers on the specified domain.',
  27. 'adsi_dc_enum' => 'Enumerate all domain controllers on the specified domain.',
  28. 'adsi_domain_query' => 'Enumerate all objects on the specified domain that match a filter.'
  29. }
  30. end
  31. #
  32. # Name for this dispatcher
  33. #
  34. def name
  35. 'Extapi: ADSI Management'
  36. end
  37. #
  38. # Options for the adsi_nested_group_user_enum command.
  39. #
  40. @@adsi_nested_group_user_enum_opts = Rex::Parser::Arguments.new(
  41. '-h' => [false, 'Help banner'],
  42. '-o' => [true, 'Path to output file.'],
  43. '-m' => [true, 'Maximum results to return.'],
  44. '-p' => [true, 'Result set page size.']
  45. )
  46. def adsi_nested_group_user_enum_usage
  47. print_line('USAGE:')
  48. print_line(' adsi_nested_group_user_enum <domain> <Group DN> [-h] [-m maxresults] [-p pagesize] [-o file]')
  49. print_line
  50. print_line('DESCRIPTION:')
  51. print_line(' Enumerate the users who are members of the named group, taking nested groups into account.')
  52. print_line(' For example, specifying the "Domain Admins" group DN will list all users who are effectively')
  53. print_line(' members of the Domain Admins group, even if they are in practice members of intermediary groups.')
  54. print_line
  55. print_line('EXAMPLE:')
  56. print_line(' The example below will list all members of the "Domain Admins" group on the STUFUS domain:')
  57. print_line(' adsi_nested_group_user_enum STUFUS "CN=Domain Admins,CN=Users,DC=mwrinfosecurity,DC=com"')
  58. print_line(@@adsi_nested_group_user_enum_opts.usage)
  59. end
  60. #
  61. # Enumerate domain groups.
  62. #
  63. def cmd_adsi_nested_group_user_enum(*args)
  64. args.unshift('-h') if args.length == 0
  65. if args.include?('-h') || args.length < 2
  66. adsi_nested_group_user_enum_usage
  67. return true
  68. end
  69. domain = args.shift
  70. groupdn = args.shift
  71. # This OID (canonical name = LDAP_MATCHING_RULE_IN_CHAIN) will recursively search each 'memberof' parent
  72. # https://support.microsoft.com/en-us/kb/275523 for more information -stufus
  73. filter = "(&(objectClass=user)(memberof:1.2.840.113556.1.4.1941:=#{groupdn}))"
  74. fields = ['samaccountname', 'name', 'distinguishedname', 'description', 'comment']
  75. args = [domain, filter] + fields + args
  76. return cmd_adsi_domain_query(*args)
  77. end
  78. #
  79. # Options for the adsi_user_enum command.
  80. #
  81. @@adsi_user_enum_opts = Rex::Parser::Arguments.new(
  82. '-h' => [false, 'Help banner.'],
  83. '-o' => [true, 'Path to output file.'],
  84. '-m' => [true, 'Maximum results to return.'],
  85. '-p' => [true, 'Result set page size.']
  86. )
  87. def adsi_user_enum_usage
  88. print_line('USAGE:')
  89. print_line(' adsi_user_enum <domain> [-h] [-m maxresults] [-p pagesize] [-o file]')
  90. print_line
  91. print_line('DESCRIPTION:')
  92. print_line(' Enumerate all users on the target domain.')
  93. print_line(' Enumeration returns information such as the user name, SAM account name, status, comments etc')
  94. print_line(@@adsi_user_enum_opts.usage)
  95. end
  96. #
  97. # Enumerate domain users.
  98. #
  99. def cmd_adsi_user_enum(*args)
  100. args.unshift('-h') if args.length == 0
  101. if args.include?('-h')
  102. adsi_user_enum_usage
  103. return true
  104. end
  105. domain = args.shift
  106. filter = '(objectClass=user)'
  107. fields = ['samaccountname', 'name', 'distinguishedname', 'description', 'comment']
  108. args = [domain, filter] + fields + args
  109. return cmd_adsi_domain_query(*args)
  110. end
  111. #
  112. # Options for the adsi_group_enum command.
  113. #
  114. @@adsi_group_enum_opts = Rex::Parser::Arguments.new(
  115. '-h' => [false, 'Help banner.'],
  116. '-o' => [true, 'Path to output file.'],
  117. '-m' => [true, 'Maximum results to return.'],
  118. '-p' => [true, 'Result set page size.']
  119. )
  120. def adsi_group_enum_usage
  121. print_line('USAGE:')
  122. print_line(' adsi_nested_group_user_enum <domain> [-h] [-m maxresults] [-p pagesize] [-o file]')
  123. print_line
  124. print_line('DESCRIPTION:')
  125. print_line(' Enumerate all groups on the target domain.')
  126. print_line
  127. print_line('EXAMPLE:')
  128. print_line(' The example below will list all groups on the STUFUS domain.')
  129. print_line(' adsi_group_enum STUFUS')
  130. print_line(@@adsi_group_enum_opts.usage)
  131. end
  132. #
  133. # Enumerate domain groups.
  134. #
  135. def cmd_adsi_group_enum(*args)
  136. args.unshift('-h') if args.length == 0
  137. if args.include?('-h')
  138. adsi_group_enum_usage
  139. return true
  140. end
  141. domain = args.shift
  142. filter = '(objectClass=group)'
  143. fields = ['name', 'distinguishedname', 'description',]
  144. args = [domain, filter] + fields + args
  145. return cmd_adsi_domain_query(*args)
  146. end
  147. #
  148. # Options for the adsi_computer_enum command.
  149. #
  150. @@adsi_computer_enum_opts = Rex::Parser::Arguments.new(
  151. '-h' => [false, 'Help banner.'],
  152. '-o' => [true, 'Path to output file.'],
  153. '-m' => [true, 'Maximum results to return.'],
  154. '-p' => [true, 'Result set page size.']
  155. )
  156. def adsi_computer_enum_usage
  157. print_line('USAGE:')
  158. print_line(' adsi_computer_enum <domain> [-h] [-m maxresults] [-p pagesize] [-o file]')
  159. print_line
  160. print_line('DESCRIPTION:')
  161. print_line(' Enumerate all computers on the target domain.')
  162. print_line(@@adsi_computer_enum_opts.usage)
  163. end
  164. #
  165. # Enumerate domain computers.
  166. #
  167. def cmd_adsi_computer_enum(*args)
  168. args.unshift('-h') if args.length == 0
  169. if args.include?('-h')
  170. adsi_computer_enum_usage
  171. return true
  172. end
  173. domain = args.shift
  174. filter = '(objectClass=computer)'
  175. fields = ['name', 'dnshostname', 'distinguishedname', 'operatingsystem',
  176. 'operatingsystemversion', 'operatingsystemservicepack', 'description',
  177. 'comment' ]
  178. args = [domain, filter] + fields + args
  179. return cmd_adsi_domain_query(*args)
  180. end
  181. #
  182. # Options for the adsi_dc_enum command.
  183. #
  184. @@adsi_dc_enum_opts = Rex::Parser::Arguments.new(
  185. '-h' => [false, 'Help banner.'],
  186. '-o' => [true, 'Path to output file.'],
  187. '-m' => [true, 'Maximum results to return.'],
  188. '-p' => [true, 'Result set page size.']
  189. )
  190. def adsi_dc_enum_usage
  191. print_line('USAGE:')
  192. print_line(' adsi_dc_enum <domain> [-h] [-m maxresults] [-p pagesize] [-o file]')
  193. print_line
  194. print_line('DESCRIPTION:')
  195. print_line(' Enumerate the domain controllers on the target domain.')
  196. print_line(@@adsi_dc_enum_opts.usage)
  197. end
  198. #
  199. # Enumerate domain dcs.
  200. #
  201. def cmd_adsi_dc_enum(*args)
  202. args.unshift('-h') if args.length == 0
  203. if args.include?('-h')
  204. adsi_dc_enum_usage
  205. return true
  206. end
  207. domain = args.shift
  208. # This LDAP filter will pull out domain controllers
  209. filter = '(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))'
  210. fields = ['name', 'dnshostname', 'distinguishedname', 'operatingsystem',
  211. 'operatingsystemversion', 'operatingsystemservicepack', 'description', 'comment' ]
  212. args = [domain, filter] + fields + args
  213. return cmd_adsi_domain_query(*args)
  214. end
  215. #
  216. # Options for the adsi_domain_query command.
  217. #
  218. @@adsi_domain_query_opts = Rex::Parser::Arguments.new(
  219. '-h' => [false, 'Help banner.'],
  220. '-o' => [true, 'Path to output file.'],
  221. '-m' => [true, 'Maximum results to return.'],
  222. '-p' => [true, 'Result set page size.']
  223. )
  224. def adsi_domain_query_usage
  225. print_line('USAGE:')
  226. print_line(' adsi_domain_query <domain> <filter> <field 1> [field 2 [field ..]] [-h] [-m maxresults] [-p pagesize] [-o file]')
  227. print_line
  228. print_line('DESCRIPTION:')
  229. print_line(' Enumerates the objects on the target domain, returning the set of fields that are specified.')
  230. print_line(@@adsi_domain_query_opts.usage)
  231. end
  232. #
  233. # Enumerate domain objects.
  234. #
  235. def cmd_adsi_domain_query(*args)
  236. page_size = DEFAULT_PAGE_SIZE
  237. max_results = DEFAULT_MAX_RESULTS
  238. args.unshift('-h') if args.length < 3
  239. output_file = nil
  240. @@adsi_domain_query_opts.parse(args) { |opt, idx, val|
  241. case opt
  242. when '-p'
  243. page_size = val.to_i
  244. when '-o'
  245. output_file = val
  246. when '-m'
  247. max_results = val.to_i
  248. when '-h'
  249. adsi_domain_query_usage
  250. return true
  251. end
  252. }
  253. # Assume that the flags are passed in at the end. Safe?
  254. switch_index = args.index { |a| a.start_with?('-') }
  255. if switch_index
  256. args = args.first(switch_index)
  257. end
  258. domain = args.shift
  259. filter = args.shift
  260. objects = client.extapi.adsi.domain_query(domain, filter, max_results, page_size, args)
  261. table = Rex::Text::Table.new(
  262. 'Header' => "#{domain} Objects",
  263. 'Indent' => 0,
  264. 'SortIndex' => 0,
  265. 'Columns' => objects[:fields]
  266. )
  267. objects[:results].each do |c|
  268. table << to_table_row(c)
  269. end
  270. print_line
  271. print_line(table.to_s)
  272. print_line("Total objects: #{objects[:results].length}")
  273. print_line
  274. if output_file
  275. ::File.open(output_file, 'w') do |f|
  276. f.write("#{table.to_s}\n")
  277. f.write("\nTotal objects: #{objects[:results].length}\n")
  278. end
  279. end
  280. return true
  281. end
  282. protected
  283. #
  284. # Convert an ADSI result row to a table row so that it can
  285. # be rendered to screen appropriately.
  286. #
  287. # @param result [Array[Hash]] Array of type/value pairs.
  288. #
  289. # @return [Array[String]] Renderable view of the value.
  290. #
  291. def to_table_row(result)
  292. values = []
  293. result.each do |v|
  294. case v[:type]
  295. when :string, :number, :bool
  296. values << v[:value].to_s
  297. when :raw
  298. # for UI level stuff, rendering raw as hex is really the only option
  299. values << Rex::Text.to_hex(v[:value], '')
  300. when :array
  301. val = "#{to_table_row(v[:value]).join(', ')}"
  302. # we'll truncate the output of the array because it could be excessive if we
  303. # don't. Users who want the detail of this stuff should probably script it.
  304. if val.length > 50
  305. val = "<#{val[0,50]}..."
  306. end
  307. values << val
  308. when :dn
  309. values << "#{value[:label]}: #{value[:string] || Rex::Text.to_hex(value[:raw], '')}"
  310. when :path
  311. values << "Vol: #{v[:volume]}, Path: #{v[:path]}, Type: #{v[:vol_type]}"
  312. when :unknown
  313. values << '(unknown)'
  314. end
  315. end
  316. values
  317. end
  318. end
  319. end
  320. end
  321. end
  322. end