Path: blob/master/modules/auxiliary/admin/ldap/ldap_object_attribute.rb
21089 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::LDAP7include Msf::OptionalSession::LDAP89def initialize(info = {})10super(11update_info(12info,13'Name' => 'LDAP Update Object',14'Description' => %q{15This module allows creating, reading, updating and deleting attributes of LDAP objects.16Users can specify the object and must specify a corresponding attribute.17},18'Author' => ['jheysel'],19'License' => MSF_LICENSE,20'Actions' => [21['CREATE', { 'Description' => 'Create an LDAP object' }],22['READ', { 'Description' => 'Read the the LDAP object' }],23['UPDATE', { 'Description' => 'Modify the LDAP object' }],24['DELETE', { 'Description' => 'Delete the LDAP object' }]25],26'DefaultAction' => 'READ',27'Notes' => {28'Stability' => [CRASH_SAFE],29'Reliability' => [],30'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]31}32)33)3435register_options(36[37OptString.new('BASE_DN', [false, 'LDAP base DN if you already have it']),38OptEnum.new('OBJECT_LOOKUP', [true, 'How to look up the target LDAP object', 'dN', ['dN', 'sAMAccountName']]),39OptString.new('OBJECT', [true, 'The target LDAP object']),40OptString.new('ATTRIBUTE', [true, 'The LDAP attribute to update (e.g., userPrincipalName)']),41OptString.new('VALUE', [false, 'The value for the specified LDAP object attribute'], conditions: ['ACTION', 'in', %w[Create Update] ])42]43)44end4546def find_target_object47search_filter = "(&(#{ldap_escape_filter(datastore['OBJECT_LOOKUP'])}=#{ldap_escape_filter(datastore['OBJECT'])}))"48result = []4950@ldap.search(base: @base_dn, filter: search_filter, attributes: ['distinguishedName', datastore['ATTRIBUTE']]) do |entry|51result << entry52end5354if result.empty?55fail_with(Failure::NotFound, "Could not find any object matching the filter: #{search_filter}")56elsif result.size > 157fail_with(Failure::UnexpectedReply, "Found multiple objects matching the filter: #{search_filter}. This should not happen.")58end5960result.first61end6263def action_read64target_object = find_target_object65target_dn = target_object['dN'].first66attribute_value = target_object[datastore['ATTRIBUTE'].to_sym]&.first6768if attribute_value.blank?69fail_with(Failure::NotFound, "Attribute #{datastore['ATTRIBUTE']} is not set for #{target_dn}")70end7172print_good("Found #{target_dn} with #{datastore['ATTRIBUTE']} set to #{attribute_value}")73attribute_value74end7576def action_create77target_object = find_target_object78target_dn = target_object['dN'].first79attribute = datastore['ATTRIBUTE'].to_sym80value = datastore['VALUE']8182print_status("Attempting to add attribute #{datastore['ATTRIBUTE']} with value #{value} to #{target_dn}...")8384ops = [[:add, attribute, value]]85@ldap.modify(dn: target_dn, operations: ops)86validate_query_result!(@ldap.get_operation_result.table)8788print_good("Successfully added attribute #{datastore['ATTRIBUTE']} with value #{value} to #{target_dn}")89end9091def action_update92target_object = find_target_object93target_dn = target_object['dN'].first94attribute = datastore['ATTRIBUTE'].to_sym95original_value = target_object[attribute]&.first96print_status("Current value of #{datastore['OBJECT']}'s #{datastore['ATTRIBUTE']}: #{original_value}")9798ops = [[:replace, attribute, datastore['VALUE']]]99100print_status("Attempting to update #{datastore['ATTRIBUTE']} for #{target_dn} to #{datastore['VALUE']}...")101@ldap.modify(dn: target_dn, operations: ops)102validate_query_result!(@ldap.get_operation_result.table)103104print_good("Successfully updated #{target_dn}'s #{datastore['ATTRIBUTE']} to #{datastore['VALUE']}")105original_value106end107108def action_delete109target_object = find_target_object110target_dn = target_object['dN'].first111attribute = datastore['ATTRIBUTE'].to_sym112113print_status("Attempting to delete attribute #{datastore['ATTRIBUTE']} from #{target_dn}...")114115ops = [[:delete, attribute]]116@ldap.modify(dn: target_dn, operations: ops)117validate_query_result!(@ldap.get_operation_result.table)118119print_good("Successfully deleted attribute #{datastore['ATTRIBUTE']} from #{target_dn}")120end121122def run123if (datastore['ACTION'].downcase == 'update' || datastore['ACTION'].downcase == 'create') && datastore['VALUE'].blank?124fail_with(Failure::BadConfig, 'The VALUE option must be set for CREATE and UPDATE actions.')125end126127ldap_connect do |ldap|128validate_bind_success!(ldap)129130if (@base_dn = datastore['BASE_DN'])131vprint_status("User-specified base DN: #{@base_dn}")132else133vprint_status('Discovering base DN automatically')134135unless (@base_dn = ldap.base_dn)136fail_with(Failure::NotFound, "Couldn't discover base DN!")137end138end139@ldap = ldap140141result = send("action_#{action.name.downcase}")142print_good('The operation completed successfully!')143result144end145rescue Errno::ECONNRESET146fail_with(Failure::Disconnected, 'The connection was reset.')147rescue Rex::ConnectionError => e148fail_with(Failure::Unreachable, e.message)149rescue Net::LDAP::Error => e150fail_with(Failure::Unknown, "#{e.class}: #{e.message}")151end152end153154155