Land #5895, rework of ADSI modules

bug/bundler_fix
HD Moore 2015-08-31 14:10:41 -07:00
commit ff6fbfa738
No known key found for this signature in database
GPG Key ID: 7549FB3DB1DD1F32
3 changed files with 271 additions and 20 deletions

View File

@ -27,7 +27,10 @@ class Console::CommandDispatcher::Extapi::Adsi
def commands
{
"adsi_user_enum" => "Enumerate all users on the specified domain.",
"adsi_group_enum" => "Enumerate all groups on the specified domain.",
"adsi_nested_group_user_enum" => "Recursively enumerate users who are effectively members of the group specified.",
"adsi_computer_enum" => "Enumerate all computers on the specified domain.",
"adsi_dc_enum" => "Enumerate all domain controllers on the specified domain.",
"adsi_domain_query" => "Enumerate all objects on the specified domain that match a filter."
}
end
@ -39,6 +42,56 @@ class Console::CommandDispatcher::Extapi::Adsi
"Extapi: ADSI Management"
end
#
# Options for the adsi_nested_group_user_enum command.
#
@@adsi_nested_group_user_enum_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner" ],
"-m" => [ true, "Maximum results to return." ],
"-p" => [ true, "Result set page size." ]
)
def adsi_nested_group_user_enum_usage
print_line("USAGE:")
print_line(" adsi_nested_group_user_enum <domain> <Group DN> [-h] [-m maxresults] [-p pagesize]")
print_line
print_line("DESCRIPTION:")
print_line(" Enumerate the users who are members of the named group, taking nested groups into account.")
print_line(" For example, specifying the 'Domain Admins' group DN will list all users who are effectively")
print_line(" members of the Domain Admins group, even if they are in practice members of intermediary groups.")
print_line
print_line("EXAMPLE:")
print_line(" The example below will list all members of the 'Domain Admins' group on the STUFUS domain:")
print_line(" adsi_nested_group_user_enum STUFUS \"CN=Domain Admins,CN=Users,DC=mwrinfosecurity,DC=com\"")
print_line(@@adsi_nested_group_user_enum_opts.usage)
end
#
# Enumerate domain groups.
#
def cmd_adsi_nested_group_user_enum(*args)
args.unshift("-h") if args.length == 0
if args.include?("-h") || args.length < 2
adsi_nested_group_user_enum_usage
return true
end
domain = args.shift
groupdn = args.shift
# This OID (canonical name = LDAP_MATCHING_RULE_IN_CHAIN) will recursively search each 'memberof' parent
# https://support.microsoft.com/en-us/kb/275523 for more information -stufus
filter = "(&(objectClass=user)(memberof:1.2.840.113556.1.4.1941:=#{groupdn}))"
fields = [
"samaccountname",
"name",
"distinguishedname",
"description",
"comment"
]
args = [domain, filter] + fields + args
return cmd_adsi_domain_query(*args)
end
#
# Options for the adsi_user_enum command.
#
@ -49,12 +102,13 @@ class Console::CommandDispatcher::Extapi::Adsi
)
def adsi_user_enum_usage
print(
"\nUsage: adsi_user_enum <domain> [-h] [-m maxresults] [-p pagesize]\n\n" +
"Enumerate the users on the target domain.\n\n" +
"Enumeration returns information such as the user name, SAM account name, locked\n" +
"status, desc, and comment.\n" +
@@adsi_user_enum_opts.usage)
print_line("USAGE:")
print_line(" adsi_user_enum <domain> [-h] [-m maxresults] [-p pagesize]")
print_line
print_line("DESCRIPTION:")
print_line(" Enumerate all users on the target domain.")
print_line(" Enumeration returns information such as the user name, SAM account name, status, comments etc")
print_line(@@adsi_user_enum_opts.usage)
end
#
@ -80,6 +134,49 @@ class Console::CommandDispatcher::Extapi::Adsi
return cmd_adsi_domain_query(*args)
end
#
# Options for the adsi_group_enum command.
#
@@adsi_group_enum_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner" ],
"-m" => [ true, "Maximum results to return." ],
"-p" => [ true, "Result set page size." ]
)
def adsi_group_enum_usage
print_line("USAGE:")
print_line(" adsi_nested_group_user_enum <domain> [-h] [-m maxresults] [-p pagesize]")
print_line
print_line("DESCRIPTION:")
print_line(" Enumerate all groups on the target domain.")
print_line
print_line("EXAMPLE:")
print_line(" The example below will list all groups on the STUFUS domain.")
print_line(" adsi_group_enum STUFUS")
print_line(@@adsi_group_enum_opts.usage)
end
#
# Enumerate domain groups.
#
def cmd_adsi_group_enum(*args)
args.unshift("-h") if args.length == 0
if args.include?("-h")
adsi_group_enum_usage
return true
end
domain = args.shift
filter = "(objectClass=group)"
fields = [
"name",
"distinguishedname",
"description",
]
args = [domain, filter] + fields + args
return cmd_adsi_domain_query(*args)
end
#
# Options for the adsi_computer_enum command.
#
@ -90,11 +187,12 @@ class Console::CommandDispatcher::Extapi::Adsi
)
def adsi_computer_enum_usage
print(
"\nUsage: adsi_computer_enum <domain> [-h] [-m maxresults] [-p pagesize]\n\n" +
"Enumerate the computers on the target domain.\n\n" +
"Enumeration returns information such as the computer name, desc, and comment.\n" +
@@adsi_computer_enum_opts.usage)
print_line("USAGE:")
print_line(" adsi_computer_enum <domain> [-h] [-m maxresults] [-p pagesize]")
print_line
print_line("DESCRIPTION:")
print_line(" Enumerate all computers on the target domain.")
print_line(@@adsi_computer_enum_opts.usage)
end
#
@ -111,7 +209,56 @@ class Console::CommandDispatcher::Extapi::Adsi
filter = "(objectClass=computer)"
fields = [
"name",
"dnshostname",
"distinguishedname",
"operatingsystem",
"operatingsystemversion",
"operatingsystemservicepack",
"description",
"comment"
]
args = [domain, filter] + fields + args
return cmd_adsi_domain_query(*args)
end
#
# Options for the adsi_dc_enum command.
#
@@adsi_dc_enum_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner" ],
"-m" => [ true, "Maximum results to return." ],
"-p" => [ true, "Result set page size." ]
)
def adsi_dc_enum_usage
print_line("USAGE:")
print_line(" adsi_dc_enum <domain> [-h] [-m maxresults] [-p pagesize]")
print_line
print_line("DESCRIPTION:")
print_line(" Enumerate the domain controllers on the target domain.")
print_line(@@adsi_dc_enum_opts.usage)
end
#
# Enumerate domain dcs.
#
def cmd_adsi_dc_enum(*args)
args.unshift("-h") if args.length == 0
if args.include?("-h")
adsi_dc_enum_usage
return true
end
domain = args.shift
# This LDAP filter will pull out domain controllers
filter = "(&(objectCategory=computer)(userAccountControl:1.2.840.113556.1.4.803:=8192))"
fields = [
"name",
"dnshostname",
"distinguishedname",
"operatingsystem",
"operatingsystemversion",
"operatingsystemservicepack",
"description",
"comment"
]
@ -129,11 +276,12 @@ class Console::CommandDispatcher::Extapi::Adsi
)
def adsi_domain_query_usage
print(
"\nUsage: adsi_domain_query <domain> <filter> <field 1> [field 2 [field ..]] [-h] [-m maxresults] [-p pagesize]\n\n" +
"Enumerate the objects on the target domain.\n\n" +
"Enumeration returns the set of fields that are specified.\n" +
@@adsi_domain_query_opts.usage)
print_line("USAGE:")
print_line(" adsi_domain_query <domain> <filter> <field 1> [field 2 [field ..]] [-h] [-m maxresults] [-p pagesize]")
print_line
print_line("DESCRIPTION:")
print_line(" Enumerates the objects on the target domain, returning the set of fields that are specified.")
print_line(@@adsi_domain_query_opts.usage)
end
#

