Skip to content

Commit 0d19e47

Browse files
Land #16677, Add module for adding/deleting computers via MS-SAMR
2 parents 1964e61 + c4be01c commit 0d19e47

File tree

3 files changed

+350
-1
lines changed

3 files changed

+350
-1
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ GEM
427427
ruby-progressbar (1.11.0)
428428
ruby-rc4 (0.1.5)
429429
ruby2_keywords (0.0.5)
430-
ruby_smb (3.1.3)
430+
ruby_smb (3.1.5)
431431
bindata
432432
openssl-ccm
433433
openssl-cmac
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
## Vulnerable Application
2+
Add, lookup and delete computer accounts via MS-SAMR. By default standard active directory users can add up to 10 new
3+
computers to the domain. Administrative privileges however are required to delete the created accounts.
4+
5+
## Verification Steps
6+
7+
1. From msfconsole
8+
2. Do: `use auxiliary/admin/dcerpc/samr_computer`
9+
3. Set the `RHOSTS`, `SMBUser` and `SMBPass` options
10+
1. Set the `COMPUTER_NAME` option for `DELETE_COMPUTER` and `LOOKUP_COMPUTER` actions
11+
4. Run the module and see that a new machine account was added
12+
13+
## Options
14+
15+
### SMBDomain
16+
17+
The Windows domain to use for authentication. The domain will automatically be identified if this option is left in its
18+
default value.
19+
20+
### COMPUTER_NAME
21+
22+
The computer name to add, lookup or delete. This option is optional for the `ADD_COMPUTER` action, and required for the
23+
`LOOKUP_COMPUTER` and `DELETE_COMPUTER` actions.
24+
25+
### COMPUTER_PASSWORD
26+
27+
The password for the new computer. This option is only used for the `ADD_COMPUTER` action. If left blank, a random value
28+
will be generated.
29+
30+
## Actions
31+
32+
### ADD_COMPUTER
33+
34+
Add a new computer to the domain. This action will fail with status `STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED` if the
35+
user has exceeded the maximum number of computer accounts that they are allowed to create.
36+
37+
After the computer account is created, the password will be set for it. If `COMPUTER_NAME` is set, that value will be
38+
used and the module will fail if the selected name is already in use. If `COMPUTER_NAME` is *not* set, a random value
39+
will be used.
40+
41+
### DELETE_COMPUTER
42+
43+
Delete a computer from the domain. This action requires that the `COMPUTER_NAME` option be set.
44+
45+
### LOOKUP_COMPUTER
46+
47+
Lookup a computer in the domain. This action verifies that the specified computer exists, and looks up its security ID
48+
(SID), which includes the relative ID (RID) as the last component.
49+
50+
## Scenarios
51+
52+
### Windows Server 2019
53+
54+
First, a new computer account is created and its details are logged to the database.
55+
56+
```
57+
msf6 auxiliary(admin/dcerpc/samr_computer) > set RHOSTS 192.168.159.96
58+
RHOSTS => 192.168.159.96
59+
msf6 auxiliary(admin/dcerpc/samr_computer) > set SMBUser aliddle
60+
SMBUser => aliddle
61+
msf6 auxiliary(admin/dcerpc/samr_computer) > set SMBPass Password1
62+
SMBPass => Password1
63+
msf6 auxiliary(admin/dcerpc/samr_computer) > show options
64+
65+
Module options (auxiliary/admin/dcerpc/samr_computer):
66+
67+
Name Current Setting Required Description
68+
---- --------------- -------- -----------
69+
COMPUTER_NAME no The computer name
70+
COMPUTER_PASSWORD no The password for the new computer
71+
RHOSTS 192.168.159.96 yes The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
72+
RPORT 445 yes The target port (TCP)
73+
SMBDomain . no The Windows domain to use for authentication
74+
SMBPass Password1 no The password for the specified username
75+
SMBUser aliddle no The username to authenticate as
76+
77+
78+
Auxiliary action:
79+
80+
Name Description
81+
---- -----------
82+
ADD_COMPUTER Add a computer account
83+
84+
85+
msf6 auxiliary(admin/dcerpc/samr_computer) > run
86+
[*] Running module against 192.168.159.96
87+
88+
[*] 192.168.159.96:445 - Using automatically identified domain: MSFLAB
89+
[+] 192.168.159.96:445 - Successfully created MSFLAB\DESKTOP-2X8F54QG$ with password MCoDkNALd3SdGR1GoLhqniEkWa8Me9FY
90+
[*] Auxiliary module execution completed
91+
msf6 auxiliary(admin/dcerpc/samr_computer) > creds
92+
Credentials
93+
===========
94+
95+
host origin service public private realm private_type JtR Format
96+
---- ------ ------- ------ ------- ----- ------------ ----------
97+
192.168.159.96 192.168.159.96 445/tcp (smb) DESKTOP-2X8F54QG$ MCoDkNALd3SdGR1GoLhqniEkWa8Me9FY MSFLAB Password
98+
99+
msf6 auxiliary(admin/dcerpc/samr_computer) >
100+
```
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'ruby_smb/dcerpc/client'
7+
8+
class MetasploitModule < Msf::Auxiliary
9+
include Msf::Exploit::Remote::SMB::Client::Authenticated
10+
include Msf::Exploit::Remote::DCERPC
11+
include Msf::Auxiliary::Report
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'SAMR Computer Management',
18+
'Description' => %q{
19+
Add, lookup and delete computer accounts via MS-SAMR. By default
20+
standard active directory users can add up to 10 new computers to the
21+
domain. Administrative privileges however are required to delete the
22+
created accounts.
23+
},
24+
'License' => MSF_LICENSE,
25+
'Author' => [
26+
'JaGoTu', # @jagotu Original Impacket code
27+
'Spencer McIntyre',
28+
],
29+
'References' => [
30+
['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/addcomputer.py'],
31+
],
32+
'Notes' => {
33+
'Reliability' => [],
34+
'Stability' => [],
35+
'SideEffects' => [ IOC_IN_LOGS ]
36+
},
37+
'Actions' => [
38+
[ 'ADD_COMPUTER', { 'Description' => 'Add a computer account' } ],
39+
[ 'DELETE_COMPUTER', { 'Description' => 'Delete a computer account' } ],
40+
[ 'LOOKUP_COMPUTER', { 'Description' => 'Lookup a computer account' } ]
41+
],
42+
'DefaultAction' => 'ADD_COMPUTER'
43+
)
44+
)
45+
46+
register_options([
47+
OptString.new('COMPUTER_NAME', [ false, 'The computer name' ]),
48+
OptString.new('COMPUTER_PASSWORD', [ false, 'The password for the new computer' ], conditions: %w[ACTION == ADD_COMPUTER]),
49+
Opt::RPORT(445)
50+
])
51+
end
52+
53+
def connect_samr
54+
vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')
55+
samr = @tree.open_file(filename: 'samr', write: true, read: true)
56+
57+
vprint_status('Binding to \\samr...')
58+
samr.bind(endpoint: RubySMB::Dcerpc::Samr)
59+
vprint_good('Bound to \\samr')
60+
61+
samr
62+
end
63+
64+
def run
65+
begin
66+
connect
67+
rescue Rex::ConnectionError => e
68+
fail_with(Failure::Unreachable, e.message)
69+
end
70+
71+
begin
72+
smb_login
73+
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
74+
fail_with(Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
75+
end
76+
report_service(
77+
host: rhost,
78+
port: rport,
79+
host_name: simple.client.default_name,
80+
proto: 'tcp',
81+
name: 'smb',
82+
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
83+
)
84+
85+
begin
86+
@tree = simple.client.tree_connect("\\\\#{sock.peerhost}\\IPC$")
87+
rescue RubySMB::Error::RubySMBError => e
88+
fail_with(Failure::Unreachable, "Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")
89+
end
90+
91+
begin
92+
@samr = connect_samr
93+
@server_handle = @samr.samr_connect
94+
rescue RubySMB::Dcerpc::Error::FaultError => e
95+
elog(e.message, error: e)
96+
fail_with(Failure::UnexpectedReply, "Connection failed (DCERPC fault: #{e.status_name})")
97+
end
98+
99+
if datastore['SMBDomain'].blank? || datastore['SMBDomain'] == '.'
100+
all_domains = @samr.samr_enumerate_domains_in_sam_server(server_handle: @server_handle).map(&:to_s).map(&:encode)
101+
all_domains.delete('Builtin')
102+
if all_domains.empty?
103+
fail_with(Failure::NotFound, 'No domains were found on the SAM server.')
104+
elsif all_domains.length > 1
105+
print_status("Enumerated domains: #{all_domains.join(', ')}")
106+
fail_with(Failure::BadConfig, 'The SAM server has more than one domain, the target must be specified.')
107+
end
108+
109+
@domain_name = all_domains.first
110+
print_status("Using automatically identified domain: #{@domain_name}")
111+
else
112+
@domain_name = datastore['SMBDomain']
113+
end
114+
115+
@domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: @domain_name)
116+
@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: @domain_sid)
117+
send("action_#{action.name.downcase}")
118+
rescue RubySMB::Dcerpc::Error::DcerpcError => e
119+
elog(e.message, error: e)
120+
fail_with(Failure::UnexpectedReply, e.message)
121+
rescue RubySMB::Error::RubySMBError
122+
elog(e.message, error: e)
123+
fail_with(Failure::Unknown, e.message)
124+
end
125+
126+
def random_hostname(prefix: 'DESKTOP')
127+
"#{prefix}-#{Rex::Text.rand_base(8, '', ('A'..'Z').to_a + ('0'..'9').to_a)}$"
128+
end
129+
130+
def action_add_computer
131+
if datastore['COMPUTER_NAME'].blank?
132+
computer_name = random_hostname
133+
4.downto(0) do |attempt|
134+
break if @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [ computer_name ]).nil?
135+
136+
computer_name = random_hostname
137+
fail_with(Failure::BadConfig, 'Could not find an unused computer name.') if attempt == 0
138+
end
139+
else
140+
computer_name = datastore['COMPUTER_NAME']
141+
if @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [ computer_name ])
142+
fail_with(Failure::BadConfig, 'The specified computer name already exists.')
143+
end
144+
end
145+
146+
result = @samr.samr_create_user2_in_domain(
147+
domain_handle: @domain_handle,
148+
name: computer_name,
149+
account_type: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT,
150+
desired_access: RubySMB::Dcerpc::Samr::USER_FORCE_PASSWORD_CHANGE | RubySMB::Dcerpc::Samr::MAXIMUM_ALLOWED
151+
)
152+
153+
user_handle = result[:user_handle]
154+
if datastore['COMPUTER_PASSWORD'].blank?
155+
password = Rex::Text.rand_text_alphanumeric(32)
156+
else
157+
password = datastore['COMPUTER_PASSWORD']
158+
end
159+
160+
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
161+
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
162+
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
163+
i1: {
164+
password_expired: 1,
165+
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
166+
},
167+
user_password: {
168+
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
169+
password,
170+
@simple.client.application_key
171+
)
172+
}
173+
)
174+
)
175+
@samr.samr_set_information_user2(
176+
user_handle: user_handle,
177+
user_info: user_info
178+
)
179+
180+
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
181+
tag: RubySMB::Dcerpc::Samr::USER_CONTROL_INFORMATION,
182+
member: RubySMB::Dcerpc::Samr::UserControlInformation.new(
183+
user_account_control: RubySMB::Dcerpc::Samr::USER_WORKSTATION_TRUST_ACCOUNT
184+
)
185+
)
186+
@samr.samr_set_information_user2(
187+
user_handle: user_handle,
188+
user_info: user_info
189+
)
190+
print_good("Successfully created #{@domain_name}\\#{computer_name} with password #{password}")
191+
report_creds(@domain_name, computer_name, password)
192+
end
193+
194+
def action_delete_computer
195+
fail_with(Failure::BadConfig, 'This action requires COMPUTER_NAME to be specified.') if datastore['COMPUTER_NAME'].blank?
196+
computer_name = datastore['COMPUTER_NAME']
197+
198+
details = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [ computer_name ])
199+
fail_with(Failure::BadConfig, 'The specified computer was not found.') if details.nil?
200+
details = details[computer_name]
201+
202+
handle = @samr.samr_open_user(domain_handle: @domain_handle, user_id: details[:rid])
203+
@samr.samr_delete_user(user_handle: handle)
204+
print_good('The specified computer has been deleted.')
205+
end
206+
207+
def action_lookup_computer
208+
fail_with(Failure::BadConfig, 'This action requires COMPUTER_NAME to be specified.') if datastore['COMPUTER_NAME'].blank?
209+
computer_name = datastore['COMPUTER_NAME']
210+
211+
details = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [ computer_name ])
212+
if details.nil?
213+
print_error('The specified computer was not found.')
214+
return
215+
end
216+
details = details[computer_name]
217+
sid = @samr.samr_rid_to_sid(object_handle: @domain_handle, rid: details[:rid]).to_s
218+
print_good("Found #{@domain_name}\\#{computer_name} (SID: #{sid})")
219+
end
220+
221+
def report_creds(domain, username, password)
222+
service_data = {
223+
address: datastore['RHOST'],
224+
port: datastore['RPORT'],
225+
service_name: 'smb',
226+
protocol: 'tcp',
227+
workspace_id: myworkspace_id
228+
}
229+
230+
credential_data = {
231+
module_fullname: fullname,
232+
origin_type: :service,
233+
private_data: password,
234+
private_type: :password,
235+
username: username,
236+
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
237+
realm_value: domain
238+
}.merge(service_data)
239+
240+
credential_core = create_credential(credential_data)
241+
242+
login_data = {
243+
core: credential_core,
244+
status: Metasploit::Model::Login::Status::UNTRIED
245+
}.merge(service_data)
246+
247+
create_credential_login(login_data)
248+
end
249+
end

0 commit comments

Comments
 (0)