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. Commercial Alternative to JupyterHub.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/ldap/change_password.rb
Views: 15959
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
8
include Msf::Auxiliary::Report
9
include Msf::Exploit::Remote::LDAP
10
include Msf::OptionalSession::LDAP
11
12
ATTRIBUTE = 'unicodePwd'.freeze
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'Change Password',
19
'Description' => %q{
20
This module allows Active Directory users to change their own passwords, or reset passwords for
21
accounts they have privileges over.
22
},
23
'Author' => [
24
'smashery' # module author
25
],
26
'References' => [
27
['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'],
28
['URL', 'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2'],
29
],
30
'License' => MSF_LICENSE,
31
'Actions' => [
32
['RESET', { 'Description' => "Reset a target user's password, having permissions over their account" }],
33
['CHANGE', { 'Description' => "Change the user's password, knowing the existing password" }]
34
],
35
'DefaultAction' => 'RESET',
36
'Notes' => {
37
'Stability' => [],
38
'SideEffects' => [ IOC_IN_LOGS ],
39
'Reliability' => []
40
}
41
)
42
)
43
44
register_options([
45
OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET]]),
46
OptString.new('NEW_PASSWORD', [ true, 'The new password to set for the user' ])
47
])
48
end
49
50
def fail_with_ldap_error(message)
51
ldap_result = @ldap.get_operation_result.table
52
return if ldap_result[:code] == 0
53
54
print_error(message)
55
if ldap_result[:code] == 19
56
extra_error = ''
57
if action.name == 'CHANGE' && !datastore['SESSION'].blank?
58
# If you're already in a session, you could provide the wrong password, and you get this error
59
extra_error = ' or incorrect current password'
60
end
61
62
error = "The password changed failed, likely due to a password policy violation (e.g. not sufficiently complex, matching previous password, or changing the password too often)#{extra_error}"
63
fail_with(Failure::NotFound, error)
64
else
65
validate_query_result!(ldap_result)
66
end
67
end
68
69
def ldap_get(filter, attributes: [])
70
raw_obj = @ldap.search(base: @base_dn, filter: filter, attributes: attributes)&.first
71
return nil unless raw_obj
72
73
obj = {}
74
75
obj['dn'] = raw_obj['dn'].first.to_s
76
unless raw_obj['sAMAccountName'].empty?
77
obj['sAMAccountName'] = raw_obj['sAMAccountName'].first.to_s
78
end
79
80
obj
81
end
82
83
def run
84
if action.name == 'CHANGE'
85
fail_with(Failure::BadConfig, 'Must set USERNAME when changing password') if datastore['USERNAME'].blank?
86
fail_with(Failure::BadConfig, 'Must set PASSWORD when changing password') if datastore['PASSWORD'].blank?
87
elsif action.name == 'RESET'
88
fail_with(Failure::BadConfig, 'Must set TARGET_USER when resetting password') if datastore['TARGET_USER'].blank?
89
end
90
if session.blank? && datastore['USERNAME'].blank? && datastore['LDAP::Auth'] != Msf::Exploit::Remote::AuthOption::SCHANNEL
91
print_warning('Connecting with an anonymous bind')
92
end
93
ldap_connect do |ldap|
94
validate_bind_success!(ldap)
95
96
if (@base_dn = datastore['BASE_DN'])
97
print_status("User-specified base DN: #{@base_dn}")
98
else
99
print_status('Discovering base DN automatically')
100
101
if (@base_dn = ldap.base_dn)
102
print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")
103
else
104
fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
105
end
106
end
107
@ldap = ldap
108
109
begin
110
send("action_#{action.name.downcase}")
111
rescue ::IOError => e
112
fail_with(Failure::UnexpectedReply, e.message)
113
end
114
end
115
rescue Errno::ECONNRESET
116
fail_with(Failure::Disconnected, 'The connection was reset.')
117
rescue Rex::ConnectionError => e
118
fail_with(Failure::Unreachable, e.message)
119
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
120
fail_with(Failure::NoAccess, e.message)
121
rescue Rex::Proto::LDAP::LdapException => e
122
fail_with(Failure::NoAccess, e.message)
123
rescue Net::LDAP::Error => e
124
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
125
end
126
127
def get_user_obj(username)
128
obj = ldap_get("(sAMAccountName=#{ldap_escape_filter(username)})", attributes: ['sAMAccountName'])
129
fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{username}") unless obj
130
131
obj
132
end
133
134
def action_reset
135
target_user = datastore['TARGET_USER']
136
obj = get_user_obj(target_user)
137
138
new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')
139
unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, new_pass)
140
fail_with_ldap_error("Failed to reset the password for #{datastore['TARGET_USER']}.")
141
end
142
print_good("Successfully reset password for #{datastore['TARGET_USER']}.")
143
end
144
145
def action_change
146
obj = get_user_obj(datastore['USERNAME'])
147
148
new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')
149
old_pass = "\"#{datastore['PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')
150
unless @ldap.modify(dn: obj['dn'], operations: [[:delete, ATTRIBUTE, old_pass], [:add, ATTRIBUTE, new_pass]])
151
fail_with_ldap_error("Failed to reset the password for #{datastore['USERNAME']}.")
152
end
153
print_good("Successfully changed password for #{datastore['USERNAME']}.")
154
end
155
end
156
157