View File

@ -0,0 +1,97 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rex'
require 'msf/core'
class Metasploit3 < Msf::Post
include Msf::Auxiliary::Report
include Msf::Post::Windows::LDAP
# include Msf::Post::Windows::Accounts
USER_FIELDS = ['name',
'distinguishedname',
'description'].freeze
def initialize(info = {})
super(update_info(
info,
'Name' => 'Windows Gather Active Directory Groups',
'Description' => %{
This module will enumerate AD groups on the specified domain.
},
'License' => MSF_LICENSE,
'Author' => [
'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'
],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ]
))
register_options([
OptString.new('ADDITIONAL_FIELDS', [false, 'Additional fields to retrieve, comma separated', nil]),
], self.class)
end
def run
@user_fields = USER_FIELDS.dup
if datastore['ADDITIONAL_FIELDS']
additional_fields = datastore['ADDITIONAL_FIELDS'].gsub(/\s+/,"").split(',')
@user_fields.push(*additional_fields)
end
max_search = datastore['MAX_SEARCH']
begin
q = query('(objectClass=group)', max_search, @user_fields)
rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e
# Can't bind or in a network w/ limited accounts
print_error(e.message)
return
end
if q.nil? || q[:results].empty?
print_status('No results returned.')
else
results_table = parse_results(q[:results])
print_line results_table.to_s
end
end
# Takes the results of LDAP query, parses them into a table
# and records and usernames as {Metasploit::Credential::Core}s in
# the database.
#
# @param [Array<Array<Hash>>] the LDAP query results to parse
# @return [Rex::Ui::Text::Table] the table containing all the result data
def parse_results(results)
domain = datastore['DOMAIN'] || get_domain
domain_ip = client.net.resolve.resolve_host(domain)[:ip]
# Results table holds raw string data
results_table = Rex::Ui::Text::Table.new(
'Header' => "Domain Groups",
'Indent' => 1,
'SortIndex' => -1,
'Columns' => @user_fields
)
results.each do |result|
row = []
result.each do |field|
if field.nil?
row << ""
else
row << field[:value]
end
end
results_table << row
end
results_table
end
end

