Land #5895, rework of ADSI modules
commit
ff6fbfa738
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue