Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/ldap/ldap_object_attribute.rb
21089 views
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
include Msf::Exploit::Remote::LDAP
8
include Msf::OptionalSession::LDAP
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'LDAP Update Object',
15
'Description' => %q{
16
This module allows creating, reading, updating and deleting attributes of LDAP objects.
17
Users can specify the object and must specify a corresponding attribute.
18
},
19
'Author' => ['jheysel'],
20
'License' => MSF_LICENSE,
21
'Actions' => [
22
['CREATE', { 'Description' => 'Create an LDAP object' }],
23
['READ', { 'Description' => 'Read the the LDAP object' }],
24
['UPDATE', { 'Description' => 'Modify the LDAP object' }],
25
['DELETE', { 'Description' => 'Delete the LDAP object' }]
26
],
27
'DefaultAction' => 'READ',
28
'Notes' => {
29
'Stability' => [CRASH_SAFE],
30
'Reliability' => [],
31
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
32
}
33
)
34
)
35
36
register_options(
37
[
38
OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),
39
OptEnum.new('OBJECT_LOOKUP', [true, 'How to look up the target LDAP object', 'dN', ['dN', 'sAMAccountName']]),
40
OptString.new('OBJECT', [true, 'The target LDAP object']),
41
OptString.new('ATTRIBUTE', [true, 'The LDAP attribute to update (e.g., userPrincipalName)']),
42
OptString.new('VALUE', [false, 'The value for the specified LDAP object attribute'], conditions: ['ACTION', 'in', %w[Create Update] ])
43
]
44
)
45
end
46
47
def find_target_object
48
search_filter = "(&(#{ldap_escape_filter(datastore['OBJECT_LOOKUP'])}=#{ldap_escape_filter(datastore['OBJECT'])}))"
49
result = []
50
51
@ldap.search(base: @base_dn, filter: search_filter, attributes: ['distinguishedName', datastore['ATTRIBUTE']]) do |entry|
52
result << entry
53
end
54
55
if result.empty?
56
fail_with(Failure::NotFound, "Could not find any object matching the filter: #{search_filter}")
57
elsif result.size > 1
58
fail_with(Failure::UnexpectedReply, "Found multiple objects matching the filter: #{search_filter}. This should not happen.")
59
end
60
61
result.first
62
end
63
64
def action_read
65
target_object = find_target_object
66
target_dn = target_object['dN'].first
67
attribute_value = target_object[datastore['ATTRIBUTE'].to_sym]&.first
68
69
if attribute_value.blank?
70
fail_with(Failure::NotFound, "Attribute #{datastore['ATTRIBUTE']} is not set for #{target_dn}")
71
end
72
73
print_good("Found #{target_dn} with #{datastore['ATTRIBUTE']} set to #{attribute_value}")
74
attribute_value
75
end
76
77
def action_create
78
target_object = find_target_object
79
target_dn = target_object['dN'].first
80
attribute = datastore['ATTRIBUTE'].to_sym
81
value = datastore['VALUE']
82
83
print_status("Attempting to add attribute #{datastore['ATTRIBUTE']} with value #{value} to #{target_dn}...")
84
85
ops = [[:add, attribute, value]]
86
@ldap.modify(dn: target_dn, operations: ops)
87
validate_query_result!(@ldap.get_operation_result.table)
88
89
print_good("Successfully added attribute #{datastore['ATTRIBUTE']} with value #{value} to #{target_dn}")
90
end
91
92
def action_update
93
target_object = find_target_object
94
target_dn = target_object['dN'].first
95
attribute = datastore['ATTRIBUTE'].to_sym
96
original_value = target_object[attribute]&.first
97
print_status("Current value of #{datastore['OBJECT']}'s #{datastore['ATTRIBUTE']}: #{original_value}")
98
99
ops = [[:replace, attribute, datastore['VALUE']]]
100
101
print_status("Attempting to update #{datastore['ATTRIBUTE']} for #{target_dn} to #{datastore['VALUE']}...")
102
@ldap.modify(dn: target_dn, operations: ops)
103
validate_query_result!(@ldap.get_operation_result.table)
104
105
print_good("Successfully updated #{target_dn}'s #{datastore['ATTRIBUTE']} to #{datastore['VALUE']}")
106
original_value
107
end
108
109
def action_delete
110
target_object = find_target_object
111
target_dn = target_object['dN'].first
112
attribute = datastore['ATTRIBUTE'].to_sym
113
114
print_status("Attempting to delete attribute #{datastore['ATTRIBUTE']} from #{target_dn}...")
115
116
ops = [[:delete, attribute]]
117
@ldap.modify(dn: target_dn, operations: ops)
118
validate_query_result!(@ldap.get_operation_result.table)
119
120
print_good("Successfully deleted attribute #{datastore['ATTRIBUTE']} from #{target_dn}")
121
end
122
123
def run
124
if (datastore['ACTION'].downcase == 'update' || datastore['ACTION'].downcase == 'create') && datastore['VALUE'].blank?
125
fail_with(Failure::BadConfig, 'The VALUE option must be set for CREATE and UPDATE actions.')
126
end
127
128
ldap_connect do |ldap|
129
validate_bind_success!(ldap)
130
131
if (@base_dn = datastore['BASE_DN'])
132
vprint_status("User-specified base DN: #{@base_dn}")
133
else
134
vprint_status('Discovering base DN automatically')
135
136
unless (@base_dn = ldap.base_dn)
137
fail_with(Failure::NotFound, "Couldn't discover base DN!")
138
end
139
end
140
@ldap = ldap
141
142
result = send("action_#{action.name.downcase}")
143
print_good('The operation completed successfully!')
144
result
145
end
146
rescue Errno::ECONNRESET
147
fail_with(Failure::Disconnected, 'The connection was reset.')
148
rescue Rex::ConnectionError => e
149
fail_with(Failure::Unreachable, e.message)
150
rescue Net::LDAP::Error => e
151
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
152
end
153
end
154
155