efficiency.love
← Field Notes
2026-02-20 · Security Lab · 6 min read

I attacked my own network to catch the attack.

The best way to know your alarm works is to break in yourself. So I ran a real Kerberoasting attack against my own Active Directory lab, stole a service account's password in four seconds, then went back to the logs to find the fingerprint it left. That fingerprint became a Splunk rule that now watches for the same move every fifteen minutes. This is the whole loop: simulate the attack, read the scar, write the rule.

Kerberoasting is one of those attacks that feels almost unfair once you see it. In a Windows network, every service (a database, a web app) can have an account behind it, and any logged-in user is allowed to ask the domain for a ticket to talk to that service. Kerberoasting means grabbing those service tickets and cracking them offline to recover the account's password. No alarms trip during the theft, because asking for a ticket is a completely normal thing to do. The attacker just walks off with the encrypted ticket and cracks it on their own machine, at their own pace, where nobody is watching.

I wanted a detection I could trust, so I built the attack first. The lab: a domain controller named DC01 (Windows Server 2022), a Kali Linux box as the attacker, and Splunk (the SIEM, meaning the central log-search and alerting system) pulling every security event off the domain controller. Then I planted the bait: a service account, svc_sqldb, given an SPN and a deliberately terrible password. An SPN is just the service's name tag in the directory, the identifier that says "this account runs the SQL service," and its presence is exactly what makes an account roastable.

the attack, start to finish
enumerate → impacket-GetUserSPNs finds svc_sqldb (has an SPN)
extract → request its TGS ticket, dump the hash (etype 23 / RC4)
crack → hashcat -m 13100 vs rockyou.txt
result → password recovered in 4 seconds

Three steps, and the timer tells the story. First, enumeration: an impacket command (a Python attack toolkit) asks the domain which accounts have SPNs, and svc_sqldb pops up. Second, extraction: request the service ticket for it. That ticket is a TGS, the Kerberos ticket you present to reach a service, and part of it is encrypted with the service account's password. Pull it out and you have a hash to crack. Third, cracking: feed the hash to hashcat against the rockyou wordlist. The password came back in four seconds, because it was weak on purpose. In a real network you would not know the password was weak, but the crack works the same way. That is the whole point.

The tell was one cipher that should never be there

Now the fun half. Every ticket request I made landed in the logs as a Windows security event, code 4769, "a Kerberos service ticket was requested." My starting pile was 191 of them, and almost all of them were legitimate, normal traffic. Finding the attack meant finding what made it different, and the difference was the encryption.

Here is the tell. Modern Kerberos uses AES, a strong cipher. The attack tools ask for the ticket in RC4 instead, an ancient, weak cipher, precisely because RC4 is faster to crack offline. So RC4 showing up in a ticket request is the anomaly: it is the crowbar left at the window. In the logs, AES is encryption type 0x12 and RC4 is 0x17. Filtering my 191 events down to just the RC4 ones (0x17) cut it to four.

splunk detection query
index=main sourcetype="WinEventLog:Security" EventCode=4769 Ticket_Encryption_Type=0x17
| where NOT match(Service_Name, "\$")
| table _time, Account_Name, Service_Name, Client_Address

// result → tuser → svc_sqldb → ::ffff:192.168.56.102 (the Kali host)

One more filter, and this is the detail that separates a working rule from a noisy one. Some of those RC4 requests are real: computer accounts in Active Directory legitimately use RC4 during normal operations. The trick is that machine account names always end in a dollar sign. So the second line, NOT match(Service_Name, "\$"), drops anything ending in $ and keeps only the human and service accounts, the ones an attacker actually targets. What survived was a single row: user tuser requesting a ticket for svc_sqldb, coming from the Kali host's address. The rule found my attack and nothing else.

The theft made no noise. The scar it left was one weak cipher showing up where a strong one should have been.

Then I made it watch forever

A query you have to remember to run is not a detection. So the last step was to hand it to Splunk as a saved alert: "Kerberoasting Detection - RC4 TGS Request," set to run on the schedule */15 * * * *, which is cron for every fifteen minutes. It looks back exactly fifteen minutes each time, fires at High severity if it finds even one result, and adds it to the triggered-alerts list. The look-back window is set to match the run interval on purpose, so it never re-flags the same event twice as it slides forward.

And a detection is only half a job without a plan for the moment it fires. The writeup ends with the triage runbook: confirm the RC4 and non-machine source are real, then scope it, because one roasted SPN is suspicious but a source hitting many SPNs at once is an active campaign. Identify what the targeted account can reach, contain by resetting that service account's password to 25 or more random characters (which instantly makes any stolen hash worthless), investigate the source host, and document the timeline. The rule is honest about its own blind spots too: legacy apps that genuinely need RC4 will trip it, so real deployment means baselining which accounts legitimately use RC4 and quietly excluding them.

Why this is on the portfolio

If you are sizing me up, here is the point. This is the full blue-team loop done end to end, not a copied signature. I stood up the network, played the attacker, watched what the attack left behind in real logs, and wrote the rule that catches it, tuned down to the one dollar sign that separates a real threat from routine noise. Simulate, observe, detect, deploy. The four-second crack is left in the story on purpose, because it is the reason the detection has to exist.

// Security Lab is the honest story behind the detections in the build log. Real attacks run in a controlled lab, real logs, the boring tuning left in on purpose. Simulated against lab.local; no production systems touched.

One entry in a longer build log.

See the build log
// or reach me at hello@efficiency.love