View File

@ -13,6 +13,7 @@ class Metasploit3 < Msf::Post
UAC_DISABLED = 0x02
USER_FIELDS = ['sAMAccountName',
'name',
'userPrincipalName',
'userAccountControl',
'lockoutTime',
@ -26,12 +27,16 @@ class Metasploit3 < Msf::Post
'Name' => 'Windows Gather Active Directory Users',
'Description' => %{
This module will enumerate user accounts in the default Active Domain (AD) directory and stores
them in the database.
them in the database. If GROUP_MEMBER is set to the DN of a group, this will list the members of
that group by performing a recursive/nested search (i.e. it will list users who are members of
groups that are members of groups that are members of groups (etc) which eventually include the
target group DN.
},
'License' => MSF_LICENSE,
'Author' => [
'Ben Campbell',
'Carlos Perez <carlos_perez[at]darkoperator.com>'
'Carlos Perez <carlos_perez[at]darkoperator.com>',
'Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'
],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ]
@ -42,6 +47,7 @@ class Metasploit3 < Msf::Post
OptBool.new('EXCLUDE_LOCKED', [true, 'Exclude in search locked accounts..', false]),
OptBool.new('EXCLUDE_DISABLED', [true, 'Exclude from search disabled accounts.', false]),
OptString.new('ADDITIONAL_FIELDS', [false, 'Additional fields to retrieve, comma separated', nil]),
OptString.new('GROUP_MEMBER', [false, 'Recursively list users that are effectve members of the group DN specified.', nil]),
OptEnum.new('UAC', [true, 'Filter on User Account Control Setting.', 'ANY',
[
'ANY',
@ -58,7 +64,7 @@ class Metasploit3 < Msf::Post
@user_fields = USER_FIELDS.dup
if datastore['ADDITIONAL_FIELDS']
additional_fields = datastore['ADDITIONAL_FIELDS'].gsub(/\s+/,"").split(',')
additional_fields = datastore['ADDITIONAL_FIELDS'].gsub(/\s+/, "").split(',')
@user_fields.push(*additional_fields)
end
@ -131,7 +137,6 @@ class Metasploit3 < Msf::Post
results_table
end
# Builds the LDAP query 'filter' used to find our User Accounts based on
# criteria set by user in the Datastore.
#
@ -140,6 +145,7 @@ class Metasploit3 < Msf::Post
inner_filter = '(objectCategory=person)(objectClass=user)'
inner_filter << '(!(lockoutTime>=1))' if datastore['EXCLUDE_LOCKED']
inner_filter << '(!(userAccountControl:1.2.840.113556.1.4.803:=2))' if datastore['EXCLUDE_DISABLED']
inner_filter << "(memberof:1.2.840.113556.1.4.1941:=#{datastore['GROUP_MEMBER']})" if datastore['GROUP_MEMBER']
case datastore['UAC']
when 'ANY'
when 'NO_PASSWORD'