AS-REP roasting: the checkbox that hands out hashes
An account with Kerberos pre-authentication switched off will hand an encrypted AS-REP to anyone who asks for it by name, and no password is needed to ask. Here's the one LDAP filter that finds them, the 4768 field that catches a roast in progress, and the fix that survives the accounts which genuinely need the flag.
01 The Big PictureFor leadership · no jargon
Okay, so Alfresco's Process Services documentation still tells you to tick one box. Step h of the Kerberos SSO setup, in the 25.2 docs: enable Do not require Kerberos preauthentication on the service account. Step i then runs ktpass with -crypto all, which mints every supported key type, RC4 included. The same steps sit in the 24.3, 24.4 and 25.1 docs, so this is a vendor's standing instruction rather than one stale page.
So this post is about AS-REP roasting: what that one ticked box turns an account into the moment somebody knows its name, how to find every other account like it, and what to do about the few that have to stay that way. Kerberos is the ticket system Windows uses to log in. Normally you prove you know the password before the domain controller prints a ticket, and the proof is a timestamp encrypted with a key derived from that password. Switch pre-authentication off for an account and the proof is skipped: the controller prints the ticket for anyone who asks, and the valuable part of it is encrypted with the account's own password. You can't read it, but you can carry it home and guess passwords against it on your own hardware, for as long as you like.
Think of the box office that normally checks your ID before printing a ticket. With the flag set, the clerk prints one for anyone who says the name, and the ticket is stamped with a code that only that person's password produces. Take it home, try passwords until the stamp matches. That's the whole attack, and unlike Kerberoasting it needs no account of your own to start: no password, no ticket, no prior foothold, just a name and a route to a domain controller.
Which is why the useful half of this post is the tidying up. The accounts carrying the flag got it because a vendor guide, a consultant or an old troubleshooting thread said to, and the change outlived the reason. Finding them is one LDAP filter. Clearing them is one cmdlet. But the interesting part is the handful that genuinely cannot lose the flag, and what you do about those.
02 Technical BreakdownFor engineers
Right, into the protocol. RFC 4120 is unusually candid here: section 3.1 describes the feature and the attack in the same paragraph.
"Without pre-authentication, the authentication server does not know whether the client is actually the principal named in the request. It simply sends a reply without knowing or caring whether they are the same. This is acceptable because nobody but the principal whose identity was given in the request will be able to use the reply. Its critical information is encrypted in that principal's key. However, an attacker can send a KRB_AS_REQ message to get known plaintext in order to attack the principal's key. Especially if the key is based on a password, this may create a security exposure."
That paragraph is RFC 4120, section 3.1.
The exchange it describes is the AS exchange: a client with no credentials asks the KDC for a ticket-granting ticket. A client that doesn't send pre-authentication up front gets a KRB_ERROR with code 25, KDC_ERR_PREAUTH_REQUIRED, carrying PA-ETYPE-INFO2 in the error data so the client knows which salt and etype to use. It then retries with PA-ENC-TIMESTAMP (padata type 2), a timestamp encrypted with its long-term key and key usage 1 (RFC 4120, sections 3.1.3 and 5.2.7.2). That retry is the "prove it" step, and a client that already has the password, as Windows does at logon, sends it in the first request, which is why a normal 4768 shows PreAuthType 2.
Set UF_DONT_REQUIRE_PREAUTH on an account that is still enabled, and the KDC skips straight to the good part. A disabled account answers with KDC_ERR_CLIENT_REVOKED instead, which is why Impacket's LDAP filter excludes disabled accounts. The AS-REP comes back with two fields that matter:
ticket, a TGT encrypted in thekrbtgtkey. Useless to the attacker, and worth saying out loud because it confuses people looking at their first roast.enc-part, anEncASRepPart(APPLICATION 25) encrypted in the target account's long-term key, key usage 3 (RFC 4120, section 5.4.2). This is the blob that gets cracked.
The AS-REP is therefore a password checker that the domain hands out on request. A password guess produces a key, the key verifies the blob's checksum, and the result is a yes or no with no KDC in the loop. No lockout counter, no failed logon, no rate limit, and nothing written to the account itself. The RFC says it plainly: the reply gives an attacker "ciphertext that may be used to mount brute force or dictionary attacks to decrypt the credentials" (section 10).
01 The flag itself
One bit in userAccountControl, and Microsoft documents both the bit and the checkbox:
- The constant is
UF_DONT_REQUIRE_PREAUTH = 0x00400000(MS-SAMR, section 2.2.1.13). - Microsoft's KB 305144 calls the same bit
DONT_REQ_PREAUTH, hex0x400000, decimal4194304. - PowerShell exposes it as
DoesNotRequirePreAuth, and sets it withSet-ADAccountControl -DoesNotRequirePreAuth. - ADUC has it on the Account tab under Account options: "Do not require Kerberos preauthentication".
The LDAP filter uses the bitwise matching rule, <attribute>:<rule OID>:=<decimal>, where 1.2.840.113556.1.4.803 is LDAP_MATCHING_RULE_BIT_AND. The value has to be decimal, so 4194304, not 0x400000:
(userAccountControl:1.2.840.113556.1.4.803:=4194304) That string is the whole hunt, and it's the one to memorise. Impacket's GetNPUsers.py uses the same clause with two extra filters, (!(userAccountControl:1.2.840.113556.1.4.803:=2)) to drop disabled accounts and (!(objectCategory=computer)) to drop computers (examples/GetNPUsers.py, lines 234-236). Rubeus gets there by a different road, samAccountType=805306368, which is the user object type.
In PowerShell, the getter property maps back to the same bit:
Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true' -Properties DoesNotRequirePreAuth
Get-ADComputer -Filter 'DoesNotRequirePreAuth -eq $true' -Properties DoesNotRequirePreAuthNote the second line. The bit is valid on computer accounts, and most tooling filters them out by design, so an inventory built only from user objects is incomplete. Whether a domain controller will actually issue an AS-REP for a computer object with the flag set isn't something I could confirm, so treat it as a candidate rather than a confirmed roast. If you have tested it, tell me.
02 What makes the hash crackable, and how fast
The etype decides everything about the cost of guessing. With no pre-authentication the KDC picks from the etypes the client offers, and client tools ask for RC4 first.
- etype 23, rc4-hmac:
MD4(UNICODE(password)), the NT hash. No salt, no iterations, so it is cheap. - etype 17, aes128-cts-hmac-sha1-96:
PBKDF2-HMAC-SHA1, 4096 iterations by default, thenDK(tkey, "kerberos"). About 1,000 times dearer per guess. - etype 18, aes256-cts-hmac-sha1-96: the same construction with a 256-bit key. About 1,400 times dearer per guess.
RC4 is the default in practice because of a second AD quirk: if msDS-SupportedEncryptionTypes is blank on the account, the KDC uses DefaultSupportedEncryptionTypes, and the table in Microsoft's RC4 guidance maps the zero value to RC4-HMAC-MD5. A blank attribute is the normal state on accounts nobody has looked at in years, so an untouched estate lands on the cheapest hash.
The RC4-HMAC ciphertext layout is what makes the blob self-verifying: Checksum[16] || Confounder[8] || Data, where the checksum is an HMAC over the rest keyed from the derived key (RFC 4757, section 4). That's why every etype 23 hash starts with those 16 bytes:
[email protected]:1A2B3C4D5E6F708192A3B4C5D6E7F809$0F1E2D3C... Field by field: krb5asrep is the format, 23 the etype, [email protected] the principal, the first 16 bytes the keyed checksum, and everything after the $ the confounder plus ciphertext. Impacket splits the blob at byte 16 to build it (GetNPUsers.py, lines 197-199). hashcat takes it as mode 18200. The AES variants go to 32100 (etype 17) and 32200 (etype 18), which only exist from hashcat 7.0 onward, so an older install won't recognise the hash at all.
On a single RTX 4090, hashcat's own benchmark puts 18200 at 3,564.1 MH/s. The AES TGS modes on the same card run at 5,100.2 kH/s and 2,562.9 kH/s, which makes the etype 23 path roughly 700 to 1,400 times faster per guess. I couldn't find a benchmark for 32100 or 32200, and they run the same PBKDF2 loop, so treat those two as the proxy rather than a measurement.
03 The roast, with the tools that exist
None of this is exotic, which is the point. Unauthenticated, with a username list:
GetNPUsers.py -dc-ip 10.0.0.10 -no-pass -usersfile users.txt \
-format hashcat -outputfile asrep.txt example.local/ -usersfile skips LDAP entirely and just tries names, -no-pass keeps it unauthenticated, and -outputfile implies -request. Against a healthy account the tool prints User alice doesn't have UF_DONT_REQUIRE_PREAUTH set, because it decodes the KDC error instead of a reply (lines 166-173). That message is also free user enumeration.
The same request from a Windows host, if you're already sitting on one:
Rubeus.exe asreproast /outfile:hashes.txt /format:hashcat
Rubeus.exe asreproast /user:alice /dc:dc01.example.local /format:hashcatAnd from Linux, where NetExec does it in one line:
nxc ldap 10.0.0.10 -u users.txt -p '' --asreproast output.txtWith no password it goes straight at the KDC. Give it credentials and it reads the candidate list over LDAP first, which is the difference between guessing names and knowing them.
Then the guessing happens somewhere else entirely, which is the reason this technique keeps working:
hashcat -m 18200 asrep.txt wordlist.txt04 AS-REP roasting versus Kerberoasting
Worth putting side by side, because people conflate them and the preconditions are not the same:
- Credentials: AS-REP roasting needs none, Kerberoasting needs any valid domain account.
- Request: an
AS-REQwith no pre-auth, versus aTGS-REQwithPA-TGS-REQ. - Target precondition:
UF_DONT_REQUIRE_PREAUTH, versus an SPN on the account. - Reply: an AS-REP encrypted in the user's key, versus a TGS-REP encrypted in the service account's key.
The practical consequence: Kerberoasting is a post-foothold move, AS-REP roasting is a pre-foothold one. You can run it from a coffee shop against a DC that answers on 88, with nothing but a name.
Which brings up a detail that matters for both sides. A roast never touches badPwdCount, because nothing failed: the account wasn't asked to authenticate. Pre-authentication failures do lock accounts, which is why spraying and roasting are different risk profiles (Kerbrute's README makes the same distinction between user enumeration and password spraying). If your lockout alarms are the only Kerberos detection you have, this attack is invisible to them.
03 Affected
Any account with the bit set, and any domain controller reachable on TCP or UDP 88. No credentials, no foothold, no user interaction, no CVE. The only precondition on the attacker's side is a name, and names come from LinkedIn, from a username list run through kerbrute userenum, or from the same directory over LDAP.
And the flag is almost never a decision anybody remembers making. The documented reasons are compatibility and legacy clients, and vendor documentation that is still being published is the best evidence that the habit is alive:
- Alfresco Process Services, 25.2, and the same steps in 24.3, 24.4 and 25.1. Step h: "Select the Account tab and enable the Do not require Kerberos preauthentication option in the Account Options section." Step i then runs
ktpasswith-crypto all, which generates every key type, RC4 included. From 24.3 to 25.2, one instruction, and the account it produces is roastable with the weakest etype. That's a vendor guide calling a misconfiguration setup. - VMware ESX 4.x and 5.0 with AD integration. Field fix for "ASN.1 encoding ended unexpectedly" was to clear pre-authentication on the ESX computer account. The post that documents it shows a script sweeping every ESX computer in an OU.
- The troubleshooting thread. Sysadmins debugging a Kerberos failure get told to tick the box as a test, with varying degrees of warning. There's a good r/sysadmin thread where the poster admits they could tick it on their own account and won't, because the account is privileged. Not everyone stops to think about it.
The joint international guide (CISA, NSA, NCSC-UK and friends) is blunt about the population: that setting exists only for systems that don't support Kerberos, which are typically legacy and uncommon. Legacy and uncommon, yet present in enough estates that Microsoft ships a posture assessment for it and Defender for Identity alerts on the roast.
Two honest caveats before the fixes. First, the flag is a writable attribute, so it's also a persistence trick: anyone who can write userAccountControl on an account can make it roastable, and event 5136 is what catches that. Second, the "what breaks" folklore around this flag is mostly folklore. Applications that authenticate by LDAP simple bind cannot be affected by it, because simple bind never performs an AS exchange; kinit with a password performs pre-authentication by default in both MIT and Heimdal. If someone tells you a legacy app needs the flag, ask for the client software and version, and test it before you believe them.
04 Detection
Two hunts, and they answer different questions: which accounts are exposed (static, exact), and who is roasting them (behavioural, noisy).
01 The inventory
Start with the exact answer. This is the query to keep, run weekly, and diff:
$out = 'C:\temp\preauth-inventory.csv'
Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true' -Properties DoesNotRequirePreAuth,
PasswordLastSet, PasswordNeverExpires, ServicePrincipalName, LastLogonDate, Enabled |
Select-Object SamAccountName, Enabled, PasswordLastSet, PasswordNeverExpires,
ServicePrincipalName, LastLogonDate, DistinguishedName |
Sort-Object SamAccountName |
Export-Csv $out -NoTypeInformation The two columns that decide the rest of the work are PasswordLastSet and ServicePrincipalName. An old password with an SPN is a service; an old password with no SPN and a human name is somebody's old admin account, which is worse.
Where PowerShell isn't available (a Linux jump box, an appliance, a script that must not need RSAT), the LDAP filter does the same job:
ldapsearch -x -H ldaps://dc01.example.local -D '[email protected]' -W \
-b 'DC=example,DC=local' \
'(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=4194304))' \
sAMAccountName userAccountControl pwdLastSetNote what the filter leaves out on purpose: disabled accounts stay in the results, unlike Impacket's version. A disabled account with the flag set is a re-enable away from being roastable, and "we disabled it" is not remediation.
If you already run BloodHound, these accounts are in its data with the paths attached, so the same question is one query away.
02 The behavioural hunt
The DC-side signal is event 4768, "A Kerberos authentication ticket (TGT) was requested". Four fields do the work:
PreAuthType:0means logon without pre-authentication;2is the normal encrypted timestamp.Status:0x0, because a roast succeeds.ServiceName:krbtgt.TicketEncryptionType:0x17RC4,0x11AES128,0x12AES256.
The Sigma rule that does the rounds keys on all four, and it's worth reading before you trust it:
# excerpt: the metadata block is trimmed, the detection is verbatim
title: Potential AS-REP Roasting via Kerberos TGT Requests
id: 3e2f1b2c-4d5e-11ee-be56-0242ac120002
logsource:
product: windows
service: security
detection:
selection:
EventID: 4768
TicketEncryptionType: '0x17'
ServiceName: 'krbtgt'
PreAuthType: 0
condition: selection
level: medium The blind spot is TicketEncryptionType: '0x17'. It only fires when the attacker asks for RC4. Ask for AES (etype 17 or 18) and you get 0x11 or 0x12 with the same PreAuthType: 0, and the rule is silent. Write the second rule yourself: 4768, PreAuthType: 0, ServiceName: krbtgt, with your known pre-auth-disabled service accounts allowlisted by name and by source address.
Two things make that second rule survivable. First, 4771 will not help you here: it is the pre-authentication failure event, and Microsoft's 4768 documentation says it is generated instead of 4768 when pre-authentication fails. A roast never fails pre-authentication, so nothing fires. Second, the account's legitimate logons look identical in PreAuthType, because that's the whole point of the flag. The signal isn't the field, it's the deviation: a name that never normally authenticates, appearing from a source address that has never asked for a TGT for it, often in a burst across several accounts.
One more shape worth hunting, and I'll flag it as my reading of the protocol rather than something Microsoft documents: the TGT a roast produces is never used. The session key lives inside the enc-part, which is encrypted with the account's password, so the attacker cannot turn the TGT into service tickets. In the logs that looks like a 4768 followed by nothing: no 4769 for that principal from that address. A real logon gets a TGT and then immediately goes shopping. A roast gets one and walks away.
For the attribute being flipped, two events and one prerequisite:
auditpol /set /subcategory:"User Account Management" /success:enable /failure:enable
auditpol /set /subcategory:"Directory Service Changes" /success:enable /failure:enable
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4738,4742,5136} -MaxEvents 200 |
Where-Object Message -match 'userAccountControl' 4738 is a user account changing and 4742 the computer-account equivalent, which matters because the bit is valid on both. 5136 is the directory write, the one that catches an attacker with write access to the account rather than a helpdesk. Directory Service Changes is usually off, which is why most estates have the first and not the second.
And the check that tells you whether any of this has data behind it at all:
auditpol /get /subcategory:"Kerberos Authentication Service"If success auditing is off, you have no 4768 and no hunt. That is a five-minute fix and it should come before the rule.
If you run Defender for Identity, the detection is built in: external ID 2412, triggered "when an attacker targets accounts with disabled Kerberos preauthentication" to obtain TGT data. Microsoft also ships the find-it-before-they-do half as a posture assessment row, "Remove Don't require Kerberos preauthentication", which is the same query you just ran, with a remediation column.
05 Remediation
01 Inventory, then get an owner per account
Run the inventory above and send each line to whoever owns the application. The question is not "can we clear this?", it's "what client software and version stops working if we do?". In most estates the answer is a shrug, because the flag was ticked during a troubleshooting session nobody remembers, for an application nobody can name. Some accounts will have a real answer, and those are the interesting ones.
02 Clear the flag, and keep a change log
$log = 'C:\temp\preauth-remediation.csv'
Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true' -Properties DoesNotRequirePreAuth |
ForEach-Object {
try {
Set-ADAccountControl -Identity $_ -DoesNotRequirePreAuth $false -ErrorAction Stop
'{0},{1},cleared' -f (Get-Date -Format o), $_.SamAccountName | Add-Content $log
} catch {
'{0},{1},FAILED,{2}' -f (Get-Date -Format o), $_.SamAccountName, $_.Exception.Message |
Add-Content $log
}
} Set-ADAccountControl is the right cmdlet because it sets one flag. Do not use Set-ADUser -Replace @{userAccountControl=...} with a number you worked out in your head: it replaces the whole integer, and clearing NORMAL_ACCOUNT (512) or ACCOUNTDISABLE (2) is how people disable accounts by accident. If you have no AD module, the ADSI equivalent is $u.userAccountControl.Value = $u.userAccountControl.Value -band (-bnot 0x400000), but read the value first and check what else is set in it.
03 Rotate the password in the same window
This is the step that gets skipped, and it's the one that decides whether the change means anything.
- Clearing the flag does not invalidate a hash already captured. The AS-REP you (or an attacker) collected last month is encrypted with the key from the password in force at the time. It stays crackable offline until that password changes.
- Resetting the password does not clear the flag. Roast the account one second after your reset and you get a fresh blob against the new password.
So do both, in one change: clear the flag, reset the password, then verify both. If the account is a service with a fixed password, the rotation is the point at which you move it to 30-plus random characters, which is the next section.
04 For the accounts that genuinely cannot lose it
In order of preference. Most of the legacy offenders can't take option one, which is why they are legacy.
Replace the account with a gMSA. Passwords for gMSAs are 120 characters, complex, randomly generated and rotated automatically, so even if somebody ticked the box on it, the hash is not crackable and the material goes stale. New-ADServiceAccount -Name gmsa-sql01 -DNSHostName gmsa-sql01.corp.example.com -PrincipalsAllowedToRetrieveManagedPassword 'SQL01$', then Install-ADServiceAccount on the host. Windows Server 2025 adds dMSA, which can supersede an existing service account instead of requiring a new one, so this is getting easier for the services that can't be rebuilt.
Keytab, AES only. Re-run ktpass with -crypto AES256-SHA1 rather than -crypto all, set msDS-SupportedEncryptionTypes to 24 (AES128 plus AES256, no RC4), then reset the password, because the AES keys are derived at password change. The application loads the keytab, not a password. This is the fix for the Alfresco pattern above, and for the SAML and Java SSO setups that inherit it.
Long random password plus containment. If it must keep both the flag and a password: 30-plus characters, generated, in the secret store, rotated on a schedule with a name against it. Then take away everything the account doesn't need. Not in Domain Admins, Enterprise Admins, Server Operators, Backup Operators or Account Operators. Logon restricted to the hosts that run the service, with deny logon locally and deny logon through Remote Desktop where the service allows it. The joint guide's line is the one to take to a change board: accounts that must bypass pre-authentication should get the minimum privileges they need and stay out of highly privileged groups like Domain Admins.
Two numbers worth having straight before you brief this, because the internet is sloppy about them. Microsoft's service account guidance says a 14-character minimum and recommends longer; the gMSA figure is 120. The CISA, NSA, NCSC-UK joint guide sets a 30-character minimum for service accounts. The widely repeated "25 characters" has no Microsoft page behind it that I could find, so use 30 and cite the joint guide.
05 Turn RC4 off where you can, and be honest about what it buys
AES-only makes the hash 700 to 1,400 times dearer per guess, which is real and worth having. It does not stop a weak password, and Microsoft says so in as many words: "AES alone is not a solution for brute forcing and needs to be used in combination with strong passwords."
Do it per account first, which is safe and reversible, and leave domain-wide policy alone until you have data:
Set-ADUser -Identity 'svc-legacyapp' -Replace @{ 'msDS-SupportedEncryptionTypes' = 24 }
# then reset the password so AES keys are derivedValue 24 is AES128 plus AES256. Value 28 is RC4 plus both AES. Value 4 is RC4 only. Harvest 4768/4769 encryption types for a month before you touch the domain-wide GPO. The one r/sysadmin post-mortem I read had a consultant flip the domain setting overnight and an estate wake up broken, which is the blast radius you are avoiding.
06 Make it come back loudly
The remediation project ends, and then somebody follows a vendor guide. Two controls close that loop: a daily diff of the inventory (alert on any addition, not the total), and a 5136 subscription filtered to userAccountControl so you see the write when it happens rather than at the next audit.
06 Verification
Prove it rather than assume it, in this order.
The inventory is empty:
Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true' | Measure-ObjectExpect 0. Anything else is either an account you deliberately excepted, or one you missed.
The specific account is still a normal account, and the flag is gone:
Get-ADUser svc-legacyapp -Properties DoesNotRequirePreAuth, userAccountControl, Enabled |
Select-Object SamAccountName, DoesNotRequirePreAuth, userAccountControl, Enabled Read userAccountControl as well as the boolean. DoesNotRequirePreAuth should be False, Enabled True, and the integer should still contain 512 (NORMAL_ACCOUNT). If it doesn't, the cmdlet wasn't what changed it.
The DC refuses to play, which is the only test that counts:
echo 'svc-legacyapp' > one.txt
GetNPUsers.py -dc-ip 10.0.0.10 -no-pass -usersfile one.txt example.local/ Expect User svc-legacyapp doesn't have UF_DONT_REQUIRE_PREAUTH set and no hash line. A hash here means the flag is still set on the account the DC is reading, which is worth checking against replication if the attribute looks clear on one DC.
The log now tells the truthful story:
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4768} -MaxEvents 200 |
Where-Object Message -match 'svc-legacyapp' |
Select-Object TimeCreated, Message PreAuthType should be 2 for that account's logons from now on. Anything at 0 is a roast attempt, an old client, or a flag you missed.
For the accounts you deliberately left with the flag, the verification is a crack test rather than a config check: roast it yourself, then run hashcat against the hash with the wordlist a pentester would use, and confirm it fails. That's the test that means something, because it's the one that will be run against you.
07 Rollback
Re-ticking the flag is a per-account operation with a paper trail, never a fleet-wide revert. If an application genuinely needs it back:
Set-ADAccountControl -Identity 'svc-legacyapp' -DoesNotRequirePreAuth $trueIn the same change: rotate to a 30-plus character random password, add the account to the allowlist in your 4768 rule, and put an owner and a review date in the exception register. A ticked box with a decade-old password is the finding; a ticked box with a 30-character random password and no privileged group membership is a documented risk.
And please don't re-tick the box to make an authentication failure go away without finding the cause. The usual causes are clock skew, an enctype or salt mismatch, an SPN registered on the wrong account, or a keytab generated with the wrong crypto. All of them are findable, and KRB5_TRACE on a Linux client will usually tell you which in one run. The flag doesn't fix any of them, it just moves the failure somewhere quieter and hands out a hash in the meantime.
08 References
- RFC 4120: The Kerberos Network Authentication Service (V5), sections 3.1, 3.1.3, 5.2.7.2, 5.4.2, 5.9.1 and 10
- RFC 4757: The RC4-HMAC Kerberos Encryption Types, section 2 (key derivation) and section 4 (ciphertext layout)
- RFC 3962: Advanced Encryption Standard (AES) Encryption for Kerberos 5, section 4 (PBKDF2 string-to-key, 4096 iterations)
- IANA Kerberos parameters: encryption type registry
- MS-SAMR section 2.2.1.13: UF_FLAG codes (
UF_DONT_REQUIRE_PREAUTH = 0x00400000) - Microsoft: use the UserAccountControl flags to manipulate user account properties (KB 305144)
- Microsoft: Preauthentication) (the compatibility rationale and the ADUC checkbox)
- Set-ADAccountControl (
-DoesNotRequirePreAuth) - Microsoft: Search Filter Syntax (
LDAP_MATCHING_RULE_BIT_AND) - Microsoft: Event 4768 (field tables, including Pre-Authentication Type)
- Microsoft Learn: Detect and remediate RC4 usage (blank
msDS-SupportedEncryptionTypesdefaults to RC4, plus the 2026 deprecation timeline) - Microsoft, Active Directory hardening series part 4: enforcing AES for Kerberos
- Microsoft Security: guidance to help mitigate Kerberoasting (14-character minimum, 120-character gMSA, the AES caveat)
- Microsoft Defender for Identity: credential access alerts and the what's new archive entry for external ID 2412
- Microsoft Defender for Identity: accounts security posture assessment (the "Remove Don't require Kerberos preauthentication" row)
- Microsoft 365 Defender hunting queries: Roasting
- fortra/impacket: examples/GetNPUsers.py
- GhostPack/Rubeus (
asreproast,/format:hashcat, thesamAccountTypefilter inRubeus/lib/Roast.cs) - NetExec: LDAP ASREPRoast
- ropnop/kerbrute (
--hash-file, and why userenum doesn't lock accounts) - hashcat: module 18200 (etype 23 AS-REP), 32100 (etype 17), 32200 (etype 18)
- hashcat 6.2.6 benchmarks on an RTX 4090 (18200 at 3,564.1 MH/s)
- SigmaHQ: Potential AS-REP Roasting via Kerberos TGT Requests
- CISA: Detecting and Mitigating Active Directory Compromises (joint with NSA, ACSC, CCCS, NCSC-UK, NCSC-NZ: the AS-REP section, the 30-character service account minimum)
- CISA eviction strategies: T1558.004
- MITRE ATT&CK T1558.004: AS-REP Roasting (and mitigation M1047)
- Microsoft: group Managed Service Accounts overview and delegated MSAs in Server 2025
- Hyland: Alfresco Process Services 25.2, Kerberos SSO configuration (step h; identical in 24.3, 24.4 and 25.1)
- Disabling Kerberos pre-authentication on ESX computer accounts with PowerShell
- r/sysadmin: AD users "do not require Kerberos Pre-Authentication" report
- r/blueteamsec: AS-REP Roasting
- r/sysadmin: Kerberos preauthentication failures, can't identify the cause
- r/sysadmin: AD Kerberos issues, RC4 notably (the blast radius of enctype enforcement)
One gap I'll flag rather than paper over: I couldn't find a public incident report that names AS-REP roasting as the step that mattered. It shows up in post-incident AD reviews as "credential access consistent with roasting", not in initial-access write-ups, which tells you something about how it's used. If you have a redacted report you can point me at, I'd like to read it.
If you've got one of these accounts that genuinely cannot lose the flag, or you've tested a roast against a computer object, I'd like to hear about it. Corrections and edge cases are always welcome :)
exit(0);
Ship the fix. Prove it’s closed.
If this runbook doesn’t fit your stack, send the CVE — an operator writes a verified path, usually within a business day. Or contribute your own and get co-authored credit.