Skip to content

Commit 479078a

Browse files
committed
Adding changing/resetting password module
1 parent 20b8fc6 commit 479078a

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework
2+
##
3+
4+
require 'ruby_smb/dcerpc/client'
5+
require 'pry-byebug'
6+
7+
class MetasploitModule < Msf::Auxiliary
8+
include Msf::Exploit::Remote::SMB::Client
9+
include Msf::Exploit::Remote::SMB::Client::Authenticated
10+
include Msf::Auxiliary::Report
11+
include Msf::OptionalSession::SMB
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'SMB Password Change',
18+
'Description' => %q{
19+
Change the password of an account using SMB. This provides several different
20+
APIs, each of which have their respective benefits and drawbacks.
21+
},
22+
'License' => MSF_LICENSE,
23+
'Author' => [
24+
'smashery'
25+
],
26+
'References' => [
27+
['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'],
28+
],
29+
'Notes' => {
30+
'Reliability' => [],
31+
'Stability' => [],
32+
'SideEffects' => [ IOC_IN_LOGS ]
33+
},
34+
'Actions' => [
35+
[ 'RESET', { 'Description' => "Reset the target's password without knowing the existing one (requires appropriate permissions)" } ],
36+
[ 'RESET_NTLM', { 'Description' => "Reset the target's NTLM hash, without knowing the existing password. This will not update kerberos keys." } ],
37+
[ 'CHANGE', { 'Description' => 'Change the password, knowing the existing one.' } ]
38+
],
39+
'DefaultAction' => 'RESET'
40+
)
41+
)
42+
43+
register_options(
44+
[
45+
OptString.new('NEW_PASSWORD', [false, 'The new password to change to', '']),
46+
OptString.new('TARGET_USER', [false, 'The user to change the password of. If not provided, will change for the account provided in SMBUser', ''], conditions: ['ACTION', 'in', %w[RESET RESET_NTLM]]),
47+
OptString.new('NEW_NTLM', [false, 'The new NTLM hash to change to', ''])
48+
]
49+
)
50+
end
51+
52+
def connect_samr(domain_name, target_user)
53+
vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')
54+
@samr = @tree.open_file(filename: 'samr', write: true, read: true)
55+
56+
vprint_status('Binding to \\samr...')
57+
@samr.bind(endpoint: RubySMB::Dcerpc::Samr)
58+
vprint_good('Bound to \\samr')
59+
end
60+
61+
def connect_samr
62+
vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')
63+
@samr = @tree.open_file(filename: 'samr', write: true, read: true)
64+
65+
vprint_status('Binding to \\samr...')
66+
@samr.bind(endpoint: RubySMB::Dcerpc::Samr)
67+
vprint_good('Bound to \\samr')
68+
end
69+
70+
def run
71+
case action.name
72+
when 'CHANGE'
73+
run_change
74+
when 'RESET'
75+
run_reset
76+
when 'RESET_NTLM'
77+
run_reset_ntlm
78+
end
79+
80+
# Don't disconnect the client if it's coming from the session so it can be reused
81+
unless session
82+
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)
83+
disconnect
84+
end
85+
end
86+
87+
def authenticate(anonymous_on_expired: false)
88+
if session
89+
print_status("Using existing session #{session.sid}")
90+
client = session.client
91+
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)
92+
simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too
93+
else
94+
connect
95+
begin
96+
begin
97+
smb_login
98+
rescue Rex::Proto::SMB::Exceptions::LoginError => e
99+
if anonymous_on_expired &&
100+
(e.source.is_a?(Rex::Proto::Kerberos::Model::Error::KerberosError) && [Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_KEY_EXPIRED].include?(e.source.error_code) ||
101+
e.source.is_a?(::WindowsError::ErrorCode) && [::WindowsError::NTStatus::STATUS_PASSWORD_EXPIRED, ::WindowsError::NTStatus::STATUS_PASSWORD_MUST_CHANGE].include?(e.source))
102+
# Password has expired - we'll need to anonymous connect
103+
opts = {
104+
:username => '',
105+
:password => '',
106+
:domain => '',
107+
:auth_protocol => Msf::Exploit::Remote::AuthOption::NTLM
108+
}
109+
disconnect
110+
connect
111+
smb_login(opts: opts)
112+
else
113+
raise
114+
end
115+
end
116+
117+
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
118+
fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")
119+
end
120+
end
121+
122+
report_service(
123+
host: simple.address,
124+
port: simple.port,
125+
host_name: simple.client.default_name,
126+
proto: 'tcp',
127+
name: 'smb',
128+
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"
129+
)
130+
131+
begin
132+
@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")
133+
rescue RubySMB::Error::RubySMBError => e
134+
fail_with(Module::Failure::Unreachable,
135+
"Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")
136+
end
137+
138+
connect_samr
139+
140+
end
141+
142+
def run_reset
143+
authenticate(anonymous_on_expired: false)
144+
145+
@server_handle = @samr.samr_connect
146+
domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: datastore['SMBDomain'])
147+
@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: domain_sid)
148+
user_rids = @samr.samr_lookup_names_in_domain(domain_handle: @domain_handle, names: [datastore['TARGET_USER']])
149+
rid = user_rids[datastore['TARGET_USER']][:rid]
150+
user_handle = @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)
151+
152+
user_info = RubySMB::Dcerpc::Samr::SamprUserInfoBuffer.new(
153+
tag: RubySMB::Dcerpc::Samr::USER_INTERNAL4_INFORMATION_NEW,
154+
member: RubySMB::Dcerpc::Samr::SamprUserInternal4InformationNew.new(
155+
i1: {
156+
password_expired: 0,
157+
which_fields: RubySMB::Dcerpc::Samr::USER_ALL_NTPASSWORDPRESENT | RubySMB::Dcerpc::Samr::USER_ALL_PASSWORDEXPIRED
158+
},
159+
user_password: {
160+
buffer: RubySMB::Dcerpc::Samr::SamprEncryptedUserPasswordNew.encrypt_password(
161+
datastore['NEW_PASSWORD'],
162+
simple.client.application_key
163+
)
164+
}
165+
)
166+
)
167+
@samr.samr_set_information_user2(
168+
user_handle: user_handle,
169+
user_info: user_info
170+
)
171+
end
172+
173+
def run_change
174+
authenticate(anonymous_on_expired: true)
175+
176+
@samr.samr_unicode_change_password_user2(target_username: datastore['SMBUser'], old_password: datastore['SMBPass'], new_password: datastore['NEW_PASSWORD'])
177+
178+
rescue RubySMB::Error::RubySMBError => e
179+
fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")
180+
rescue Rex::ConnectionError => e
181+
fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")
182+
rescue Msf::Exploit::Remote::MsSamr::MsSamrError => e
183+
fail_with(Module::Failure::BadConfig, "[#{e.class}] #{e}")
184+
rescue ::StandardError => e
185+
raise e
186+
ensure
187+
@samr.close_handle(@domain_handle) if @domain_handle
188+
@samr.close_handle(@server_handle) if @server_handle
189+
@samr.close if @samr
190+
@tree.disconnect! if @tree
191+
end
192+
end

0 commit comments

Comments
 (0)