From ae09549057233764c8160fc8a13157a8c5be044c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 20 Dec 2015 20:17:06 +0000 Subject: [PATCH 1/9] New module, strating with managedby_groups --- .../gather/enum_ad_managedby_groups.rb | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 modules/post/windows/gather/enum_ad_managedby_groups.rb diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb new file mode 100644 index 0000000000..ade732c27a --- /dev/null +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -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 ' + ], + '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>] 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 From 89728fd8fe9949369ff8000f9bd1bb2beb3d74ed Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 20 Dec 2015 21:16:17 +0000 Subject: [PATCH 2/9] Working version --- .../gather/enum_ad_managedby_groups.rb | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index ade732c27a..8f30acee1f 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -9,18 +9,23 @@ require 'msf/core' class Metasploit3 < Msf::Post include Msf::Auxiliary::Report include Msf::Post::Windows::LDAP -# include Msf::Post::Windows::Accounts - USER_FIELDS = ['name', + USER_FIELDS = ['cn', 'distinguishedname', + 'managedBy', 'description'].freeze def initialize(info = {}) super(update_info( info, - 'Name' => 'Windows Gather Active Directory Groups', + 'Name' => 'Windows Gather Active Directory Managed Groups', 'Description' => %{ - This module will enumerate AD groups on the specified domain. + This module will enumerate AD groups on the specified domain which are managed by users. + It will also identify whether those groups have the 'Manager can update membership list' + option set; if so, it would allow that member to update the contents of that group. This + could either be used as a persistence mechanism (for example, set your user as the 'Domain + Admins' group manager) or could be used to detect privilege escalation opportunities + without having domain admin privileges. }, 'License' => MSF_LICENSE, 'Author' => [ @@ -31,7 +36,9 @@ class Metasploit3 < Msf::Post )) register_options([ - OptString.new('ADDITIONAL_FIELDS', [false, 'Additional fields to retrieve, comma separated', nil]), + OptString.new('ADDITIONAL_FIELDS', [false, 'Additional group fields to retrieve, comma separated.', nil]), + OptBool.new('RESOLVE_MANAGERS', [true, 'Query LDAP to get the account name of group managers.', true]), + OptBool.new('SECURITY_GROUPS_ONLY', [true, 'Only include security groups.', true]), ], self.class) end @@ -46,7 +53,11 @@ class Metasploit3 < Msf::Post max_search = datastore['MAX_SEARCH'] begin - q = query('(objectClass=group)', max_search, @user_fields) + qs = '(&(objectClass=group)(managedBy=*))' + if datastore['SECURITY_GROUPS_ONLY'] + qs = '(&(objectClass=group)(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' + end + q = query('(&(objectClass=group)(managedBy=*))', 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) @@ -56,23 +67,19 @@ class Metasploit3 < Msf::Post if q.nil? || q[:results].empty? print_status('No results returned.') else + if datastore['RESOLVE_MANAGERS'] + @user_fields << 'Manager Account Name' + end 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>] 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", + 'Header' => "Groups with Managers", 'Indent' => 1, 'SortIndex' => -1, 'Columns' => @user_fields @@ -88,6 +95,19 @@ class Metasploit3 < Msf::Post row << field[:value] end end + if datastore['RESOLVE_MANAGERS'] + begin + managedBy_cn = result[2][:value].split(',')[0] + m = query("(&(objectClass=user)(objectCategory=person)(#{managedBy_cn}))", 1, ['sAMAccountName']) + if !m.nil? && !m[:results].empty? + row << m[:results][0][0][:value] + else + row << "" + end + rescue + row << "" + end + end results_table << row end From c0a93433af637545201d0ec1247d23bead98ecc4 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 20 Dec 2015 21:16:42 +0000 Subject: [PATCH 3/9] msftidy --- modules/post/windows/gather/enum_ad_managedby_groups.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index 8f30acee1f..86034d7395 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Post 'Description' => %{ This module will enumerate AD groups on the specified domain which are managed by users. It will also identify whether those groups have the 'Manager can update membership list' - option set; if so, it would allow that member to update the contents of that group. This + option set; if so, it would allow that member to update the contents of that group. This could either be used as a persistence mechanism (for example, set your user as the 'Domain Admins' group manager) or could be used to detect privilege escalation opportunities without having domain admin privileges. @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Post if datastore['SECURITY_GROUPS_ONLY'] qs = '(&(objectClass=group)(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' end - q = query('(&(objectClass=group)(managedBy=*))', max_search, @user_fields) + q = query('(&(objectClass=group)(managedBy=*))', 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) From 07caaf352b71917b42d5f02fd17684500b642e1d Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 20 Dec 2015 21:18:21 +0000 Subject: [PATCH 4/9] made comment match purpose --- modules/post/windows/gather/enum_ad_managedby_groups.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index 86034d7395..474ead866a 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -20,8 +20,8 @@ class Metasploit3 < Msf::Post info, 'Name' => 'Windows Gather Active Directory Managed Groups', 'Description' => %{ - This module will enumerate AD groups on the specified domain which are managed by users. - It will also identify whether those groups have the 'Manager can update membership list' + This module will enumerate AD groups on the specified domain which are specifically managed. + It cannot at the moment identify whether the 'Manager can update membership list' option option set; if so, it would allow that member to update the contents of that group. This could either be used as a persistence mechanism (for example, set your user as the 'Domain Admins' group manager) or could be used to detect privilege escalation opportunities From c394caad27b45f6ebb99e1ce87f48784d656236b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 20 Dec 2015 21:19:24 +0000 Subject: [PATCH 5/9] actually made the securitygroups only option do something --- .../gather/enum_ad_managedby_groups.rb | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index 474ead866a..920d898f25 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Post register_options([ OptString.new('ADDITIONAL_FIELDS', [false, 'Additional group fields to retrieve, comma separated.', nil]), OptBool.new('RESOLVE_MANAGERS', [true, 'Query LDAP to get the account name of group managers.', true]), - OptBool.new('SECURITY_GROUPS_ONLY', [true, 'Only include security groups.', true]), + OptBool.new('SECURITY_GROUPS_ONLY', [true, 'Only include security groups.', true]) ], self.class) end @@ -46,7 +46,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 @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Post if datastore['SECURITY_GROUPS_ONLY'] qs = '(&(objectClass=group)(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))' end - q = query('(&(objectClass=group)(managedBy=*))', max_search, @user_fields) + q = query(qs, 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) @@ -67,9 +67,7 @@ class Metasploit3 < Msf::Post if q.nil? || q[:results].empty? print_status('No results returned.') else - if datastore['RESOLVE_MANAGERS'] - @user_fields << 'Manager Account Name' - end + @user_fields << 'Manager Account Name' if datastore['RESOLVE_MANAGERS'] results_table = parse_results(q[:results]) print_line results_table.to_s end @@ -77,7 +75,6 @@ class Metasploit3 < Msf::Post # Takes the results of LDAP query, parses them into a table def parse_results(results) - results_table = Rex::Ui::Text::Table.new( 'Header' => "Groups with Managers", 'Indent' => 1, @@ -95,23 +92,22 @@ class Metasploit3 < Msf::Post row << field[:value] end end - if datastore['RESOLVE_MANAGERS'] - begin - managedBy_cn = result[2][:value].split(',')[0] - m = query("(&(objectClass=user)(objectCategory=person)(#{managedBy_cn}))", 1, ['sAMAccountName']) - if !m.nil? && !m[:results].empty? - row << m[:results][0][0][:value] - else - row << "" - end - rescue + if datastore['RESOLVE_MANAGERS'] + begin + managedBy_cn = result[2][:value].split(',')[0] + m = query("(&(objectClass=user)(objectCategory=person)(#{managedBy_cn}))", 1, ['sAMAccountName']) + if !m.nil? && !m[:results].empty? + row << m[:results][0][0][:value] + else row << "" end - end + rescue + row << "" + end + end results_table << row end results_table end - end From 9493b333df6c65da6093f463501216d2bf2f6c76 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sun, 20 Dec 2015 21:22:03 +0000 Subject: [PATCH 6/9] rubocop --- modules/post/windows/gather/enum_ad_managedby_groups.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index 920d898f25..e1a14d876d 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -94,8 +94,8 @@ class Metasploit3 < Msf::Post end if datastore['RESOLVE_MANAGERS'] begin - managedBy_cn = result[2][:value].split(',')[0] - m = query("(&(objectClass=user)(objectCategory=person)(#{managedBy_cn}))", 1, ['sAMAccountName']) + managedby_cn = result[2][:value].split(',')[0] + m = query("(&(objectClass=user)(objectCategory=person)(#{managedby_cn}))", 1, ['sAMAccountName']) if !m.nil? && !m[:results].empty? row << m[:results][0][0][:value] else @@ -104,8 +104,7 @@ class Metasploit3 < Msf::Post rescue row << "" end - end - + end results_table << row end results_table From b0fca769d75c51ef18c76603a52f946428533b07 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 21 Dec 2015 10:39:30 +0000 Subject: [PATCH 7/9] capitalisation --- modules/post/windows/gather/enum_ad_managedby_groups.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index e1a14d876d..e10615241f 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -37,8 +37,8 @@ class Metasploit3 < Msf::Post register_options([ OptString.new('ADDITIONAL_FIELDS', [false, 'Additional group fields to retrieve, comma separated.', nil]), - OptBool.new('RESOLVE_MANAGERS', [true, 'Query LDAP to get the account name of group managers.', true]), - OptBool.new('SECURITY_GROUPS_ONLY', [true, 'Only include security groups.', true]) + OptBool.new('RESOLVE_MANAGERS', [true, 'Query LDAP to get the account name of group managers.', TRUE]), + OptBool.new('SECURITY_GROUPS_ONLY', [true, 'Only include security groups.', TRUE]) ], self.class) end From e8c8c54cb02c7723714a10e9774d502f1b63d074 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 21 Dec 2015 11:44:37 +0000 Subject: [PATCH 8/9] Use a regex with a negative lookbehind to cope with CNs that contain commas --- modules/post/windows/gather/enum_ad_managedby_groups.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index e10615241f..fdc5ea742a 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Post end if datastore['RESOLVE_MANAGERS'] begin - managedby_cn = result[2][:value].split(',')[0] + managedby_cn = result[2][:value].split(/,(? Date: Tue, 12 Jan 2016 11:20:20 +0000 Subject: [PATCH 9/9] Just search on DN for samaccountname --- modules/post/windows/gather/enum_ad_managedby_groups.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_ad_managedby_groups.rb b/modules/post/windows/gather/enum_ad_managedby_groups.rb index fdc5ea742a..72959f6086 100644 --- a/modules/post/windows/gather/enum_ad_managedby_groups.rb +++ b/modules/post/windows/gather/enum_ad_managedby_groups.rb @@ -94,8 +94,7 @@ class Metasploit3 < Msf::Post end if datastore['RESOLVE_MANAGERS'] begin - managedby_cn = result[2][:value].split(/,(?