<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Known Good</title><description>Cybersecurity writing by Kajal Dhanjal.</description><link>https://knowngood.au</link><language>en-au</language><item><title>What an MFA prompt actually proves.</title><link>https://knowngood.au/writing/what-an-mfa-prompt-actually-proves</link><guid isPermaLink="true">https://knowngood.au/writing/what-an-mfa-prompt-actually-proves</guid><description>Seven sign-in methods, what each one attests, and how each one gets beaten.</description><pubDate>Sun, 04 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every MFA prompt answers one narrow question: did someone have this factor, at this moment?&lt;/p&gt;
&lt;p&gt;That is a useful question. It is also not the question most of us think MFA is answering. We treat a completed MFA prompt as proof that the right person is signing in, on purpose, to the right site. Most methods prove none of those three things.&lt;/p&gt;
&lt;p&gt;The difference matters, because attackers do not need to break the factor. They only need to work in the space between what the factor proves and what we assume it proves.&lt;/p&gt;
&lt;p&gt;Each row below is a common sign-in method. The columns ask what it proves, what it does not, and how that gap gets used.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;What it does not prove&lt;/th&gt;
&lt;th&gt;How it gets beaten&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMS code&lt;/td&gt;
&lt;td&gt;Someone could read a message sent to that phone number&lt;/td&gt;
&lt;td&gt;That the number is still on the user&apos;s SIM, or where the code was typed&lt;/td&gt;
&lt;td&gt;SIM swap, SS7 interception, real-time phishing [1], [2]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice call code&lt;/td&gt;
&lt;td&gt;Someone could answer a call to that phone number&lt;/td&gt;
&lt;td&gt;Same as SMS: the number is not the phone&lt;/td&gt;
&lt;td&gt;SIM swap, SS7 interception, real-time phishing [2]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authenticator app code (TOTP)&lt;/td&gt;
&lt;td&gt;Someone had the app or its seed when the code was generated&lt;/td&gt;
&lt;td&gt;Which site the code was typed into&lt;/td&gt;
&lt;td&gt;Real-time phishing relay [1], [2]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push approval&lt;/td&gt;
&lt;td&gt;Someone holding the enrolled device tapped Approve&lt;/td&gt;
&lt;td&gt;That they started the sign-in&lt;/td&gt;
&lt;td&gt;Push bombing until someone taps Approve [2], [3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push with number matching&lt;/td&gt;
&lt;td&gt;The approver could see a sign-in screen showing the number&lt;/td&gt;
&lt;td&gt;That the sign-in screen was the real one&lt;/td&gt;
&lt;td&gt;A relay that passes the real number through to a fake page [2], [4], [10]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passkey or FIDO2 security key&lt;/td&gt;
&lt;td&gt;A private key scoped to this site signed a challenge, and the browser bound the request to the real origin&lt;/td&gt;
&lt;td&gt;Who holds the session after sign-in&lt;/td&gt;
&lt;td&gt;Session token theft after sign-in, fallback to a weaker method, help desk reset [3], [6], [7], [8]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows Hello for Business&lt;/td&gt;
&lt;td&gt;A key tied to this device signed in, unlocked by a local PIN or biometric&lt;/td&gt;
&lt;td&gt;Who holds the session after sign-in&lt;/td&gt;
&lt;td&gt;Session token theft after sign-in, fallback to a weaker method, help desk reset [3], [8], [9]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Three things the table shows&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The phone number is not the phone.&lt;/strong&gt; SMS and voice codes go to a number, and a number can be moved to another SIM. NIST now treats the public phone network as a &lt;em&gt;restricted&lt;/em&gt; authenticator, and advises verifiers to consider signals like SIM change and number porting before relying on it [1]. CISA ranks SMS and voice as the weakest form of MFA, vulnerable to phishing, SS7 attacks and SIM swaps [2].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Codes and taps do not know where they are going.&lt;/strong&gt; An authenticator code is valid wherever it is typed. A push approval is valid for whichever sign-in triggered it. NIST is direct about it: OTP and out-of-band authentication are not phishing resistant [1]. Number matching fixed one specific problem. It stops someone approving a prompt they never started, because they now need a number from a screen in front of them [4]. Microsoft now has it on for every Authenticator push, with no opt out [5]. But a relay sits in the middle of the real session, so the number the victim sees is the genuine one, arriving on a page that is not [10]. CISA&apos;s own ranking says it plainly: number matching is resistant to push bombing and still vulnerable to phishing [2].&lt;/p&gt;
&lt;p&gt;In 2022 Microsoft described a campaign that had targeted more than 10,000 organisations since September 2021 using exactly this gap [10]. The attacker sat between the user and the real sign-in page, relayed everything both ways, and kept the session cookie at the end. Microsoft&apos;s description of the outcome applies to every row above: the attacker &quot;gets authenticated to a session on the user&apos;s behalf, regardless of the sign-in method&quot; [10].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Only origin-bound methods check the address, and even they stop at the door.&lt;/strong&gt; Passkeys, FIDO2 keys and Windows Hello for Business are different in kind. A WebAuthn credential works only with the entity it was registered to, and the origin the browser actually sees is part of what gets signed [7]. A lookalike domain gets nothing it can use. That is why CISA names FIDO/WebAuthn and PKI-based methods as the phishing-resistant forms of MFA [2], and why Microsoft&apos;s phishing-resistant authentication strength allows only Windows Hello for Business or platform credential, FIDO2 keys and multifactor certificate-based authentication [9].&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/what-an-mfa-prompt-actually-proves/figure-1-relay.webp&quot; alt=&quot;Figure 1: A lookalike page relays a code, push or number match to the real site and keeps the session cookie. A passkey has nothing to sign for the lookalike domain.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 1. Same lookalike page, two different outcomes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But look at the third column for those rows. Strong MFA protects the sign-in. It does not protect the session that comes out of it. Microsoft&apos;s guidance on token theft puts it this way: replaying a token &quot;issued to an identity that has already completed multifactor authentication&quot; satisfies MFA, and access is granted [8]. Commodity credential theft malware steals browser cookies for exactly this reason [8]. And if an account can still fall back to SMS, or a help desk will reset the factor over the phone, the attacker simply picks the weaker door [3].&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/what-an-mfa-prompt-actually-proves/figure-2-three-questions.webp&quot; alt=&quot;Figure 2: Seven MFA methods against three questions. Every method shows someone had a factor, only passkeys and Windows Hello for Business confirm the real site, and none protect the session after sign-in.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 2. Three questions, seven methods. Only two methods check the site, and none protect the session after sign-in.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;What happened at Uber&lt;/h2&gt;
&lt;p&gt;In September 2022, Uber published what happened to them [11]. Uber says it is likely the attacker bought the contractor&apos;s corporate password on the dark web, after malware had infected the contractor&apos;s personal device. The attacker then kept trying to log in. Each attempt sent the contractor a login approval request, and at first that blocked access. Eventually, the contractor accepted one, and the attacker was in [11].&lt;/p&gt;
&lt;p&gt;Run that through the table. The password was already gone. The push prompt proved exactly what the push row says it proves: someone holding the enrolled device tapped Approve. It never proved that the person tapping had started the sign-in, and nothing in the flow asked.&lt;/p&gt;
&lt;p&gt;Uber said it believes the attacker is affiliated with Lapsus$ [11]. The next year, a joint advisory on Scattered Spider described the same playbook at scale: repeated MFA prompts until someone presses Accept, SIM swaps, and social engineering of IT help desks to reset passwords or MFA tokens [3]. That advisory has since been updated with investigations running through June 2025, and now carries seven agencies including Australia&apos;s ACSC [3].&lt;/p&gt;
&lt;h2&gt;Why people press Approve&lt;/h2&gt;
&lt;p&gt;It is tempting to read the Uber story as a user who should have known better. I do not think that is the useful reading.&lt;/p&gt;
&lt;p&gt;For most people, Approve is a button pressed often and almost always correctly. Repetition turns it into a reflex, not a decision. A push prompt asks someone to make a security judgement with no context, in a second, often while they are doing something else. Repeated prompts turn a refusal into a cost: the phone keeps buzzing until you make it stop. And the help desk is staffed by people whose job is to get colleagues back to work quickly.&lt;/p&gt;
&lt;p&gt;None of those are failures of the people involved. They are properties of the design. A factor that relies on a person&apos;s judgement in that moment inherits every pressure that moment carries. The methods at the bottom of the table are stronger partly because they stop asking the human to make that call. The browser checks the origin. The person never has to.&lt;/p&gt;
&lt;h2&gt;What to do with this&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Put phishing-resistant MFA where the damage would be worst first.&lt;/strong&gt; Admins, finance and anyone who can approve payments. In Entra, that is the phishing-resistant authentication strength in Conditional Access [9].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove the fallback, not just the weak default.&lt;/strong&gt; If SMS or voice remains a registered method, it is still a way in [1], [3].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat the help desk as part of MFA.&lt;/strong&gt; Factor resets need verification that cannot be done by someone who has read the user&apos;s LinkedIn profile [3].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assume a strong sign-in can still lead to a stolen session.&lt;/strong&gt; Plan detection and response for token replay, not only for failed sign-ins [8], [10].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MFA is still one of the best controls we have. It just answers a smaller question than the one we keep asking it. Before trusting a prompt, ask what it actually proved.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;[1] National Institute of Standards and Technology, &quot;Digital Identity Guidelines: Authentication and Authenticator Management,&quot; NIST SP 800-63B-4, 2025. [Online]. Available: https://pages.nist.gov/800-63-4/sp800-63b.html&lt;/p&gt;
&lt;p&gt;[2] Cybersecurity and Infrastructure Security Agency, &quot;Implementing Phishing-Resistant MFA,&quot; Fact sheet, Oct. 2022. [Online]. Available: https://www.cisa.gov/sites/default/files/publications/fact-sheet-implementing-phishing-resistant-mfa-508c.pdf&lt;/p&gt;
&lt;p&gt;[3] Federal Bureau of Investigation et al., &quot;Scattered Spider,&quot; Joint Cybersecurity Advisory AA23-320A, Nov. 16, 2023, updated Jul. 29, 2025. [Online]. Available: https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a&lt;/p&gt;
&lt;p&gt;[4] Cybersecurity and Infrastructure Security Agency, &quot;Implementing Number Matching in MFA Applications,&quot; Fact sheet, Oct. 2022. [Online]. Available: https://www.cisa.gov/sites/default/files/publications/fact-sheet-implement-number-matching-in-mfa-applications-508c.pdf&lt;/p&gt;
&lt;p&gt;[5] Microsoft, &quot;How number matching works in multifactor authentication push notifications for Authenticator,&quot; Microsoft Learn. [Online]. Available: https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-mfa-number-match&lt;/p&gt;
&lt;p&gt;[6] FIDO Alliance, &quot;Passkeys.&quot; [Online]. Available: https://fidoalliance.org/passkeys/&lt;/p&gt;
&lt;p&gt;[7] W3C, &quot;Web Authentication: An API for accessing Public Key Credentials, Level 3.&quot; [Online]. Available: https://www.w3.org/TR/webauthn-3/&lt;/p&gt;
&lt;p&gt;[8] Microsoft Security, &quot;Token tactics: How to prevent, detect, and respond to cloud token theft,&quot; Microsoft Security Blog, Nov. 16, 2022. [Online]. Available: https://www.microsoft.com/en-us/security/blog/2022/11/16/token-tactics-how-to-prevent-detect-and-respond-to-cloud-token-theft/&lt;/p&gt;
&lt;p&gt;[9] Microsoft, &quot;Conditional Access authentication strengths,&quot; Microsoft Learn. [Online]. Available: https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths&lt;/p&gt;
&lt;p&gt;[10] Microsoft Security, &quot;From cookie theft to BEC: Attackers use AiTM phishing sites as entry point to further financial fraud,&quot; Microsoft Security Blog, Jul. 12, 2022. [Online]. Available: https://www.microsoft.com/en-us/security/blog/2022/07/12/from-cookie-theft-to-bec-attackers-use-aitm-phishing-sites-as-entry-point-to-further-financial-fraud/&lt;/p&gt;
&lt;p&gt;[11] Uber, &quot;Security update,&quot; Uber Newsroom, Sep. 16, 2022, updated Sep. 19, 2022. [Online]. Available: https://www.uber.com/newsroom/security-update/&lt;/p&gt;
</content:encoded><category>Social engineering</category></item><item><title>The MITRE ATT&amp;CK most people are learning was retired in October 2025.</title><link>https://knowngood.au/writing/mitre-attack-v18-v19-detection-strategies</link><guid isPermaLink="true">https://knowngood.au/writing/mitre-attack-v18-v19-detection-strategies</guid><description>What v18 and v19 changed, and how to read the framework now.</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week someone messaged me asking a simple question: what should they read to understand MITRE ATT&amp;amp;CK properly, beyond my blog posts and the matrix?&lt;/p&gt;
&lt;p&gt;I started typing a list. Then I stopped, because the honest answer is that most of what they would find teaches a version of ATT&amp;amp;CK that no longer exists.&lt;/p&gt;
&lt;h3&gt;What changed&lt;/h3&gt;
&lt;p&gt;On 28 October 2025, v18 was released, in which MITRE removed the Detections field and retired Data Sources entirely [1], [2]. Two new object types replaced them. A &lt;strong&gt;Detection Strategy&lt;/strong&gt; describes the adversary behaviour you are trying to catch. It points to &lt;strong&gt;Analytics&lt;/strong&gt;, which are written for one platform each and link to named log sources, which now live directly on Data Components rather than as separate objects [2].&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/mitre-attack-v18-v19-detection-strategies/img1.webp&quot; alt=&quot;MITRE ATT&amp;amp;CK Enterprise matrix before v18, with Defense Evasion as a tactic&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;MITRE ATT&amp;amp;CK prior to the change&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The reason being the old guidance was too vague to act on. One sentence of detection advice had to cover Windows, Linux, cloud and containers at once, and it usually resolved to something like &quot;use Sysmon&quot; [2].&lt;/p&gt;
&lt;p&gt;Then v19 landed on 28 April 2026 and moved the layer above it. Defense Evasion was retired as a tactic and split into Stealth, which inherited TA0005, and Defense Impairment [3], [4]. Stealth covers blending in. Defense Impairment covers switching your controls off.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/mitre-attack-v18-v19-detection-strategies/img2.webp&quot; alt=&quot;MITRE ATT&amp;amp;CK v19 Enterprise matrix with Stealth and Defense Impairment as tactics&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;MITRE ATT&amp;amp;CK v19&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;What that looks like on one technique&lt;/h2&gt;
&lt;p&gt;Open T1078, Valid Accounts, the technique behind most of the intrusions you might have read about this year.&lt;/p&gt;
&lt;p&gt;Under Tactics it now says &lt;strong&gt;Stealth&lt;/strong&gt;. The description below it still says Defense Evasion, because the prose has not caught up with the restructure yet [5]. I read that page twice before I trusted what I was seeing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/mitre-attack-v18-v19-detection-strategies/img3.webp&quot; alt=&quot;MITRE ATT&amp;amp;CK Valid Accounts page listing Stealth under Tactics&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Scroll down and there is no detection paragraph. There is one Detection Strategy, &lt;strong&gt;DET0560, Detection of Valid Account Abuse Across Platforms&lt;/strong&gt;, and it fans out into five analytics [5], [6]:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Analytic&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;What it reads&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AN1543&lt;/td&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;Anomalous logon patterns, abnormal logon types, inconsistent geography or timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AN1544&lt;/td&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;SSH logins, sudo and su abuse, service account anomalies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AN1545&lt;/td&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Interactive and remote logins at unusual hours, unexpected child processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AN1546&lt;/td&gt;
&lt;td&gt;Identity provider&lt;/td&gt;
&lt;td&gt;Impossible travel, risky sign-ins, repeated MFA attempts and failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AN1547&lt;/td&gt;
&lt;td&gt;Containers&lt;/td&gt;
&lt;td&gt;Service accounts and kubeconfigs used from unexpected nodes or IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Five analytics. Five different sets of telemetry. Under the old model this was one technique with one line of advice, and you could tick it off a coverage map without ever asking which of those five you could actually see.&lt;/p&gt;
&lt;p&gt;That is the real change. The framework now forces you to answer the platform question before it will give you anything useful.&lt;/p&gt;
&lt;h2&gt;How to read it&lt;/h2&gt;
&lt;p&gt;The habit the matrix teaches is top-down. Pick a tactic, pick a technique, tick the box. It feels like coverage, and it measures nothing.&lt;/p&gt;
&lt;p&gt;The new structure lets you go the other way, and the other way is honest:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Start with a log source you actually collect. Sign in logs. Sysmon. Auth logs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the Data Components it feeds, then the Analytics that read them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Walk up to the Detection Strategies, and from there to the techniques.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What you get is not a coverage map. It is a list of the techniques you are genuinely positioned to see, and the far longer list you are not. AN1546 is available to almost anyone with an identity provider. AN1547 is available to nobody who is not running Kubernetes. The old model let you blur those two together. This one will not.&lt;/p&gt;
&lt;h2&gt;What to &lt;em&gt;ACTUALLY&lt;/em&gt; read&lt;/h2&gt;
&lt;p&gt;Not the tutorials. Read MITRE&apos;s own blog posts on the new detection model [1], [2], then the v19 release notes and the Defense Evasion crosswalk [3], [4]. After that, open Detection Strategies and Analytics on the ATT&amp;amp;CK site directly [6], [7] and pick one technique you care about. An hour spent walking a single technique down to its log sources will teach you more than a week of matrix diagrams.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Why I bothered writing it down&lt;/h2&gt;
&lt;p&gt;I never sent that list of links.&lt;/p&gt;
&lt;p&gt;Instead I went and read the object model properly, walked one technique down to its log sources, and found that a good part of what I would have confidently told them a month ago was out of date.That would be wrong at the level of which objects the framework even has.&lt;/p&gt;
&lt;p&gt;That is why I write in public. Publishing makes me check things I would otherwise assume, and being corrected in the open is a far cheaper way to learn than being quietly wrong in front of someone who knows better. Every post I put up is a bet that the checking is worth the exposure. So far it has been.&lt;/p&gt;
&lt;p&gt;If any of this was useful to you, there is one small thing you can do with that.&lt;/p&gt;
&lt;p&gt;I am a finalist in the AISA Cyber Security Awards 2026, Student of the Year category. Voting closes at midnight on Friday 18 September, and only AISA members can vote, so if that is you and this post earned its place in your afternoon, this is where to say so:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://aisa.secure-platform.com/awards/gallery/rounds/82008/details/11878&quot;&gt;&lt;strong&gt;https://aisa.secure-platform.com/awards/gall&lt;/strong&gt;&lt;/a&gt;&lt;a href=&quot;https://aisa.secure-platform.com/awards/gallery/rounds/82008/details/11878&quot;&gt;&lt;strong&gt;ery?roundId=82008&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The link is member gated. Log in first or it will bounce you to a sign in page.&lt;/p&gt;
&lt;p&gt;And if you are the person who sent me that message: this is the answer. Sorry it took a week.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;[1] A. L. Robertson, &quot;ATT&amp;amp;CK v18: Detection Strategies, More Adversary Insights,&quot; MITRE ATT&amp;amp;CK, Oct. 28, 2025. [Online]. Available: &lt;a href=&quot;https://medium.com/mitre-attack/att-ck-v18-detection-strategies-more-adversary-insights-8f82d839ee9e&quot;&gt;https://medium.com/mitre-attack/att-ck-v18-detection-strategies-more-adversary-insights-8f82d839ee9e&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[2] L. Crumpton, &quot;What Comes After Detection Rules? Smarter Detection Strategies in ATT&amp;amp;CK,&quot; MITRE ATT&amp;amp;CK, Oct. 22, 2025. [Online]. Available: &lt;a href=&quot;https://medium.com/mitre-attack/smarter-detection-strategies-in-attack-7e6738fec31f&quot;&gt;https://medium.com/mitre-attack/smarter-detection-strategies-in-attack-7e6738fec31f&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[3] A. L. Robertson, &quot;ATT&amp;amp;CK v19: The Defense Evasion Split, ICS Sub-Techniques, New AI and Social Engineering Coverage, and Detection Strategies for Mobile,&quot; MITRE ATT&amp;amp;CK, Apr. 29, 2026. [Online]. Available: &lt;a href=&quot;https://medium.com/mitre-attack/att-ck-v19-the-defense-evasion-split-ics-sub-techniques-new-ai-social-engineering-coverage-ff329cb65d66&quot;&gt;https://medium.com/mitre-attack/att-ck-v19-the-defense-evasion-split-ics-sub-techniques-new-ai-social-engineering-coverage-ff329cb65d66&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[4] The MITRE Corporation, &quot;Updates: April 2026,&quot; MITRE ATT&amp;amp;CK. [Online]. Available: &lt;a href=&quot;https://attack.mitre.org/resources/updates/updates-april-2026/&quot;&gt;https://attack.mitre.org/resources/updates/updates-april-2026/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[5] The MITRE Corporation, &quot;Valid Accounts, Technique T1078, Enterprise,&quot; MITRE ATT&amp;amp;CK. [Online]. Available: &lt;a href=&quot;https://attack.mitre.org/techniques/T1078/&quot;&gt;https://attack.mitre.org/techniques/T1078/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[6] The MITRE Corporation, &quot;Detection Strategies,&quot; MITRE ATT&amp;amp;CK. [Online]. Available: &lt;a href=&quot;https://attack.mitre.org/detectionstrategies/&quot;&gt;https://attack.mitre.org/detectionstrategies/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[7] The MITRE Corporation, &quot;Analytics,&quot; MITRE ATT&amp;amp;CK. [Online]. Available: &lt;a href=&quot;https://attack.mitre.org/analytics/&quot;&gt;https://attack.mitre.org/analytics/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;**All technique, strategy and analytic details verified against ATT&amp;amp;CK content v19.2 on 9 September 2026.**&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>MITRE ATT&amp;CK</category></item><item><title>Anatomy of an identity compromise</title><link>https://knowngood.au/writing/anatomy-of-an-identity-compromise</link><guid isPermaLink="true">https://knowngood.au/writing/anatomy-of-an-identity-compromise</guid><description>A login-only intrusion, step by step, and where the detection actually lives.</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Why nothing fired&lt;/h3&gt;
&lt;p&gt;When a detection misses, the first instinct is to blame the rule. Wrong threshold, wrong table, wrong logic. Sometimes that is exactly right.&lt;/p&gt;
&lt;p&gt;But there is a class of intrusion where every rule can be correct, every event can be present, every log can be enabled and retained, and still nothing fires because no single event in the sequence is anomalous. The attacker never drops a file. Never runs a binary. Never touches an endpoint you have an agent on. They sign in, they change a setting, they read some mail. Each action is one an ordinary user performs on an ordinary Tuesday.&lt;/p&gt;
&lt;p&gt;This is the login-only intrusion, and it is the shape most identity compromises take.&lt;/p&gt;
&lt;p&gt;What follows is the anatomy: seven steps, the event each one writes, and why each one reads as normal in isolation. Then a real intrusion that ran exactly this way. Then the part that matters: where the detection actually lives, which is never in the row.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/anatomy-of-an-identity-compromise/img1.webp&quot; alt=&quot;Flow diagram of a login-only intrusion in seven steps, from credential obtained to mail read, with the log each step writes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 1 — Anatomy of a login-only intrusion. Seven steps. One writes nothing. The rest write events that look like work.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What each step writes to your logs&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Event written&lt;/th&gt;
&lt;th&gt;Why it reads as normal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Credential obtained externally&lt;/td&gt;
&lt;td&gt;Nothing in your tenant&lt;/td&gt;
&lt;td&gt;The intrusion starts before your telemetry does&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Spray attempts&lt;/td&gt;
&lt;td&gt;Sign-in log failures: 50126, 50053, 50055&lt;/td&gt;
&lt;td&gt;Failed sign-ins are constant background noise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Sign-in succeeds&lt;/td&gt;
&lt;td&gt;Interactive sign-in log: location, Conditional Access result&lt;/td&gt;
&lt;td&gt;Valid credential from a plausible place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4a&lt;/td&gt;
&lt;td&gt;Auth method registered&lt;/td&gt;
&lt;td&gt;Entra ID audit log, Authentication Methods category [13]&lt;/td&gt;
&lt;td&gt;Indistinguishable from someone setting up a new phone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4b&lt;/td&gt;
&lt;td&gt;Consent granted / app added&lt;/td&gt;
&lt;td&gt;Entra audit log: Consent to application, Add service principal, Add OAuth2PermissionGrant&lt;/td&gt;
&lt;td&gt;Self-service consent is ordinary in most tenants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;App uses its token&lt;/td&gt;
&lt;td&gt;Non-interactive sign-in log&lt;/td&gt;
&lt;td&gt;No user, no authentication factor, a log view most people never open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Inbox rule created&lt;/td&gt;
&lt;td&gt;Unified audit log: New-InboxRule, Set-InboxRule [6]&lt;/td&gt;
&lt;td&gt;Users create rules constantly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Mail read&lt;/td&gt;
&lt;td&gt;MailItemsAccessed&lt;/td&gt;
&lt;td&gt;The account reading its own mail is what the account does&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A few of these deserve more than a row.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 2 is not a blind spot; it is an attention problem.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The failures are there, with codes attached: AADSTS50126 for an invalid username or password and AADSTS50053 when an account locks out after too many attempts [1]. Microsoft&apos;s own Sentinel analytic rule for password spray keys on exactly this family of failure codes [2]. So the telemetry exists, and a detection for it exists. The question is whether anyone is watching the tenant the spray landed in. Hold that thought for the next section.&lt;/p&gt;
&lt;p&gt;One caveat if you build on this: Microsoft states plainly that these error codes are subject to change and that applications taking a dependency on the numbers will break over time [1]. Pin them in a lookup you control rather than hard-coding them into a rule.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Steps 4a and 4b are the same objective reached two different ways, and the difference is the most practically useful thing in this post.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Both steps answer the same attacker question: how do I keep this access after the password I stole gets rotated? They answer it differently, and the two answers fail to different responses.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;4a attaches the persistence to the user.&lt;/em&gt; A new authentication method (an authenticator app or a phone number) is registered against the compromised account. Microsoft&apos;s audit logs for authentication methods exist precisely so admins can confirm which users have registered which methods [13]. From the attacker&apos;s side, this is cheap and quiet, and it survives a password reset on its own. From the defender&apos;s side, it is also the easier of the two to undo: the method belongs to the user object, so reviewing and stripping that user&apos;s registered methods closes it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;4b attaches the persistence to an application.&lt;/em&gt; Consenting to an application creates a service principal in the tenant with permissions of its own, and both the application registration and the permission grant are written as their own audit entries [5]. This is the part worth sitting with: that application does not authenticate as the user. It holds its own credentials and its own permissions. It never needed the stolen password, and it does not care that the password has changed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which means the standard credential-compromise runbook – reset the password, revoke the user&apos;s sessions, and re-register MFA – closes 4a completely and does nothing whatsoever to 4b. You can run that runbook, mark the ticket resolved, and leave the attacker&apos;s access entirely intact.&lt;/p&gt;
&lt;p&gt;This is not hypothetical. It is the branch the intrusion in the next section took.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Step 5 is in a log most people have never opened.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Non-interactive sign-ins are a subset of the sign-in logs, performed by a client app on behalf of a user without the user providing an authentication factor, including a client app exchanging an OAuth 2.0 refresh token for an access token [3]. The interactive view, by contrast, is the one that records a person completing an authentication challenge [4]. If your queries only read the interactive view, this step is invisible to you even though it is fully logged.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3&gt;On step 7: Microsoft&apos;s documentation does not agree with itself&lt;/h3&gt;
&lt;p&gt;One Microsoft Learn page places &lt;code&gt;MailItemsAccessed&lt;/code&gt; in Audit (Standard), enabled by default for users with an Office 365 or Microsoft 365 E3 or E5 licence [7]. Another says capturing &lt;code&gt;MailItemsAccessed&lt;/code&gt; operations requires E5 [8]. A third says mailbox audit events return only for E5 users when you search the unified audit log [9].&lt;/p&gt;
&lt;p&gt;The event is real either way. Whether it reaches you is a licensing question, and the answer you get depends on which page you read. If you support tenants on Business Premium, do not assume this row is populated. Test it in a tenant you control before you rely on it in an investigation and note that these records are not retroactive, so discovering the gap during an incident is discovering it too late.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;An intrusion that ran on identity alone&lt;/h2&gt;
&lt;p&gt;In late November 2023, the Russian state-sponsored actor Microsoft tracks as Midnight Blizzard ran a password spray against a legacy, non-production test tenant account that did not have MFA enabled [10].&lt;/p&gt;
&lt;p&gt;From that foothold, the actor moved through the identity plane and nowhere else. They found a legacy test OAuth application that already held elevated permissions in the corporate environment. They created additional malicious OAuth applications and created a new user account for the sole purpose of granting consent to them. They then used the legacy test application to grant the Office 365 Exchange Online &lt;code&gt;full_access_as_app&lt;/code&gt; role and authenticated to Exchange Online through those applications to reach corporate mailboxes [10].&lt;/p&gt;
&lt;p&gt;Microsoft has said the actor accessed a small percentage of corporate email accounts, including members of the senior leadership team and staff in cybersecurity and legal functions and exfiltrated emails and attached documents [11]. Microsoft detected the intrusion on 12 January 2024.&lt;/p&gt;
&lt;p&gt;No malware. No endpoint. No file written to disk anywhere an EDR agent could see it. Every step is a row in the table above, and step 4b is the branch it took.&lt;/p&gt;
&lt;p&gt;Two things worth saying plainly about this case.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The first is that the spray in step 2 was not invisible.&lt;/strong&gt; It was against a legacy, non-production test tenant. Microsoft has not said what monitoring that tenant had, and I am not going to claim it had none. What is documented is the interval: the spray began in late November; detection came in January. Whatever the reason, the events and the attention were not in the same place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The second is scale.&lt;/strong&gt; This is a nation-state actor against Microsoft&apos;s own corporate environment. If you run IT for a forty-seat accounting firm, the adversary in your tenant is not this one, and the lesson does not transfer by analogy. What transfers is the mechanism. The steps are the same steps, the log sources are the same log sources, and the Exchange Online application permissions used here exist in every tenant that has Exchange Online.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/anatomy-of-an-identity-compromise/img2.webp&quot; alt=&quot;Four pairs of sign-in and audit events, each harmless alone, joined by the time window that links them&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Figure 2 — Where the detection lives. No event on either side is suspicious. Every pair is.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The detection is never in the row&lt;/h2&gt;
&lt;p&gt;Look back at the table. Every one of those events, taken alone, is something a legitimate user or a legitimate application does. You cannot alert on any of them without alerting on your own organisation going about its day.&lt;/p&gt;
&lt;p&gt;The signal is in the join.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unfamiliar-location sign-in × security info registered in the same short window.&lt;/strong&gt; Users do register new authentication methods. They usually do it from the office, on a device they have used before, not four minutes after a first-ever sign-in from a new country.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New authentication method × every subsequent sign-in using only that method.&lt;/strong&gt; A user who adds a phone still has their old method and still uses it sometimes. An attacker who adds one uses nothing else.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consent granted × first token use from a different address than the granting session.&lt;/strong&gt; The person who consented and the thing that immediately started pulling data are not in the same place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inbox rule created × that session already flagged as unfamiliar.&lt;/strong&gt; Rule creation is noise. Rule creation inside a session your own risk engine already had doubts about is not.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these needs a time window. And the window is where the false positives live. Too tight and you miss the patient attacker. Too loose and you catch every user who registered a phone the same week they travelled. There is no correct number I can give you, it depends on the size of the tenant, how mobile the workforce is, and how much noise the team can absorb. Picking that number, and then revisiting it, is the actual engineering work. The joins are the easy part.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What changes Monday&lt;/h2&gt;
&lt;p&gt;I do not administer tenants for anyone&apos;s clients. What follows is what Microsoft&apos;s own documentation and the Midnight Blizzard disclosure add up to for the people who do.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check whether your queries read the non-interactive sign-in log.&lt;/strong&gt; If they only read the interactive view, step 5 is invisible to you regardless of what else you have configured [3].&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Find out what&lt;/strong&gt; &lt;code&gt;Consent to application&lt;/code&gt;&lt;strong&gt;,&lt;/strong&gt; &lt;code&gt;Add service principal&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;Add OAuth2PermissionGrant&lt;/code&gt; &lt;strong&gt;look like in your tenants right now.&lt;/strong&gt; Microsoft&apos;s own hunting guidance treats these as events that should be rare [12]. If they are not rare in your environment, you need to know why before you can use them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Confirm&lt;/strong&gt; &lt;code&gt;MailItemsAccessed&lt;/code&gt; &lt;strong&gt;is actually populated for the licences your clients hold.&lt;/strong&gt; Not from the documentation from a search in the tenant. See the callout above.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test whether your credential-reset runbook closes 4b.&lt;/strong&gt; Rotating a password, revoking sessions and re-registering MFA does not remove a consented application&apos;s permissions. Run the runbook against a test account with an app consented to it, and confirm what is still standing at the end. If your incident response stops at the password, it stops too early.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ask which of your tenants nobody is actually watching.&lt;/strong&gt; Legacy tenants, test tenants, tenants inherited from an acquisition, and the one spun up for a project that finished. In the case above, the way in was a non-production tenant that had been left behind.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Confirmed and open&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Confirmed:&lt;/strong&gt; every event and operation name in the table is documented by Microsoft and cited below. The Midnight Blizzard sequence is drawn from Microsoft&apos;s own security blog and MSRC disclosures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Open:&lt;/strong&gt; the &lt;code&gt;MailItemsAccessed&lt;/code&gt; licensing position, which Microsoft&apos;s documentation states in three different ways. I have cited all three rather than picking one. If you know which is currently correct, I would like to hear it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;[1] Microsoft, &quot;Microsoft Entra authentication and authorization error codes,&quot; Microsoft identity platform documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes&quot;&gt;https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[2] Microsoft, &quot;SigninPasswordSpray.yaml,&quot; Microsoft Sentinel analytic rules, GitHub. [Online]. Available: &lt;a href=&quot;https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rules/SigninPasswordSpray.yaml&quot;&gt;https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/Microsoft%20Entra%20ID/Analytic%20Rules/SigninPasswordSpray.yaml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[3] Microsoft, &quot;Non-interactive sign-in logs,&quot; Microsoft Entra ID documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-noninteractive-sign-ins&quot;&gt;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-noninteractive-sign-ins&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[4] Microsoft, &quot;What are interactive user sign-ins in Microsoft Entra?,&quot; Microsoft Entra ID documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-interactive-sign-ins&quot;&gt;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-interactive-sign-ins&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[5] Microsoft, &quot;Audit log activities,&quot; Microsoft Purview documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-log-activities&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-log-activities&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[6] Microsoft, &quot;Identify who modified mailbox rules,&quot; Microsoft Purview documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-log-identify-mailbox-rules&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-log-identify-mailbox-rules&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[7] Microsoft, &quot;Use MailItemsAccessed to investigate compromised accounts,&quot; Microsoft Purview documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-log-investigate-accounts&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-log-investigate-accounts&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[8] Microsoft, &quot;Investigate shared mailbox activities using audit logs,&quot; Microsoft Purview documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-log-identify-mailboxes&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-log-identify-mailboxes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[9] Microsoft, &quot;Search the audit log to investigate common support issues,&quot; Microsoft Purview documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-troubleshooting-scenarios&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-troubleshooting-scenarios&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[10] Microsoft, &quot;Midnight Blizzard: Guidance for responders on nation-state attack,&quot; Microsoft Security Blog, 25 Jan. 2024. [Online]. Available: &lt;a href=&quot;https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/&quot;&gt;https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[11] Microsoft Security Response Center, &quot;Microsoft Actions Following Attack by Nation State Actor Midnight Blizzard,&quot; Jan. 2024. [Online]. Available: &lt;a href=&quot;https://www.microsoft.com/en-us/msrc/blog/2024/01/microsoft-actions-following-attack-by-nation-state-actor-midnight-blizzard&quot;&gt;https://www.microsoft.com/en-us/msrc/blog/2024/01/microsoft-actions-following-attack-by-nation-state-actor-midnight-blizzard&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[12] Microsoft, &quot;ConsentToApplicationDiscovery.yaml,&quot; Microsoft Sentinel hunting queries, GitHub. [Online]. Available: &lt;a href=&quot;https://github.com/Azure/Azure-Sentinel/blob/master/Hunting%20Queries/AuditLogs/ConsentToApplicationDiscovery.yaml&quot;&gt;https://github.com/Azure/Azure-Sentinel/blob/master/Hunting%20Queries/AuditLogs/ConsentToApplicationDiscovery.yaml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[13] Microsoft, &quot;Microsoft Entra audit log activity reference,&quot; Microsoft Entra ID documentation. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-audit-activities&quot;&gt;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-audit-activities&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Identity and Entra ID</category></item><item><title>What your logs can&apos;t see</title><link>https://knowngood.au/writing/what-your-logs-can-t-see</link><guid isPermaLink="true">https://knowngood.au/writing/what-your-logs-can-t-see</guid><description>Nine common sources and the gaps they carry by design.</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;The premise&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;&quot;A detection rule can only fire on evidence that reached the platform.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That sounds obvious written down, and it&apos;s routinely skipped in practice. When coverage gets audited, it gets audited at the rule layer, which usually consists of questions like how many techniques we&apos;ve mapped, how many analytics are enabled, and which ATT&amp;amp;CK cells are green. That&apos;s one layer too high. Underneath every rule is a log source, and every log source is a lens: built to answer a particular question well, which means built not to answer others.&lt;/p&gt;
&lt;p&gt;Those aren&apos;t bugs, and they aren&apos;t vendor failures. They&apos;re design boundaries, chosen deliberately for cost, performance, privacy or licensing. But they set the ceiling on what your detections can ever see, and most of them stay invisible until the day something doesn&apos;t fire.&lt;/p&gt;
&lt;p&gt;Below is what nine commonly trusted sources cover and what each one structurally cannot.&lt;/p&gt;
&lt;h3&gt;The table&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Log source&lt;/th&gt;
&lt;th&gt;What it answers reliably&lt;/th&gt;
&lt;th&gt;What it structurally cannot see&lt;/th&gt;
&lt;th&gt;Why it&apos;s built that way&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Entra ID sign-in logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Who authenticated, from where, on what client, and how conditional access and MFA resolved&lt;/td&gt;
&lt;td&gt;What the token holder does once the token is issued. An attacker who steals a session or refresh token can impersonate the user until that token expires or is revoked, carrying a session that already satisfied MFA [1]&lt;/td&gt;
&lt;td&gt;Sign-in logs are an &lt;em&gt;authentication&lt;/em&gt; record, not an activity record. Activity lives in a different log. Retention is also licence-bound: seven days on Entra ID Free and thirty on P1/P2, and the change isn&apos;t retroactive [2], [3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Windows Security log event 4688&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;That a process was created, its image path, its parent, and the account that spawned it&lt;/td&gt;
&lt;td&gt;What the process was actually told to do. The Process Command Line field is empty unless you explicitly turn it on, so you get &lt;code&gt;powershell.exe ran&lt;/code&gt; without the argument that mattered [4]&lt;/td&gt;
&lt;td&gt;Command lines are logged in plain text and often contain secrets, so Microsoft ships the field disabled and leaves the trade-off to you [5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sysmon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Process lineage, network connections, image loads, file and registry activity: the richest native Windows telemetry available&lt;/td&gt;
&lt;td&gt;Anything its configuration excludes. Sysmon is disabled by default and produces almost nothing until you install it &lt;em&gt;and&lt;/em&gt; give it an XML config [6]&lt;/td&gt;
&lt;td&gt;Sysmon is a filter engine, not a firehose. Community baselines are deliberate about what they drop to keep volume survivable [7] and a filtered-out event is silently absent, not empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EDR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Behavioural detection, process trees and response actions everywhere the agent is installed&lt;/td&gt;
&lt;td&gt;Everywhere the agent isn&apos;t. Network appliances, hypervisors, unmanaged VMs, contractor laptops and BYOD aren&apos;t lightly covered; they&apos;re absent. Microsoft found that over 90% of ransomware attacks reaching the ransom stage used unmanaged devices for initial access or remote encryption [8]&lt;/td&gt;
&lt;td&gt;Agent-based tooling requires something to install the agent on. Devices you don&apos;t manage are, by definition, devices you can&apos;t instrument&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firewall / NGFW&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connections: source, destination, port, volume, allow or deny, and session duration&lt;/td&gt;
&lt;td&gt;Whatever crossed the connection. TLS hides the payload, so without inspection your firewall sees an encrypted blob to a legitimate-looking destination [9]. Behind NAT, egress logs give you the gateway address, not the host&lt;/td&gt;
&lt;td&gt;Decryption is compute-intensive, breaks certificate-pinned applications, and carries privacy and legal weight, so most environments inspect selectively or not at all [9]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Email gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inbound mail at the delivery boundary: sender, attachments, links, verdicts&lt;/td&gt;
&lt;td&gt;Everything after delivery, and everything that never crosses the boundary. Mailbox rules, auto-forwards, reads, and internal-to-internal mail don&apos;t transit the gateway&lt;/td&gt;
&lt;td&gt;The gateway is a perimeter control. Post-delivery actions are recorded by mailbox auditing in a different system entirely, and only if that auditing is configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Microsoft 365 unified audit log&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tenant-wide user and admin activity across Exchange, SharePoint, Teams and Entra&lt;/td&gt;
&lt;td&gt;Anything past your retention window, and historically events your licence tier didn&apos;t emit. Audit (Standard) retains 180 days; Audit (Premium) extends to one year, but only for specific workloads and only for users holding an E5 or equivalent licence [10], [11]&lt;/td&gt;
&lt;td&gt;This gap isn&apos;t technical; it&apos;s commercial. Which is what makes it easy to miss during design and expensive to discover during an incident&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS CloudTrail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Control-plane operations: who created, modified or deleted a resource, and who signed in to the console&lt;/td&gt;
&lt;td&gt;What happened &lt;em&gt;inside&lt;/em&gt; the resource. Data events, meaning S3 object reads and writes, Lambda invocations and DynamoDB item operations, are not logged by default and must be explicitly enabled [12]&lt;/td&gt;
&lt;td&gt;Data events are high-volume and separately billed. The default is the cheap one, and the default is where most accounts stay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DNS resolver logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Name resolution that passes through your resolver; a near-complete map of what your endpoints try to reach&lt;/td&gt;
&lt;td&gt;Resolution that goes around it. With DNS-over-HTTPS, the query is encrypted inside ordinary HTTPS on port 443, bypasses your resolver entirely, and gateway logging is usually not possible [13]&lt;/td&gt;
&lt;td&gt;DoH was designed for user privacy against network observers. Your enterprise resolver is a network observer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;How to read this&lt;/h3&gt;
&lt;p&gt;Two failure modes fall out of the table, and they look nothing alike from the console.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The first is &lt;em&gt;&lt;strong&gt;a rule written against a source that can&apos;t carry it&lt;/strong&gt;&lt;/em&gt;. The logic is sound, the ATT&amp;amp;CK mapping is correct, and the syntax validates, but the evidence never arrives. The rule stays silent, and silence gets read as safety.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The second is &lt;em&gt;&lt;strong&gt;confidence drawn from a source covering less than assumed&lt;/strong&gt;&lt;/em&gt;, almost always because a default was never changed. The rule fires, the tuning looks healthy, and the blind spot sits inside the alerts you&apos;re already receiving. This one is worse, because the alerts create the impression of coverage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither failure produces an error message. That&apos;s the point of writing them down.&lt;/p&gt;
&lt;h3&gt;The gap that costs people the breach&lt;/h3&gt;
&lt;p&gt;The clearest documented case is Storm-0558. In June 2023, a US federal agency detected suspicious activity in its Microsoft 365 tenant by spotting &lt;code&gt;MailItemsAccessed&lt;/code&gt; events carrying an unexpected application ID [14]. That single event type was how the intrusion surfaced at all.&lt;/p&gt;
&lt;p&gt;Other affected organisations couldn&apos;t run the same check. At the time, &lt;code&gt;MailItemsAccessed&lt;/code&gt; was gated behind premium licensing, so tenants without it had no way to see the mailbox access that had already happened [14]. The detection logic wasn&apos;t the variable. The log was.&lt;/p&gt;
&lt;p&gt;Microsoft subsequently expanded the event to Standard-tier customers and raised default audit retention from 90 to 180 days [10], [14]. The fix is real, and it arrived after the incident that proved it was needed, which is the whole argument in one example: &lt;em&gt;&lt;strong&gt;nobody noticed the boundary until an intrusion sat on the wrong side of it&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;What actually changes&lt;/h3&gt;
&lt;p&gt;For anyone running detections across client environments, four things follow directly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Name the source under every rule.&lt;/strong&gt; For each detection you own, write down which log it depends on and which gap that log carries. If you can&apos;t name the source, you can&apos;t state the coverage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit the defaults you inherited, not the ones you chose.&lt;/strong&gt; CLI auditing, Sysmon config, mailbox auditing, and CloudTrail data events are all off or minimal out of the box, all cheap to enable, and all invisible until they matter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Treat licence tiers as an architecture decision.&lt;/strong&gt; Retention windows and event availability are coverage decisions made by procurement, usually without anyone in detection being asked.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add sources to close named gaps, not to raise volume.&lt;/strong&gt; More telemetry isn&apos;t more coverage. Telemetry that answers a question you couldn&apos;t previously answer is.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&apos;s a reason this keeps biting. In the 2026 SANS detection engineering survey of 307 practitioners, cloud-native environments were named the number one coverage gap – more than two and a half times any other environment [15]. That isn&apos;t a rule-writing problem. It&apos;s a visibility problem wearing a rule-writing costume.&lt;/p&gt;
&lt;p&gt;Your coverage stops where your telemetry does. It&apos;s worth knowing exactly where that is before an incident tells you.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;p&gt;[1] Microsoft, &quot;Token Protection by using Microsoft Entra ID,&quot; Microsoft Community Hub, Jun. 2025. [Online]. Available: &lt;a href=&quot;https://techcommunity.microsoft.com/blog/coreinfrastructureandsecurityblog/token-protection-by-using-microsoft-entra-id-/4302207&quot;&gt;https://techcommunity.microsoft.com/blog/coreinfrastructureandsecurityblog/token-protection-by-using-microsoft-entra-id-/4302207&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[2] Microsoft, &quot;Microsoft Entra data retention&quot;, Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-reports-data-retention&quot;&gt;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-reports-data-retention&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[3] Microsoft, &quot;Microsoft Entra monitoring and health FAQ&quot;, Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reports-faq&quot;&gt;https://learn.microsoft.com/en-us/entra/identity/monitoring-health/reports-faq&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[4] Microsoft, &quot;4688(S) A new process has been created,&quot; Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4688&quot;&gt;https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4688&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[5] Microsoft, &quot;Command line process auditing&quot;, Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing&quot;&gt;https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[6] Microsoft, &quot;Enable and configure Sysmon in Windows&quot;, Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/security/operating-system-security/sysmon/how-to-enable-sysmon&quot;&gt;https://learn.microsoft.com/en-us/windows/security/operating-system-security/sysmon/how-to-enable-sysmon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[7] SwiftOnSecurity, &quot;sysmon-config: A Sysmon configuration file template with default high-quality event tracing,&quot; GitHub. [Online]. Available: &lt;a href=&quot;https://github.com/SwiftOnSecurity/sysmon-config&quot;&gt;https://github.com/SwiftOnSecurity/sysmon-config&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[8] Microsoft, &quot;10 essential insights from the Microsoft Digital Defense Report 2024&quot;, Microsoft Security Insider. [Online]. Available: &lt;a href=&quot;https://www.microsoft.com/en-us/security/security-insider/threat-landscape/10-essential-insights-from-the-microsoft-digital-defense-report-2024&quot;&gt;https://www.microsoft.com/en-us/security/security-insider/threat-landscape/10-essential-insights-from-the-microsoft-digital-defense-report-2024&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[9] Amazon Web Services, &quot;TLS inspection configuration for encrypted traffic and AWS Network Firewall&quot;, AWS Security Blog. [Online]. Available: &lt;a href=&quot;https://aws.amazon.com/blogs/security/tls-inspection-configuration-for-encrypted-traffic-and-aws-network-firewall/&quot;&gt;https://aws.amazon.com/blogs/security/tls-inspection-configuration-for-encrypted-traffic-and-aws-network-firewall/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[10] Microsoft, &quot;Learn about auditing solutions in Microsoft Purview&quot;, Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-solutions-overview&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-solutions-overview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[11] Microsoft, &quot;Manage audit log retention policies&quot;, Microsoft Learn. [Online]. Available: &lt;a href=&quot;https://learn.microsoft.com/en-us/purview/audit-log-retention-policies&quot;&gt;https://learn.microsoft.com/en-us/purview/audit-log-retention-policies&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[12] Amazon Web Services, &quot;Logging data events&quot;, AWS CloudTrail User Guide. [Online]. Available: &lt;a href=&quot;https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html&quot;&gt;https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[13] National Security Agency, &quot;Adopting Encrypted DNS in Enterprise Environments,&quot; Cybersecurity Information Sheet, Jan. 2021. [Online]. Available: &lt;a href=&quot;https://media.defense.gov/2021/Jan/14/2002564889/-1/-1/0/CSIADOPTINGENCRYPTEDDNSUOO102904_21.PDF&quot;&gt;https://media.defense.gov/2021/Jan/14/2002564889/-1/-1/0/CSI&lt;em&gt;ADOPTING&lt;/em&gt;ENCRYPTED&lt;em&gt;DNS&lt;/em&gt;U&lt;em&gt;OO&lt;/em&gt;102904_21.PDF&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[14] Cybersecurity and Infrastructure Security Agency, &quot;Microsoft Expanded Cloud Logs Implementation Playbook&quot;, CISA, Jan. 2025. [Online]. Available: &lt;a href=&quot;https://www.cisa.gov/sites/default/files/2025-01/microsoft-expanded-cloud-logs-implementation-playbook-508c.pdf&quot;&gt;https://www.cisa.gov/sites/default/files/2025-01/microsoft-expanded-cloud-logs-implementation-playbook-508c.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[15] SANS Institute and Anvilogic, &quot;The State of Detection Engineering 2026: What the Data Reveals About Accuracy, Automation, and AI Adoption,&quot; Jun. 2026. [Online]. Available: &lt;a href=&quot;https://www.sans.org/white-papers/state-detection-engineering-2026&quot;&gt;https://www.sans.org/white-papers/state-detection-engineering-2026&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Detection engineering</category></item><item><title>FortiBleed had around twenty people. JadePuffer had an agent. The way in was identical.</title><link>https://knowngood.au/writing/fortibleed-jadepuffer-coverage-audit</link><guid isPermaLink="true">https://knowngood.au/writing/fortibleed-jadepuffer-coverage-audit</guid><description>A coverage audit of my own Sentinel detections.</description><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;The thesis I had to throw out&lt;/h3&gt;
&lt;p&gt;Two intrusions dominated the last month, and the coverage of both led with AI.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;FortiBleed: an initial-access operation running on around twenty people with a defined division of labour.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;JadePuffer: an agent that reached a production database with nobody at the keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The AI is real in both. I know because I set out to argue the opposite: that it was mostly narrative, and the reporting had gone looking for a machine where there were people. A week of verification killed that thesis. SOCRadar&apos;s second FortiBleed whitepaper documents AI integrated into the crew&apos;s own workflow: penetration testing, vulnerability research, attack automation, and ransomware development [1]. That&apos;s tooling in the operational workflow – not hype applied afterwards by reporters.&lt;/p&gt;
&lt;p&gt;What replaced my thesis is narrower and more useful. AI is an operational multiplier for people who are already skilled, and it did not lower the floor for FortiBleed. That operation is closer to a small software company than a gang [2]. And the way to gain initial access never changed: valid credentials, exposed interfaces, and unpatched internet-facing services.&lt;/p&gt;
&lt;p&gt;JadePuffer sits at the other end of every axis you&apos;d normally use to rank an adversary in a graph: one agent, pointed at an exposed Langflow instance, issuing extortion demands on its own [3].&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Around twenty skilled operators at one end. One agent at the other. Both walked through the same neglected door.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I write &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;Sentinel detections&lt;/a&gt; and publish them; there are eight in my lab, documented, including the ones that went wrong. Later in this post I run every detection I own against both of these.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Twenty people and a diagnostic command&lt;/h2&gt;
&lt;p&gt;FortiBleed is not a Fortinet product-vulnerability story, and reading it as one is the first place a defender&apos;s attention is misdirected.&lt;/p&gt;
&lt;p&gt;The operation, as attributed by SOCRadar to Lynx and INC, Russian-speaking and active since at least February 2026, built a custom Golang tool called &apos;&lt;strong&gt;FortigateSniffer&lt;/strong&gt;&apos; around the native FortiOS &lt;code&gt;diagnose sniffer packet&lt;/code&gt; command, capturing authentication traffic across roughly two dozen protocols [2], [4]. Not an exploit. A supported administrative feature, driven with valid credentials, on devices the operators were already authenticated to. A FortiCloud SSO SAML bypass, CVE-2026-24858, disclosed in January, has been discussed as a possible contributor to initial access in a subset of cases and remains under investigation [4].&lt;/p&gt;
&lt;p&gt;Around 430,000 FortiGate firewalls were targeted across 659 documented harvest cycles that exposed over 110 million credentials — RADIUS, NTLM and Kerberos material among them [10]. Administrative access was achieved on 409 of them, and on 354 the full chain was completed: VPN compromise, domain controller, and domain admin [2]. Where credentials weren&apos;t already in hand, they came from cracking harvested hashes offline, which is why &lt;em&gt;&lt;strong&gt;&quot;we rotated the passwords&quot;&lt;/strong&gt;&lt;/em&gt; is a weaker sentence than it sounds and why the checklist at the end has something specific to say about it.&lt;/p&gt;
&lt;p&gt;The part that reads like a company rather than a crew came out of a recovered internal tracking document: around twenty people with a clear division of labour, a small core of lead operators driving the high-impact intrusions, backed by specialists and support staff [2]. Target inventories. Automation scripts. Operational artefacts.&lt;/p&gt;
&lt;p&gt;None of that requires a firewall zero-day. It requires organisation, division of labour, and infrastructure – the same things any product team needs.The AI sits inside that operation as leverage, not as a substitute for the people running it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/fortibleed-jadepuffer-coverage-audit/img1.webp&quot; alt=&quot;FortiBleed chain: harvest auth traffic, crack hashes offline, authenticate with credentials that already work, capture in transit, hand off to Lynx&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;One agent and thirty-one seconds&lt;/h2&gt;
&lt;p&gt;JadePuffer, documented by Sysdig in early July, started at an internet-facing Langflow instance and CVE-2025-3248 — a vulnerability with a patch available and an exploit that requires no particular artistry [3].&lt;/p&gt;
&lt;p&gt;From there it pivoted to an internet-facing production server running MySQL and Alibaba Nacos, authenticating with root MySQL credentials that did not come from the victim environment [3]. That detail is worth sitting with: somewhere upstream, a human compromise supplied those credentials. It exploited known Nacos authentication weaknesses dating back to 2021 and inserted a backdoor admin account.&lt;/p&gt;
&lt;p&gt;The moment that got the coverage: a login failed, the agent read the error, worked out what it had got wrong, and regained access in thirty-one seconds with nobody assisting it. Over the course of the intrusion it generated more than 600 distinct, purposeful payloads, narrating its own reasoning in plain language as it went [3].&lt;/p&gt;
&lt;p&gt;The clock is the difference. FortiBleed ran for months with a roster. JadePuffer ran a recovery cycle in under a minute with no one watching. Same year, same class of exposed service, opposite ends of every axis you&apos;d normally use to rank an adversary.&lt;/p&gt;
&lt;p&gt;And the ransom itself is where the machine shows its seams. Sysdig cannot say whether the Bitcoin address it produced is a real wallet or a string recalled from documentation it was trained on [3]. An operation that can self-correct in thirty-one seconds may not have been able to get paid.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/fortibleed-jadepuffer-coverage-audit/img2.webp&quot; alt=&quot;JadePuffer chain: exposed Langflow, CVE-2025-3248, root MySQL login, backdoor in Nacos, login fails, back in within 31 seconds&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Capability went up. AI sophistication didn&apos;t.&lt;/h2&gt;
&lt;p&gt;The tempting reading of those two sections is a ladder: crude attackers are at the bottom, organised crews are above them, autonomous agents are at the top, and AI is how you climb. The evidence doesn&apos;t support a ladder. It supports a spectrum, and AI is distributed across it unevenly.&lt;/p&gt;
&lt;p&gt;LAMEHUG is the hinge. A state-sponsored actor embedded LLM prompting directly into malware, the most advanced adversary category we track, doing the most novel-sounding thing available and got no meaningful gain in effectiveness or sophistication for it [5]. Meanwhile, CrowdStrike&apos;s assessment across 2025 is that AI primarily enhances established tradecraft rather than creating original attack vectors and that adversaries who use it successfully generally need enough technical proficiency to catch the model&apos;s errors [5]. Capability and AI sophistication are two different axes, and they do not track each other.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ACTOR&lt;/th&gt;
&lt;th&gt;AI in Play&lt;/th&gt;
&lt;th&gt;WHAT IT BOUGHT THEM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JadePuffer&lt;/td&gt;
&lt;td&gt;The agent is the operator.&lt;/td&gt;
&lt;td&gt;31 seconds to self-correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FortiBleed&lt;/td&gt;
&lt;td&gt;Tooling inside workflow.&lt;/td&gt;
&lt;td&gt;Leverage on skilled people&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LAMEHUG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;LLM embedded in malware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No measurable gain&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;All three started at an exposed internet-facing service&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Capability rises down the table. What the AI contributes falls.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;What all eight of my detections would have seen&lt;/h2&gt;
&lt;p&gt;A threat narrative is a targeting instruction for defender attention. That&apos;s the whole claim, and it isn&apos;t a claim about journalists. It&apos;s about what happens in your head between reading a headline and opening a query window.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;Autonomous AI ransomware&quot;&lt;/em&gt; sends you hunting AI: agentic processes, model API egress, and curiosity about something new on the box. &lt;em&gt;&quot;Around twenty people and a native sniffer command&quot;&lt;/em&gt; sends you somewhere else entirely – credential reuse, accounts authenticating from places they shouldn&apos;t, and administrative features being used exactly as designed by someone who shouldn&apos;t have them. Same fortnight, same reader, opposite queries. Only one of those two framings was ever going to be written up as interesting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SCATTERED SPIDER&lt;/strong&gt; is the external version of this. Domain credentials were reached in around three hours; one managed endpoint was touched, and a help desk was talked into the access [5]. No AI anywhere in the chain. It is the least unprecedented intrusion imaginable in write-up terms and one of the most effective in terms of outcome, and it maps onto exactly the telemetry most teams already collect and aren&apos;t querying because attention is somewhere more exciting, for instance, zero-days.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;WHAT YOU READ ABOUT&lt;/th&gt;
&lt;th&gt;WHAT THEY ACTUALLY USED&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;An autonomous AI agent&lt;/td&gt;
&lt;td&gt;An exposed Langflow instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI-written ransomware&lt;/td&gt;
&lt;td&gt;Root credentials that already worked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An AI-enabled criminal crew&lt;/td&gt;
&lt;td&gt;A built-in command and cracked hashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A sophisticated adversary&lt;/td&gt;
&lt;td&gt;A phone call to a help desk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Which is a comfortable argument to make about other people. So I ran it against my own lab.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Eight documented rules. Two real intrusions. One table. To be exact about the method: I did not replay either intrusion&apos;s telemetry through Sentinel — I audited each rule against the log sources it reads and the events these two intrusions would have produced.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Reads&lt;/th&gt;
&lt;th&gt;FortiBleed&lt;/th&gt;
&lt;th&gt;JadePuffer&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Brute force&lt;/td&gt;
&lt;td&gt;Windows Security 4625&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Hashes were cracked offline; the login that followed was valid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;PowerShell flags&lt;/td&gt;
&lt;td&gt;Windows Security&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Windows signature, neither platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Registry persistence&lt;/td&gt;
&lt;td&gt;Sysmon EID 13&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;No Windows registry in either intrusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Recon burst&lt;/td&gt;
&lt;td&gt;Windows process names&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Windows process names only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MFA registration from an untrusted IP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Entra sign-in logs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Closest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Wrong platform, right logic&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Shadow-AI execution&lt;/td&gt;
&lt;td&gt;Sysmon EID 1, &lt;code&gt;.exe&lt;/code&gt; list&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Windows endpoint telemetry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;AI egress&lt;/td&gt;
&lt;td&gt;Sysmon EID 3&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;JadePuffer ran on Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Agentic process lineage&lt;/td&gt;
&lt;td&gt;Sysmon EID 1&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;Miss&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;cmd.exe&lt;/code&gt; on a Langflow box&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All three of my AI-track detections — shadow-AI execution, AI egress, and agentic process lineage — miss both intrusions. Every one.&lt;/p&gt;
&lt;p&gt;The honest reason isn&apos;t that they&apos;re badly written. It&apos;s telemetry scope. Every AI rule I own reads Windows endpoint telemetry: Sysmon Event IDs 1 and 3, and a process name list, an &lt;code&gt;.exe&lt;/code&gt;. JadePuffer ran on Linux — Langflow, MySQL, Nacos, no &lt;code&gt;cmd.exe&lt;/code&gt; anywhere near it. FortiBleed ran on FortiOS with credentials that were valid. A rule keyed to a Windows process starting cannot see either of those, no matter how good the logic inside it is. &lt;em&gt;&lt;strong&gt;A detection is only as good as your understanding of what its data source can&lt;/strong&gt;&lt;/em&gt; see, which I wrote about in &lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot&quot;&gt;Part 7&lt;/a&gt; [6], about a gap in my own Sysmon baseline, and did not expect to be quoting back at myself this soon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The row that mattered is Part 5.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back&quot;&gt;Part 5&lt;/a&gt; correlates a sign-in from an untrusted IP with a security-info registration inside a thirty-minute window [7]. I built it as Variant B, a downgraded, free-tier version because Entra P2 wouldn&apos;t activate and I couldn&apos;t have the signal I actually wanted. I wrote it before the AI track existed, when I wasn&apos;t thinking about AI at all.&lt;/p&gt;
&lt;p&gt;Strip the platform off it, and that is FortiBleed&apos;s exact shape: a legitimate account, arriving from somewhere it shouldn&apos;t, doing something that matters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;let TrustedIPs = dynamic([&quot;&amp;lt;office/home IP 1&amp;gt;&quot;, &quot;&amp;lt;office/home IP 2&amp;gt;&quot;]);
let UntrustedSignIns = SigninLogs
| where IPAddress !in (TrustedIPs)
| where ResultType == 0
| project SignInTime = TimeGenerated, UserPrincipalName, IPAddress, Location;
let MFARegistrations = AuditLogs
| where ActivityDisplayName == &quot;User started security info registration&quot;
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| project RegTime = TimeGenerated, UserPrincipalName;
UntrustedSignIns
| join kind=inner MFARegistrations on UserPrincipalName
| where RegTime between (SignInTime .. (SignInTime + 30m))
| summarize arg_min(SignInTime, *) by UserPrincipalName, IPAddress
| project SignInTime, RegTime, UserPrincipalName, IPAddress, Location
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wrong log source. Right detection logic. It generalises because it keys on a behaviour, which doesn&apos;t age, rather than a technology, which does.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-8-watching-for-agents-not-just-attackers&quot;&gt;Part 8&lt;/a&gt; does have a real-world match; it just isn&apos;t either of these. It&apos;s the Nx npm compromise: malicious packages driving victims&apos; own local AI command-line tools into generating credential-theft commands; more than 90 customers were affected [5]. My agentic-lineage rule [8] fires on that shape. It got almost no coverage.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What changes Monday&lt;/h2&gt;
&lt;p&gt;I don&apos;t manage FortiGates for anyone&apos;s clients. What follows is what CISA&apos;s guidance [9], SOCRadar&apos;s documentation and my own coverage audit add up to for the people who do - sourced on each item, so you can check them rather than take my word for it. Here&apos;s the checklist:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Rotation isn&apos;t recovery.&lt;/strong&gt; Cracked hashes mean the old password was already usable. Rotation closes one door, not the intrusion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Confirm the PBKDF2 rehash per device — following CISA&apos;s alert, not me.&lt;/strong&gt; PBKDF2 replaced SHA-256-with-salt in FortiOS 7.2.11, 7.4.8 and 7.6.1, but a stored password stays on the old scheme until that admin logs in again, and a hidden old-password setting can retain the previous hash. On 7.2.x and 7.4.x, &lt;code&gt;login-lockout-upon-weaker-encryption&lt;/code&gt; purges the residue. Verify device by device; assume nothing from the version number alone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Log process lineage on local AI binaries.&lt;/strong&gt; Not the model traffic — the parent/child chain. The Nx case is a developer&apos;s own CLI tool being driven, and lineage is what shows it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Put valid-account abuse ahead of malware signatures.&lt;/strong&gt; Most detections now are malware-free, and valid accounts dominate cloud intrusions. Neither of these two intrusions would have tripped a signature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Audit your detection backlog for narrative bias.&lt;/strong&gt; Sort by what you built after reading something alarming, then check which log sources those rules can actually reach.&lt;/p&gt;
&lt;p&gt;Both of these intrusions came through a service that was exposed, reachable and authenticated. What was on the other end, &lt;em&gt;twenty people or one agent&lt;/em&gt;, changed nothing about the door.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Still open&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Whether JadePuffer&apos;s Bitcoin address is a real wallet or a string recalled from training data — Sysdig can&apos;t distinguish the two.&lt;/p&gt;
&lt;p&gt;CVE-2026-24858&apos;s role in FortiBleed initial access — under investigation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;col /&gt;&lt;col /&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[1]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;“FortiBleed Unmasked: Inside the Lynx and INC Ransomware Operation - SOCRadar,” &lt;em&gt;SOCRadar® Cyber Intelligence Inc.&lt;/em&gt;, July 09, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://socradar.io/resources/whitepapers/fortibleed-unmasked-inside-the-lynx-and-inc-ransomware-operation/&quot;&gt;https://socradar.io/resources/whitepapers/fortibleed-unmasked-inside-the-lynx-and-inc-ransomware-operation/&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[2]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Ameer Owda, “SOCRadar Links FortiBleed Campaign to INC and Lynx Ransomware Operations,” &lt;em&gt;SOCRadar® Cyber Intelligence Inc.&lt;/em&gt;, July 2026. &lt;a target=&quot;_self&quot; href=&quot;https://socradar.io/blog/fortibleed-inc-lynx-ransomware-link/&quot;&gt;https://socradar.io/blog/fortibleed-inc-lynx-ransomware-link/&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[3]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;M. Clark, “Jadepuffer: Agentic Ransomware for Automated Database Extortion,” &lt;a target=&quot;_self&quot; href=&quot;http://Sysdig.com&quot;&gt;&lt;em&gt;Sysdig.com&lt;/em&gt;&lt;/a&gt;, July 2026. &lt;a target=&quot;_self&quot; href=&quot;https://sysdig.com/blog/jadepuffer-agentic-ransomware-for-automated-database-extortion&quot;&gt;https://sysdig.com/blog/jadepuffer-agentic-ransomware-for-automated-database-extortion&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[4]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;Ameer Owda, “FortiBleed: The Campaign That Cracked 86,644 Firewalls,” &lt;em&gt;SOCRadar® Cyber Intelligence Inc.&lt;/em&gt;, June 16, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://socradar.io/blog/fortibleed-fortinet-firewalls-compromised&quot;&gt;https://socradar.io/blog/fortibleed-fortinet-firewalls-compromised&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[5]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;“CrowdStrike 2026 Global Threat Report,” 2026. Accessed: July 21, 2026. [Online]. Available: &lt;a target=&quot;_self&quot; href=&quot;https://www.crowdstrike.com/explore/2026-global-threat-report?utm_medium=dir&quot;&gt;https://www.crowdstrike.com/explore/2026-global-threat-report?utm_medium=dir&lt;/a&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[6]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;K. Dhanjal, “Building a Sentinel Detection Lab, Part 7: The Detection That Taught Me My Own Telemetry’S Blind Spot,” &lt;em&gt;Kajal’S Security Notes&lt;/em&gt;, June 30, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot&quot;&gt;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[7]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;K. Dhanjal, “Building a Sentinel Detection Lab, Part 5: MFA Registration, a Missing License, and the First Detection That Talks Back,” &lt;em&gt;Kajal’S Security Notes&lt;/em&gt;, June 21, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back&quot;&gt;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[8]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;K. Dhanjal, “Building a Sentinel Detection Lab, Part 8: Watching for Agents, Not Just Attackers,” &lt;em&gt;Kajal’S Security Notes&lt;/em&gt;, June 30, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-8-watching-for-agents-not-just-attackers&quot;&gt;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-8-watching-for-agents-not-just-attackers&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[9]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;“CISA Urges Hardening Fortinet Devices After Reports of Credential Exposure | CISA,” &lt;em&gt;Cybersecurity and Infrastructure Security Agency CISA&lt;/em&gt;, June 22, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://www.cisa.gov/news-events/alerts/2026/06/18/cisa-urges-hardening-fortinet-devices-after-reports-credential-exposure&quot;&gt;https://www.cisa.gov/news-events/alerts/2026/06/18/cisa-urges-hardening-fortinet-devices-after-reports-credential-exposure&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;p&gt;[10]&lt;/p&gt;&lt;/td&gt;&lt;td&gt;&lt;p&gt;“Dismantling FortiBleed: Inside a Russian Fortinet Compromise Operation - SOCRadar,” &lt;em&gt;SOCRadar® Cyber Intelligence Inc.&lt;/em&gt;, June 22, 2026. &lt;a target=&quot;_self&quot; href=&quot;https://socradar.io/resources/whitepapers/dismantling-fortibleed-inside-a-russian-fortinet-compromise-operation/&quot;&gt;https://socradar.io/resources/whitepapers/dismantling-fortibleed-inside-a-russian-fortinet-compromise-operation/&lt;/a&gt; (accessed July 21, 2026).&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Consulted, not cited&lt;/h3&gt;
&lt;p&gt;Risky Business Features, &quot;FortiBleed: the bleeding edge of AI cybercrime,&quot; podcast episode with E. Seker.&lt;br /&gt;
&lt;a href=&quot;https://open.spotify.com/episode/4WNNxX0RVyLoQHU5fDo5Ls&quot;&gt;https://open.spotify.com/episode/4WNNxX0RVyLoQHU5fDo5Ls&lt;/a&gt;Do5Ls&lt;/p&gt;
</content:encoded><category>AI security</category></item><item><title>Building a Sentinel Detection Lab, Part 8: Watching for Agents, Not Just Attackers</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-8-watching-for-agents-not-just-attackers</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-8-watching-for-agents-not-just-attackers</guid><description>The same process-lineage technique, pointed at a different actor: not a human attacker, an AI agent.</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Most process-lineage detections in SOC content are written to catch attackers—Office spawning PowerShell, a browser spawning &lt;code&gt;cmd.exe&lt;/code&gt;. Part 8 is the same technique, pointed at a different actor. Not a human attacker. An AI agent.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Why lineage, not signature&lt;/h3&gt;
&lt;p&gt;Detections 6 and 7 were both signature matches: a known binary, a known process-plus-port pattern. This one is different by necessity. Agent frameworks: LangChain-style tools and AI coding assistants commonly do real work by shelling out. The agent decides to run a command, and a script process spawns a shell to execute it. No single process in that chain is suspicious on its own. Python launching &lt;code&gt;cmd.exe&lt;/code&gt; happens constantly, for completely ordinary reasons. The signal here isn&apos;t either process alone—it&apos;s the &lt;em&gt;combination&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where Source == &quot;Microsoft-Windows-Sysmon&quot;
| where EventID == 1
| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| extend ParentImage = extract(@&quot;ParentImage:\s+(\S+)&quot;, 1, RenderedDescription)
| extend ProcessName = tolower(tostring(split(Image, &quot;\\&quot;)[-1]))
| extend ParentProcessName = tolower(tostring(split(ParentImage, &quot;\\&quot;)[-1]))
| where ParentProcessName has_any (
    &quot;ollama.exe&quot;, &quot;llamafile.exe&quot;, &quot;lmstudio.exe&quot;,
    &quot;claude.exe&quot;, &quot;chatgpt.exe&quot;, &quot;cursor.exe&quot;, &quot;aider.exe&quot;,
    &quot;python.exe&quot;, &quot;node.exe&quot;
  )
| where ProcessName has_any (&quot;cmd.exe&quot;, &quot;powershell.exe&quot;, &quot;pwsh.exe&quot;, &quot;wscript.exe&quot;, &quot;cscript.exe&quot;, &quot;bash.exe&quot;)
| project TimeGenerated, Computer, ParentImage, ParentProcessName, Image, ProcessName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both the child (&lt;code&gt;Image&lt;/code&gt;) and the parent (&lt;code&gt;ParentImage&lt;/code&gt;) come off the same EID 1 event, extracted with the same regex pattern I&apos;ve used since Part 1, normalized to lowercase. The rule only fires when the parent is a known AI/scripting process &lt;strong&gt;and&lt;/strong&gt; the child is a command interpreter on the same event. It&apos;s conceptually closest to the behavioral approach from the recon-burst detection in &lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken&quot;&gt;Part 4&lt;/a&gt;—no single field tells the story; the relationship does.&lt;/p&gt;
&lt;h3&gt;Building and validating it&lt;/h3&gt;
&lt;p&gt;To generate a real test case rather than a synthetic row, I wrote the smallest honest stand-in for an agent I could: a two-line Python script that shells out to run &lt;code&gt;echo&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;subprocess.run(&quot;cmd.exe /c echo agentic_test&quot;, shell=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s not a real agent framework. But structurally, it&apos;s exactly what one does under the hood: a script process deciding to spawn a shell. Ran it once: &lt;code&gt;python.exe&lt;/code&gt; as a parent, &lt;code&gt;cmd.exe&lt;/code&gt; as a child, caught cleanly on the first attempt. No debugging needed, which is rare enough in this lab to be worth noting on its own.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-8-watching-for-agents-not-just-attackers/08-agentic-ai-process-lineage-spawn-incident.webp&quot; alt=&quot;Incident confirming the python.exe to cmd.exe spawn was caught&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;What I&apos;m not claiming&lt;/h3&gt;
&lt;p&gt;I want to be specific about what one clean validation run does and doesn&apos;t prove. It proves the join logic works. It does &lt;strong&gt;not&lt;/strong&gt; prove this rule is tuned for production. &lt;code&gt;python.exe&lt;/code&gt; and &lt;code&gt;node.exe&lt;/code&gt; as parent processes is a wide net—any ordinary dev workflow or build script that shells out for unrelated reasons would trigger this exact same pattern. I haven&apos;t run it against real-world Python or Node usage yet, so I genuinely don&apos;t know the false-positive rate. That&apos;s the next thing to find out, not something to guess at and write down as if I already know.&lt;/p&gt;
&lt;p&gt;That distinction — a rule that&apos;s &lt;em&gt;validated&lt;/em&gt; versus a rule that&apos;s &lt;em&gt;tuned&lt;/em&gt; — has come up in nearly every post in this series. Saying it again here isn&apos;t repetition for its own sake. It&apos;s the actual discipline the job requires, and it&apos;s easy to let slip the moment something works on the first try.&lt;/p&gt;
&lt;h3&gt;What I took from this one&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A clean first-try result is not the same as a tuned result.&lt;/strong&gt; It&apos;s tempting to treat &quot;worked immediately&quot; as evidence of quality. It&apos;s evidence the logic is sound, nothing more, yet.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The honest scope of an untested assumption belongs in the rule, not just the writeup.&lt;/strong&gt; The FP-risk caveat about &lt;code&gt;python.exe&lt;/code&gt;/&lt;code&gt;node.exe&lt;/code&gt; as parents is written into the rule&apos;s description in Sentinel itself, the same way the browser-logging gap from &lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot&quot;&gt;Part 7&lt;/a&gt; is so the limitation travels with the rule, not just with this post.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A minimal stand-in is still a valid test if it&apos;s structurally honest.&lt;/strong&gt; The two-line script isn&apos;t a real agent, and I&apos;m not pretending it is. But it produces the exact process-lineage shape a real one would, which is the thing the detection actually checks for.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Three detections into the AI-tooling track now: execution, egress, and lineage. The next open thread is tuning: going back to 6 and 8 with real usage data instead of single validation runs and deciding whether that happens before or after detection 9.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;#cybersecurity #sentinel #microsoft-sentinel #kql #detection-engineering #blueteam #azure #ai-security&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Building a Sentinel Detection Lab, Part 7: The Detection That Taught Me My Own Telemetry&apos;s Blind Spot</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot</guid><description>Where AI tooling talks to once it&apos;s running, and the most useful failure in the lab so far.</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-6-shadow-ai-and-the-detection-that-s-honest-about-what-it-hasn-t-proven-yet&quot;&gt;Part 6&lt;/a&gt; caught AI tooling &lt;em&gt;launching&lt;/em&gt;. The natural next question was what happens once it&apos;s running—specifically, where does it talk to? This is the most useful failure I&apos;ve had in this lab so far. Not because the detection didn&apos;t work, but because chasing down &lt;em&gt;why&lt;/em&gt; it didn&apos;t work taught me something true about my own setup that I wouldn&apos;t have found any other way.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The plan&lt;/h3&gt;
&lt;p&gt;Simple idea: match Sysmon network-connection events (Event ID 3) against a list of known AI provider domains—OpenAI, Anthropic, Google&apos;s Gemini, and Hugging Face. Write the query, load a page, watch it fire.&lt;/p&gt;
&lt;p&gt;It didn&apos;t fire. Not once.&lt;/p&gt;
&lt;h3&gt;Chasing the gap&lt;/h3&gt;
&lt;p&gt;First check: are EID 3 events even landing in the table at all? Yes — plenty of them. So the join logic wasn&apos;t the problem; the matching was.&lt;/p&gt;
&lt;p&gt;Second check: what does &lt;code&gt;DestinationHostname&lt;/code&gt; actually look like for connections I know are real? Even confirmed traffic—Ollama calling out, a browser loading Hugging Face—showed either a CDN edge hostname (&lt;code&gt;*.&lt;/code&gt;&lt;a href=&quot;http://bc.googleusercontent.com&quot;&gt;&lt;code&gt;bc.googleusercontent.com&lt;/code&gt;&lt;/a&gt;) or a blank field. Never the literal domain typed into the address bar. Domain-string allowlisting was never going to work reliably against this data, full stop.&lt;/p&gt;
&lt;p&gt;Third check, and the one that actually mattered: I ran &lt;code&gt;summarize count() by Image&lt;/code&gt; across EID 3 events. Zero &lt;code&gt;msedge.exe&lt;/code&gt; rows. Not low — zero. Despite multiple tabs open, including a Hugging Face page I&apos;d just loaded myself to test against.&lt;/p&gt;
&lt;p&gt;The root cause: the SwiftOnSecurity Sysmon baseline config — the same config I&apos;ve been running since &lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-1-catching-brute-force-and-the-bug-that-flagged-the-wrong-account&quot;&gt;Part 1&lt;/a&gt;—deliberately excluding browser processes from network-connection logging for noise reduction. This wasn&apos;t a bug in my query. It was a real, documented limitation of the telemetry source I&apos;d chosen, and I hadn&apos;t known about it until I went looking.&lt;/p&gt;
&lt;h3&gt;Redesigning around what the data can actually show&lt;/h3&gt;
&lt;p&gt;Once I understood that, the fix wasn&apos;t a smarter regex — it was scoping the detection to match what&apos;s actually visible. I pivoted from domain-matching to process-plus-port matching: known AI-tooling processes (&lt;code&gt;ollama.exe&lt;/code&gt;, &lt;code&gt;llamafile.exe&lt;/code&gt;, &lt;code&gt;python.exe&lt;/code&gt;, &lt;code&gt;node.exe&lt;/code&gt;, etc.) making an outbound connection on 443 or 80, regardless of where it&apos;s headed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where Source == &quot;Microsoft-Windows-Sysmon&quot;
| where EventID == 3
| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| extend ProcessName = tolower(tostring(split(Image, &quot;\\&quot;)[-1]))
| extend DestinationHostname = extract(@&quot;DestinationHostname:\s+(\S+)&quot;, 1, RenderedDescription)
| extend DestinationIp = extract(@&quot;DestinationIp:\s+(\S+)&quot;, 1, RenderedDescription)
| extend DestinationPort = extract(@&quot;DestinationPort:\s+(\S+)&quot;, 1, RenderedDescription)
| where ProcessName has_any (&quot;ollama.exe&quot;, &quot;llamafile.exe&quot;, &quot;lmstudio.exe&quot;, &quot;python.exe&quot;, &quot;node.exe&quot;)
   and DestinationPort in (&quot;443&quot;, &quot;80&quot;)
| project TimeGenerated, Computer, Image, DestinationHostname, DestinationIp, DestinationPort
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s less precise about &lt;em&gt;where&lt;/em&gt; the traffic goes but far more reliable given what I now know about my own config. Validated organically: Ollama on the VM was already making real outbound calls to Google-hosted infrastructure on port 443, caught without my needing to deliberately trigger anything. 8 matching rows once the new logic was in place.&lt;/p&gt;
&lt;h3&gt;The honest scope statement&lt;/h3&gt;
&lt;p&gt;This detection catches non-browser AI egress — local tools and scripts calling out directly. It does &lt;strong&gt;not&lt;/strong&gt; catch someone pasting sensitive data into ChatGPT&apos;s web interface, because the browser traffic that would carry that simply isn&apos;t in this telemetry. That&apos;s a real, structural gap, and instead of letting it sit quietly in a comment somewhere, I wrote it straight into the rule&apos;s description in Sentinel: &lt;em&gt;&quot;Does not cover browser-based AI usage — SwiftOnSecurity Sysmon baseline excludes browser processes from EID 3 logging.&quot;&lt;/em&gt; Anyone reading the rule in six months, including me, sees the limitation without having to dig.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-7-the-detection-that-taught-me-my-own-telemetry-s-blind-spot/07-endpoint-to-ai-server-detection.webp&quot; alt=&quot;Endpoint-to-AI-Service Data Egress rule, with the browser-exclusion limitation in its description&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;What I took from this one&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A detection is only as good as your understanding of what its data source can see.&lt;/strong&gt; I&apos;d assumed Sysmon&apos;s network logging was comprehensive. It isn&apos;t, by design, and that design choice exists for good reasons but it has to factor into scope, not get discovered after the fact and quietly ignored.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&quot;It doesn&apos;t fire&quot; needs the same rigor as &quot;it fires.&quot;&lt;/strong&gt; Treating a non-result as a debugging problem, not a dead end, is what actually surfaced the real limitation here.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Document the gap in the rule itself, not just in your head.&lt;/strong&gt; A scope limitation that only exists as something I personally remember isn&apos;t documentation—it&apos;s a landmine for whoever inherits this rule next.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next up: a different kind of AI-related signal — not the tool running, not its traffic, but what it spawns. Part 8 picks up where an agent shells out and decides whether that pattern is worth flagging on its own.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;#cybersecurity #sentinel #microsoft-sentinel #kql #detection-engineering #blueteam #azure #ai-security&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Building a Sentinel Detection Lab, Part 6: Shadow AI and the Detection That&apos;s Honest About What It Hasn&apos;t Proven Yet</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-6-shadow-ai-and-the-detection-that-s-honest-about-what-it-hasn-t-proven-yet</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-6-shadow-ai-and-the-detection-that-s-honest-about-what-it-hasn-t-proven-yet</guid><description>AI tooling showing up on endpoints with nobody in IT knowing it&apos;s there.</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Five detections in, every one of them pointed at a fairly traditional attacker: brute force, malicious PowerShell, registry persistence, recon, and MFA abuse. Part 6 starts a different track—not an attacker at all, but a governance problem that&apos;s quietly become a SOC problem: AI tooling showing up on endpoints with nobody in IT knowing it&apos;s there.&lt;/p&gt;
&lt;p&gt;This is the first detection in the lab that doesn&apos;t really map cleanly onto MITRE ATT&amp;amp;CK, and I&apos;m not going to pretend it does.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The problem underneath the problem&lt;/h3&gt;
&lt;p&gt;Most &quot;AI security&quot; content jumps straight to prompt injection or model abuse. I had a more basic question first: how would I even know if someone on my network had installed a local AI tool in the first place? Ollama, LM Studio, and a desktop AI coding assistant—none of these are malware. They&apos;re also invisible to a SOC that isn&apos;t specifically looking for them, and they create a real visibility gap: unmanaged software with no enterprise logging, no DLP coverage, and a direct channel for whatever gets pasted or fed into it.&lt;/p&gt;
&lt;p&gt;That&apos;s a process-creation problem before it&apos;s anything else. Sysmon Event ID 1 already logs every process that launches—the question was just whether I could build a reliable list to match against&lt;/p&gt;
&lt;h3&gt;The detection&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where Source == &quot;Microsoft-Windows-Sysmon&quot;
| where EventID == 1
| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| extend ProcessName = tolower(tostring(split(Image, &quot;\\&quot;)[-1]))
| where ProcessName has_any (
    &quot;ollama.exe&quot;, &quot;llamafile.exe&quot;, &quot;lm-studio.exe&quot;, &quot;lmstudio.exe&quot;,
    &quot;chatgpt.exe&quot;, &quot;claude.exe&quot;, &quot;gemini.exe&quot;, &quot;copilot.exe&quot;,
    &quot;cursor.exe&quot;, &quot;aider.exe&quot;, &quot;openai.exe&quot;
  )
| project TimeGenerated, Computer, Image, ProcessName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same extraction pattern I&apos;ve used since Part 1—pull &lt;code&gt;Image&lt;/code&gt; out the unstructured &lt;code&gt;RenderedDescription&lt;/code&gt; text, isolate the filename, normalize case with &lt;code&gt;tolower()&lt;/code&gt; (Windows logs process names inconsistently, which is the same issue that caused the case-sensitivity bug in Part 4), then check it against a known list of AI tool binaries. No aggregation, no threshold—a single matching event is the full signal, the same design as the PowerShell detection in Part 2.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-6-shadow-ai-and-the-detection-that-s-honest-about-what-it-hasn-t-proven-yet/06-shadow-ai-tooling-incident.webp&quot; alt=&quot;Incident showing captured ollama.exe process creation events&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;What I validated, and what I&apos;m not claiming&lt;/h3&gt;
&lt;p&gt;I installed real Ollama on the lab VM — not a stand-in. &lt;code&gt;winget install&lt;/code&gt; doesn&apos;t work on Windows Server 2022, so this meant a direct-download install, which was itself a small real-world gotcha I hadn&apos;t anticipated. Confirmed the process running with &lt;code&gt;Get-Process ollama&lt;/code&gt;, then queried Sentinel: 5 matching rows, clean full path, nothing truncated.&lt;/p&gt;
&lt;p&gt;What I haven&apos;t done is false-positive testing at scale. One tool, one validation pass. I want to be upfront about that rather than let the writeup imply a polish level it hasn&apos;t earned. A working detection and a &lt;em&gt;tuned&lt;/em&gt; detection are different claims, and right now I&apos;m only entitled to make the first one.&lt;br /&gt;
On the MITRE mapping&lt;/p&gt;
&lt;p&gt;I&apos;ve tagged this T1588.002 (Obtain Capabilities: Tool) because it&apos;s the closest fit ATT&amp;amp;CK offers, but it&apos;s worth saying plainly: that technique describes an &lt;em&gt;attacker&lt;/em&gt; acquiring tooling pre-compromise, not an employee installing AI software on a managed endpoint. This rule functions more like a custom shadow-IT control than a classic threat detection. Forcing a cleaner ATT&amp;amp;CK story onto it than the behavior actually supports felt like the wrong move, so the repo says so directly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-6-shadow-ai-and-the-detection-that-s-honest-about-what-it-hasn-t-proven-yet/06-shadow-ai-tooling-detection.webp&quot; alt=&quot;Shadow AI Tooling Execution rule in Sentinel, mapped to T1588.002&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;What I took from this one&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A governance signal is still a real detection, even without a clean MITRE mapping.&lt;/strong&gt; Not every useful rule needs to be dressed up as catching an attacker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&quot;It worked once&quot; and &quot;it&apos;s tuned&quot; are different sentences.&lt;/strong&gt; Saying the second when I&apos;ve only earned the first is the kind of overclaim I&apos;d rather catch myself in than have a reviewer catch for me.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Small install gotchas are real findings too.&lt;/strong&gt; The &lt;code&gt;winget&lt;/code&gt; gap on Windows Server isn&apos;t a security lesson, but it&apos;s the kind of operational detail that separates a lab someone&apos;s actually built from one that&apos;s only ever been described.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next up: the network side of the same problem—what happens when that AI tooling tries to talk to the internet and a telemetry blind spot I didn&apos;t know I had until I went looking for it. That&apos;s Part 7.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;#cybersecurity #sentinel #microsoft-sentinel #kql #detection-engineering #blueteam #azure #ai-security&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Humans Aren&apos;t the Weakest Link</title><link>https://knowngood.au/writing/humans-aren-t-the-weakest-link</link><guid isPermaLink="true">https://knowngood.au/writing/humans-aren-t-the-weakest-link</guid><description>The line that excuses broken process, and what it costs dwell time.</description><pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&quot;Humans are the weakest link.&quot;&lt;/p&gt;
&lt;p&gt;You&apos;ve seen it on a vendor slide. Probably more than one. It&apos;s the most repeated line in security awareness, and it&apos;s repeated for a reason: it&apos;s comforting. If the human is the weak link, then the breach wasn&apos;t a design failure, or a missing control, or an alert nobody tuned. It was someone in finance clicking a link. Problem named. Problem outsourced.&lt;/p&gt;
&lt;p&gt;I want to push back on it—not because it&apos;s entirely wrong, but because it&apos;s lazy in a way that actively makes incident response worse.&lt;/p&gt;
&lt;h3&gt;The kernel of truth, and where it breaks&lt;/h3&gt;
&lt;p&gt;Let&apos;s be fair to the cliché first. Phishing, social engineering, weak passwords, someone plugging in a found USB—a large share of incidents &lt;em&gt;do&lt;/em&gt; start with a person. That&apos;s real. I&apos;m not going to pretend the human-shaped gap in the perimeter doesn&apos;t exist.&lt;/p&gt;
&lt;p&gt;But &quot;incidents often start with a person&quot; and &quot;the person is the weakest link&quot; are two very different claims. The first is an observation. The second is a verdict. And the verdict smuggles in an assumption: that the fix is to make the human stronger—more training, more phishing simulations, more sternly worded emails—rather than to ask why one person&apos;s one mistake was allowed to matter that much.&lt;/p&gt;
&lt;p&gt;This isn&apos;t only my take. Researchers who study the human side of security make the same argument. Dr. Iain Reid (University of Portsmouth) has talked through exactly why &quot;the human is the weakest link&quot; oversimplifies risk—and how something as mundane as time pressure quietly reshapes the decisions people make when an attack lands [1].&lt;/p&gt;
&lt;p&gt;If a single click can compromise your environment, the click isn&apos;t your weakest link. Your architecture is.&lt;/p&gt;
&lt;h3&gt;Humans are also the only link that reports&lt;/h3&gt;
&lt;p&gt;Here&apos;s what the slide never says: the same humans are your detection layer.&lt;/p&gt;
&lt;p&gt;I spend a lot of my time building detections—KQL analytics rules in Microsoft Sentinel mapped to MITRE ATT&amp;amp;CK [2]. And here&apos;s the thing those rules taught me: a detection doesn&apos;t &lt;em&gt;respond&lt;/em&gt; to anything. It fires. That&apos;s it. Something has to read it, judge it, decide it&apos;s real, and act. That something is a person.&lt;/p&gt;
&lt;p&gt;The employee who forwards a weird email to the security team instead of clicking. The SOC analyst at 2am who looks at an alert everyone else snoozed and says, &quot;Wait—that&apos;s lateral movement.&quot; Those aren&apos;t weak links. They&apos;re sensors, often the &lt;em&gt;only&lt;/em&gt; sensors that catch what slipped past the tooling.&lt;/p&gt;
&lt;p&gt;You don&apos;t get those reports from people you&apos;ve spent years telling they&apos;re the problem.&lt;/p&gt;
&lt;p&gt;And this isn&apos;t wishful thinking — it&apos;s measurable. A MITRE study trained employees with hands-on practice and feedback instead of slides, then quietly tested them with simulated social-engineering attempts over a full year. Reporting of the malicious ones improved, and the effect held up for 12 months.[3] The paper&apos;s own word for trained employees is the one I keep coming back to: &lt;em&gt;sensors&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Why this matters for IR specifically&lt;/h3&gt;
&lt;p&gt;This is where the &quot;weakest link&quot; framing stops being merely annoying and starts being expensive.&lt;/p&gt;
&lt;p&gt;Incident response lives and dies on one number: how fast you find out. Dwell time: the gap between compromise and detection is the difference between &quot;we contained it&quot; and &quot;we&apos;re calling a lawyer.&quot; And the fastest path to early detection is almost always a human who noticed something and &lt;em&gt;said something quickly&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So ask: What does a &quot;humans are the weakest link&quot; culture do to the speed of that report?&lt;/p&gt;
&lt;p&gt;It slows it down. If an organisation treats mistakes as moral failures, then the person who clicked or who realises they shouldn&apos;t have or who isn&apos;t even &lt;em&gt;sure&lt;/em&gt; hesitates. They wait. They hope it&apos;s nothing. They check with a colleague before they admit it to security. Every one of those minutes is dwell time you&apos;re handing to the attacker, for free, because you built a culture where reporting feels like a confession.&lt;/p&gt;
&lt;p&gt;Aviation worked this out decades ago. So did site reliability engineering. It&apos;s called a &lt;em&gt;just culture&lt;/em&gt;, or blameless postmortems: you separate &quot;What happened and how do we stop it recurring?&quot; from &quot;Whose fault is it?&quot; Not because nobody is ever responsible, but because the moment people fear blame, they stop giving you information and in IR, information is the whole game.&lt;/p&gt;
&lt;p&gt;Contain first. Blame never or at least not until the incident is dead and you&apos;re learning from it instead of prosecuting it.&lt;/p&gt;
&lt;h3&gt;The responder is human too&lt;/h3&gt;
&lt;p&gt;There&apos;s a second human in this story we talk about even less: the responder.&lt;/p&gt;
&lt;p&gt;The person who clicked the phish was making a fast, low-attention decision under pressure: a plausible email, a busy Tuesday, fifty other tabs open. We love to judge that decision with the calm hindsight of someone who wasn&apos;t there. But that&apos;s exactly the dynamic Reid points to: under time pressure, people lean on mental shortcuts, and attackers design for precisely that moment. And the analyst three hours into an active incident is &lt;em&gt;also&lt;/em&gt; a human under load—tired, stressed, with cognitive bandwidth shrinking, making high-stakes calls fast. There&apos;s a growing body of research on the psychological toll of serious cyber incidents on the people who respond to them too [4].&lt;/p&gt;
&lt;p&gt;If we accept that responders make worse decisions under stress — and they do — then we owe the same grace to the user who clicked. Both are humans operating inside systems that asked too much of their attention at the wrong moment. The fix isn&apos;t to demand better humans. It&apos;s to design for the ones we have.&lt;/p&gt;
&lt;h3&gt;So what actually changes&lt;/h3&gt;
&lt;p&gt;Drop the &quot;weakest link&quot; frame, and an IR program starts to look different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make reporting frictionless and safe.&lt;/strong&gt; One button, no judgement, no twelve-field form. The goal is speed, and speed comes from people not being afraid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run blameless postmortems.&lt;/strong&gt; Ask what in the system let the mistake matter, not who made it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Design alerts and playbooks for humans under stress,&lt;/strong&gt; not for a well-rested analyst with infinite time. Clear, prioritized, low-noise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Treat people as sensors.&lt;/strong&gt; Their reports are detection telemetry. Tune them, value them, and close the loop with them, the same way you would a noisy rule.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The actual weakest link&lt;/h3&gt;
&lt;p&gt;Humans aren&apos;t the weakest link. They&apos;re the most adaptable component in the whole stack: the one that improvises, notices the thing that doesn&apos;t fit, and reports it when the tooling stayed silent.&lt;/p&gt;
&lt;p&gt;The weakest link is a culture that punishes people for being human and then wonders why nobody spoke up until it was too late.&lt;/p&gt;
&lt;h3&gt;References&lt;/h3&gt;
&lt;p&gt;[1] Cybercrimeology podcast, &lt;em&gt;The Human in Security: Deception, Weapons, Crime, Culture&lt;/em&gt; — featuring Dr. Iain Reid, Senior Lecturer in Cybercrime, University of Portsmouth. &lt;a href=&quot;https://cybercrimeology.com/episodes/the-human-in-security-deception-weapons-crime-culture-7NBb6hrH&quot;&gt;https://cybercrimeology.com/episodes/the-human-in-security-deception-weapons-crime-culture-7NBb6hrH&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[2] My Sentinel detection lab: &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[3] Caputo, D. D., Danley, L., &amp;amp; Ratcliff, N. J. (2024). Employee risk recognition and reporting of malicious elicitations: longitudinal improvement with new skills-based training. &lt;em&gt;Frontiers in Psychology&lt;/em&gt;, 15, 1410426. &lt;a href=&quot;https://doi.org/10.3389/fpsyg.2024.1410426&quot;&gt;https://doi.org/10.3389/fpsyg.2024.1410426&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[4] Virtanen, T. (2024). The Psychological Effects of Continuity Threatening Cyber Incidents. &lt;em&gt;Proceedings of the 23rd European Conference on Cyber Warfare and Security&lt;/em&gt;, 23(1). &lt;a href=&quot;https://doi.org/10.34190/eccws.23.1.2268&quot;&gt;https://doi.org/10.34190/eccws.23.1.2268&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Incident response</category></item><item><title>Building a Sentinel Detection Lab, Part 5: MFA Registration, a Missing License, and the First Detection That Talks Back</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back</guid><description>Attackers registering their own MFA methods to keep access after a password reset, and the first detection that talks back.</description><pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken&quot;&gt;Part 4&lt;/a&gt; ended on this note: moving off the endpoint entirely and into identity, starting with attackers registering their own MFA methods on compromised accounts to keep access after a password reset. This is that detection — and it&apos;s also the first one in the lab that doesn&apos;t just sit there once it fires.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back/05-mfa-registration-incident.webp&quot; alt=&quot;MFA registration incident with the automated triage comment from the Logic App&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;The plan, and where it hit a wall&lt;/h3&gt;
&lt;p&gt;The textbook way to build this is on Microsoft Entra ID Protection&apos;s risk score. Correlate a &lt;em&gt;risky&lt;/em&gt; sign-in with a security-info registration that follows it, and you&apos;ve got a clean detection for T1556.006 — Modify Authentication Process: MFA — with almost no extra work, because Microsoft has already done the hard part of scoring the sign-in for you.&lt;/p&gt;
&lt;p&gt;My tenant said no. The P2 trial wouldn&apos;t activate. Try/Buy stayed greyed out — no error, no permission to request, nothing to appeal. This is a personal Azure free tenant (I&apos;m global admin on my own setup, since my university tenant blocks Entra diagnostic settings outright—a separate wall I&apos;m not getting through). P2 just wasn&apos;t on the table.&lt;/p&gt;
&lt;p&gt;So the question changed from &quot;how do I use the risk score&quot; to &quot;what do I actually have, and is it enough to build something real?&quot;&lt;/p&gt;
&lt;h3&gt;What I was actually trying to catch&lt;/h3&gt;
&lt;p&gt;T1556.006 is dangerous because it&apos;s boring. An attacker with a stolen credential registers their own authenticator app or phone number as a second factor. From that point on, a password reset doesn&apos;t lock them out — their MFA method is now a legitimate part of the account. Nothing about &quot;user registered a new MFA method&quot; looks like an attack on its own. People switch phones constantly. The signal was never going to be the registration itself. &lt;strong&gt;It&apos;s what came immediately before it.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;A risk score made out of what&apos;s free&lt;/h3&gt;
&lt;p&gt;Without a real risk engine, I needed a cheaper way to say &quot;this sign-in doesn&apos;t look like the account owner.&quot; The signal every tenant has for free, license or not, is IP address. So I built what I&apos;ve been calling Variant B: keep a short list of trusted IPs and treat a sign-in from anywhere else as untrusted. Correlate an untrusted-IP sign-in with a security-info registration on the same account inside a 30-minute window, and that&apos;s the detection.&lt;/p&gt;
&lt;p&gt;It&apos;s a coarser signal than a real risk score—no device fingerprinting, no impossible-travel logic, and no threat-intel scoring behind it. I&apos;m not going to dress it up as equivalent to something it isn&apos;t. But it&apos;s real, it&apos;s free, and it caught exactly what I simulated.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;let TrustedIPs = dynamic([&quot;&amp;lt;office/home IP 1&amp;gt;&quot;, &quot;&amp;lt;office/home IP 2&amp;gt;&quot;]);
let UntrustedSignIns = SigninLogs
| where IPAddress !in (TrustedIPs)
| where ResultType == 0
| project SignInTime = TimeGenerated, UserPrincipalName, IPAddress, Location;
let MFARegistrations = AuditLogs
| where ActivityDisplayName == &quot;User started security info registration&quot;
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| project RegTime = TimeGenerated, UserPrincipalName;
UntrustedSignIns
| join kind=inner MFARegistrations on UserPrincipalName
| where RegTime between (SignInTime .. (SignInTime + 30m))
| summarize arg_min(SignInTime, *) by UserPrincipalName, IPAddress
| project SignInTime, RegTime, UserPrincipalName, IPAddress, Location
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Walking through it: pull successful sign-ins from untrusted IPs, pull security-info-registration events from the audit log, join them on the user, keep only pairs where the registration lands within 30 minutes of the sign-in, and then dedup down to one row per user/IP pair so a single real event doesn&apos;t surface twice in the same window.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back/05-mfa-registration-detection.webp&quot; alt=&quot;MFA registration KQL query and the correlated sign-in and registration result&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back/05-mfa-registration-rule-config.webp&quot; alt=&quot;Suspicious MFA Registration analytics rule, High severity&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The trusted-IP list itself should live in a Sentinel watchlist, not hardcoded in the query. That wasn&apos;t the plan — it&apos;s a workaround for a Defender XDR bug where the watchlist upload UI silently failed every time I tried to create one. I&apos;m naming that explicitly instead of quietly shipping around it, because &quot;the UI was broken so I hardcoded it&quot; is a real tradeoff between maintainability and shipping something that actually works today.&lt;/p&gt;
&lt;h3&gt;Validating it for real&lt;/h3&gt;
&lt;p&gt;Same approach as every detection before this one — generate real telemetry, don&apos;t trust synthetic test rows. I signed in normally from my real IP first, to establish a baseline in &lt;code&gt;SignInLogs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then I connected through Proton VPN to a Seattle exit node and signed in again from there. From that same VPN session, I registered a new MFA method — the actual technique, not just a suspicious login on its own. Both events correlated back to the same &lt;code&gt;UserPrincipalName&lt;/code&gt; and fired the rule.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back/05-mfa-registration-signin.webp&quot; alt=&quot;Security info page signed in through Proton VPN on a Seattle exit node&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The part that&apos;s actually new: the detection talks back!&lt;/h2&gt;
&lt;p&gt;Every detection before this one stopped the second an incident was raised. This is the first one that doesn&apos;t.&lt;/p&gt;
&lt;p&gt;I wired a Sentinel automation rule to fire on incident creation, triggering an Azure Logic App that calls &lt;strong&gt;Add comment to incident (V3)&lt;/strong&gt;—posting an automated triage note recommending the first containment step (revoking active sessions) directly onto the incident; no analyst action required to get that documented.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back/05-mfa-registration-logicapp.webp&quot; alt=&quot;Logic App workflow: Sentinel incident trigger wired to Add comment to incident (V3)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The original plan was more ambitious: have the Logic App write the flagged IP straight to a watchlist as a lightweight auto-block. The same broken watchlist UI that forced the hardcoded IP list also made watchlist writes unreliable from Logic Apps. So I scoped the automation down to the smaller, reliable action instead. &lt;strong&gt;A working &quot;add comment&quot; beats a watchlist write that fails silently half the time&lt;/strong&gt;—and knowing when not to build the more impressive version turned out to be its own skill, separate from knowing how.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-5-mfa-registration-a-missing-license-and-the-first-detection-that-talks-back/05-mfa-registration-automation-rule.webp&quot; alt=&quot;Automation rule: when an incident is created, run the playbook&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;What I took from this one&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A missing license is a constraint to design around, not a reason to stop.&lt;/strong&gt; Variant B isn&apos;t as good as a real risk score, and saying so plainly is more useful than pretending otherwise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The reliable action beats the ambitious&lt;/strong&gt; one more &lt;strong&gt;often than I expected.&lt;/strong&gt; I had a more impressive automation in mind, and the platform itself talked me out of it. That&apos;s a real engineering decision, not a consolation prize.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dedup has a blind spot I didn&apos;t see coming.&lt;/strong&gt; My &lt;code&gt;summarize&lt;/code&gt;-based deduplication only protects against repeats &lt;em&gt;inside one query run&apos;s lookback window&lt;/em&gt;—it doesn&apos;t stop the same real event from raising a second incident if it&apos;s still inside the window on the &lt;em&gt;next&lt;/em&gt; scheduled run. I found this from actual duplicate incidents during testing, not from reading the docs first. A production version needs state tracking across runs, not just within one.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The IR playbook&lt;/h2&gt;
&lt;p&gt;Detection is half the job. Here&apos;s what I actually do when this fires—written for a solo analyst, because that&apos;s what I am in this lab. No handoff tier, no triage queue; every step assumes I&apos;m the only one responding.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Triage first: Is this real, or just a user on a new device?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pull the user&apos;s sign-in history for the past week and look for a clean break—a normal-location baseline, then an abrupt shift to the flagged IP right before the registration. Then pull the audit log sequence for what was actually registered. A pattern of &lt;em&gt;delete existing method → register new method&lt;/em&gt;, both from an untrusted IP, is the strongest tell—it suggests the attacker is replacing the victim&apos;s factor with their own, not just adding one. If the source IP traces to hosting or anonymizer infrastructure rather than a residential or known-VPN range, that alone is enough to skip straight to containment without waiting on the user to respond.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why containment comes before full investigation here.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the alert is real, the attacker may already have a working MFA method on the account. Every minute spent investigating first is a minute that the persistence mechanism survives a password reset.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1 — Revoke sessions, not the account.&lt;/strong&gt; Revoking active sessions (&lt;code&gt;Revoke-MgUserSignInSession&lt;/code&gt;or &lt;em&gt;Users → [user] → Revoke sessions&lt;/em&gt; in the portal) is the right first move over disabling the account outright—it kills the attacker&apos;s stolen tokens immediately and forces re-authentication, without the broader disruption of locking the legitimate user out too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2 — Remove the attacker-registered MFA method.&lt;/strong&gt; This is the step that actually evicts the persistence. Revoking sessions alone leaves the planted factor sitting there for the attacker to reuse the next time they authenticate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3 — Force a password reset.&lt;/strong&gt; Credentials should be assumed compromised at this point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;—&lt;strong&gt;Re-enroll the legitimate user on a verified, out-of-band channel&lt;/strong&gt;—not through any contact detail that might have been altered during the compromise. Sequencing matters here: revoke first, then remove the rogue factor and reset the password. Doing it in reverse order risks the attacker re-establishing a session before you&apos;ve actually cut them off.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Eradication, before closing.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Check for inbox forwarding rules and newly consented enterprise applications—both common follow-on persistence techniques once an attacker has a foothold and easy to miss if you stop at &quot;removed the MFA method.&quot;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Post-incident, hunt for the same pattern across other users.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If one credential set was compromised, there&apos;s a real chance others were too—the same correlation logic, run across the whole tenant for the past two weeks, surfaces anyone else with the same sign-in-then-registration pattern.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Escalation is about scope, not severity.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Single account, contained quickly → handle it end-to-end and close it. Multiple users, or a privileged account, in the same window → this isn&apos;t a single-account event anymore; treat it as a possible wider intrusion and broaden the hunt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One thing I want to be explicit about:&lt;/strong&gt; the Logic App automatically posts a comment recommending Step 1 the moment the incident is created, but it doesn&apos;t perform the revocation itself. That&apos;s a deliberate line, not a gap to fix later. Locking a user out is a judgment call (is this actually an attacker or the account owner traveling?), and that judgment stays with a person. Automating the &lt;em&gt;recommendation&lt;/em&gt; but not the &lt;em&gt;action&lt;/em&gt; was the right call for this one.&lt;/p&gt;
&lt;h3&gt;Where this is going&lt;/h3&gt;
&lt;p&gt;Five detections now—brute force, suspicious PowerShell, registry persistence, the recon-burst detection from &lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken&quot;&gt;Part 4&lt;/a&gt;, and this one. Next up: an isolated honeypot VM for Atomic Red Team validation and an AI-assisted triage layer sitting on top of the incidents this lab already generates. That&apos;s Part 6.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Building a Sentinel Detection Lab, Part 4: Catching Reconnaissance, and the Case-Sensitivity Bug That Made It Look Broken</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken</guid><description>The first detection in the lab that looks for a pattern of behavior, and the case-sensitivity bug that made it look broken.</description><pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is Part 4 of the series where I build a Microsoft Sentinel detection engineering lab from scratch and write down what actually happened — including the parts where I got it wrong. &lt;a href=&quot;https://knowngood.au/writing/series/building-a-sentinel-detection-lab&quot;&gt;Parts 1–3&lt;/a&gt; covered a brute-force detection (volume threshold), a suspicious-PowerShell detection (signature), and a registry persistence detection (signature plus a false-positive I had to tune out).&lt;/p&gt;
&lt;p&gt;This one is different. It&apos;s the first detection in the lab that doesn&apos;t look for a thing—it looks for a pattern of behavior. And it&apos;s the one that taught me the most, because for a while it just sat there returning nothing, and I was convinced I&apos;d broken it.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The detection that returned nothing&lt;/h3&gt;
&lt;p&gt;Here&apos;s where I&apos;ll start, because it&apos;s the most useful thing in this post.&lt;/p&gt;
&lt;p&gt;I wrote the rule. I ran the recon commands on my lab VM to generate test data. I ran the query. &lt;strong&gt;Empty.&lt;/strong&gt; No results.&lt;/p&gt;
&lt;p&gt;My first instinct — the wrong one — was to start rewriting the logic. Maybe my threshold was off. Maybe the time window was wrong. I poked at it for longer than I&apos;d like to admit.&lt;/p&gt;
&lt;p&gt;The actual problem: Windows doesn&apos;t log process names consistently. Some commands showed up in Sysmon as &lt;code&gt;arp.exe&lt;/code&gt;. Others showed up as &lt;code&gt;ARP.EXE&lt;/code&gt;. My filter was matching against a lowercase list, so every uppercase entry silently fell through. The data was &lt;em&gt;right there&lt;/em&gt;—my detection just couldn&apos;t see half of them because of letter casing.&lt;/p&gt;
&lt;p&gt;The fix was one function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;| extend ProcessName = tolower(tostring(split(Image, &quot;\\&quot;)[-1]))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Normalize everything to lowercase &lt;em&gt;before&lt;/em&gt; you compare. Obvious in hindsight. But the lesson stuck harder than the fix: &lt;strong&gt;when a detection returns empty, don&apos;t assume your logic is wrong&lt;/strong&gt;—check &lt;strong&gt;whether your assumptions about the shape of the data are wrong.&lt;/strong&gt; A detection that silently under-counts is more dangerous than one that errors out, because it looks like it&apos;s working. It&apos;ll sit quiet during a real attack, and you&apos;ll never know it missed.&lt;/p&gt;
&lt;p&gt;That&apos;s the whole job, really. The query is easy. Knowing why it lied to you is the skill.&lt;/p&gt;
&lt;h3&gt;What I was actually trying to catch&lt;/h3&gt;
&lt;p&gt;Reconnaissance. The phase right after an attacker lands on a machine, before they move anywhere—when they stop and ask the box &lt;em&gt;where am&lt;/em&gt; I? &lt;em&gt;who am&lt;/em&gt; I? &lt;em&gt;and what&apos;s around me.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;They run things like &lt;code&gt;whoami&lt;/code&gt;, &lt;code&gt;net.exe&lt;/code&gt;, &lt;code&gt;nltest&lt;/code&gt;, &lt;code&gt;systeminfo&lt;/code&gt;, &lt;code&gt;ipconfig&lt;/code&gt;, &lt;code&gt;tasklist&lt;/code&gt;, &lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;nslookup&lt;/code&gt;, &lt;code&gt;arp&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;. In MITRE ATT&amp;amp;CK terms this spans a few discovery techniques—T1057 (Process Discovery), T1082 (System Information Discovery), and T1016 (System Network Configuration Discovery).&lt;/p&gt;
&lt;p&gt;Here&apos;s the problem that makes this detection interesting: &lt;strong&gt;not one of those commands is malicious.&lt;/strong&gt; A sysadmin runs &lt;code&gt;ipconfig&lt;/code&gt; 50 times a day. &lt;code&gt;whoami&lt;/code&gt; is harmless. If I alerted on any single recon command, I&apos;d drown the SOC in noise, and the alert would get muted within a week.&lt;/p&gt;
&lt;p&gt;So the signal isn&apos;t the command. The signal is varied &lt;strong&gt;in a short window.&lt;/strong&gt; A real human admin doesn&apos;t fire off ten &lt;em&gt;different&lt;/em&gt; discovery tools inside ten minutes. An attacker — or an automated recon script — does. They want the full picture, fast.&lt;/p&gt;
&lt;h3&gt;A third kind of detection: behavioural clustering&lt;/h3&gt;
&lt;p&gt;My first three detections were a volume threshold and two signatures. This one needed a different shape entirely. I wasn&apos;t counting &lt;em&gt;how many times&lt;/em&gt; one bad thing happened, and I wasn&apos;t matching one known-bad string. I was measuring &lt;strong&gt;how many distinct recon tools showed up together.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s &lt;code&gt;dcount()&lt;/code&gt; — count distinct—not a plain &lt;code&gt;count()&lt;/code&gt;. Here&apos;s the tuned query:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where Source == &quot;Microsoft-Windows-Sysmon&quot;
| where EventID == 1
| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| extend ProcessName = tolower(tostring(split(Image, &quot;\\&quot;)[-1]))
| where ProcessName in (&quot;whoami.exe&quot;, &quot;net.exe&quot;, &quot;net1.exe&quot;, &quot;nltest.exe&quot;, &quot;systeminfo.exe&quot;, &quot;ipconfig.exe&quot;, &quot;tasklist.exe&quot;, &quot;hostname.exe&quot;, &quot;nslookup.exe&quot;, &quot;arp.exe&quot;, &quot;route.exe&quot;)
| summarize ReconCommands = make_set(ProcessName), DistinctReconCount = dcount(ProcessName) by Computer, bin(TimeGenerated, 10m)
| where DistinctReconCount &amp;gt;= 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Walking through it: pull Sysmon process-creation events (Event ID 1), extract the image path, and normalize the process name to lowercase (the fix from earlier); keep only known recon tools, then bucket everything into 10-minute windows per machine. &lt;code&gt;make_set()&lt;/code&gt; collects which tools appeared and &lt;code&gt;dcount()&lt;/code&gt; counts how many &lt;em&gt;distinct&lt;/em&gt; ones. If five or more different recon commands cluster inside one 10-minute window on one host, it fires.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;make_set&lt;/code&gt; is there on purpose—when this alerts, the analyst immediately sees &lt;em&gt;which&lt;/em&gt; tools ran, so they&apos;re not starting their investigation from zero.&lt;/p&gt;
&lt;h3&gt;Tuning in the opposite direction&lt;/h3&gt;
&lt;p&gt;In Part 3, my registry detection fired on a legitimate Windows process, and I fixed it by adding a narrow exclusion — I told the rule to ignore one specific benign pattern.&lt;/p&gt;
&lt;p&gt;This detection&apos;s false-positive risk is completely different, so the fix is too. Here, the thing that might trip it innocently is a sysadmin genuinely multitasking—patching, troubleshooting, and running a handful of diagnostic commands in a burst. There&apos;s no single benign pattern to exclude; the behavior itself overlaps with legit admin work.&lt;/p&gt;
&lt;p&gt;So instead of an exclusion, I tuned the &lt;strong&gt;threshold&lt;/strong&gt;. I started at 4 distinct commands, and it felt slightly twitchy, so I raised it to 5. Fewer false alarms, and a real recon sweep still trips it easily because attackers don&apos;t stop at four.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken/04-reconnaissance-command-burst-detection.webp&quot; alt=&quot;Reconnaissance Command Burst analytics rule with its query, frequency and threshold&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The meta-lesson I took from running both detections back to back: &lt;strong&gt;the direction you tune depends on where the false positives come from.&lt;/strong&gt; A specific benign actor → exclude it. A fuzzy overlap with normal behavior → move the threshold. Same goal, opposite move.&lt;/p&gt;
&lt;h3&gt;Validating it&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-4-catching-reconnaissance-and-the-case-sensitivity-bug-that-made-it-look-broken/04-reconnaissance-command-burst-incident.webp&quot; alt=&quot;Reconnaissance Command Burst incident in Defender, Discovery&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I ran a burst of distinct recon commands on the lab VM, waited for ingestion (Sentinel log latency is real — if you run the query the instant you fire the commands, you&apos;ll scare yourself with an empty result that&apos;s just lag, not failure), then confirmed the rule fired once the distinct count crossed 5. Detection and the resulting incident are both screenshotted in the &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;repo&lt;/a&gt; under &lt;code&gt;evidences/&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;What I took from this one&lt;/h3&gt;
&lt;p&gt;Three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;An empty result is not proof the logic is correct.&lt;/strong&gt; Check the data&apos;s actual shape before you touch the query. Case sensitivity, field formatting, and silent undercounting will fool you every time if you let them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Some attacks have no bad ingredient — only a bad recipe.&lt;/strong&gt; No single recon command is suspicious; the cluster is. Detection engineering is often about catching &lt;em&gt;combinations&lt;/em&gt;, not items.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tuning is a decision, not a default.&lt;/strong&gt; Exclude a known-benign actor; raise a threshold against fuzzy overlap. Knowing which is which is the part that takes judgement.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next up, I&apos;m moving off the endpoint entirely and into identity—Entra ID detections, starting with attackers registering their own MFA methods on compromised accounts to keep access after a password reset. Different log source, different threat model. That&apos;s Part 5.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Full lab on &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Building a Sentinel Detection Lab, Part 3: My Detection Flagged Legitimate Windows Behavior. Here&apos;s How I Tuned It Without Going Blind</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-3-my-detection-flagged-legitimate-windows-behavior-here-s-how-i-tuned-it-without-going-blind</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-3-my-detection-flagged-legitimate-windows-behavior-here-s-how-i-tuned-it-without-going-blind</guid><description>Registry persistence, a textbook false positive, and the single most important lesson in detection engineering.</description><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Part 3 of a series on building a detection engineering lab in Microsoft Sentinel. This post: registry persistence, a textbook false positive, and the single most important lesson in detection engineering.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;The threat&lt;/h3&gt;
&lt;p&gt;To survive a reboot, malware commonly writes itself into a registry autostart location—or a Winlogon shell key—so it launches automatically at every login. Sysmon logs registry modifications as &lt;strong&gt;Event ID 13&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;The naive detection&lt;/h3&gt;
&lt;p&gt;The logic seems obvious: flag any write to an autostart location.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where Source == &quot;Microsoft-Windows-Sysmon&quot;
| where EventID == 13
| extend TargetObject = extract(@&quot;TargetObject:\s+(\S+)&quot;, 1, RenderedDescription)
| extend Details = extract(@&quot;Details:\s+(.+?)\s+(?:User:|Image:)&quot;, 1, RenderedDescription)
| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| where TargetObject has_any (@&quot;CurrentVersion\Run&quot;, @&quot;CurrentVersion\RunOnce&quot;, @&quot;Winlogon\Shell&quot;, @&quot;Winlogon\Userinit&quot;)
| project TimeGenerated, Computer, TargetObject, Details, Image
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ran it. It fired straight away—three hits. All of them were &lt;code&gt;userinit.exe&lt;/code&gt; writing &lt;code&gt;ctfmon.exe&lt;/code&gt; to a run key.&lt;/p&gt;
&lt;h3&gt;The false positive&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ctfmon.exe&lt;/code&gt; is the legitimate Windows text/input service (keyboard layouts, handwriting, and speech). Windows itself registers it to autostart via the registry. This happens on every Windows machine, constantly. It is 100% normal.&lt;/p&gt;
&lt;p&gt;My detection wasn&apos;t &lt;em&gt;wrong&lt;/em&gt;—it correctly matched &quot;a write to an autostart location.&quot;&quot;It was &lt;strong&gt;too broad&lt;/strong&gt;. And this is the lesson:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A detection that fires on benign activity is worse than no detection at all.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&apos;s why. If a rule cries wolf on normal behavior, analysts learn to dismiss its alerts. Then, when a &lt;em&gt;real&lt;/em&gt; attack trips the same rule, nobody looks. That&apos;s alert fatigue, and it&apos;s how real intrusions get missed in environments that are technically &quot;monitored.&quot; Tuning out benign noise isn&apos;t cleanup work you do at the end—it &lt;em&gt;is&lt;/em&gt; the detection engineering.&lt;/p&gt;
&lt;h3&gt;The fix: a narrow allowlist&lt;/h3&gt;
&lt;p&gt;The tempting fix is to exclude &lt;code&gt;userinit.exe&lt;/code&gt;. That&apos;s the &lt;em&gt;wrong&lt;/em&gt; fix—an attacker who abuses &lt;code&gt;userinit.exe&lt;/code&gt; (a real technique) would then sail straight through. Good exclusions are &lt;strong&gt;narrow&lt;/strong&gt;: they drop the exact benign pattern, not a whole process.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;| where not(Image endswith &quot;userinit.exe&quot; and Details has &quot;ctfmon.exe&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This excludes &lt;em&gt;only&lt;/em&gt; the specific combination of &lt;code&gt;userinit.exe&lt;/code&gt; writing &lt;code&gt;ctfmon.exe&lt;/code&gt;. Anything else writing to those keys—including malware abusing &lt;code&gt;userinit.exe&lt;/code&gt; — still fires.&lt;/p&gt;
&lt;h3&gt;Validating both sides&lt;/h3&gt;
&lt;p&gt;This is the part people skip. After tuning, I confirmed the detection went quiet on the &lt;code&gt;ctfmon&lt;/code&gt; false positive. But &quot;it stopped firing&quot; proves only half a detection. The other half: Does it &lt;em&gt;still catch real attacks&lt;/em&gt;? Over-tuning is the silent failure mode—you suppress the noise and accidentally blind the rule.&lt;/p&gt;
&lt;p&gt;So I planted a fake persistence entry (harmless — the referenced file doesn&apos;t exist):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;New-ItemProperty -Path &quot;HKLM:\Software\Microsoft\Windows\CurrentVersion\Run&quot; -Name &quot;FakeTestMalware&quot; -Value &quot;C:\temp\evil.exe&quot; -PropertyType String -Force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The detection caught it—while &lt;em&gt;still&lt;/em&gt; ignoring the &lt;code&gt;ctfmon&lt;/code&gt; write. True positive fires, false positive suppressed. That&apos;s a complete detection.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-3-my-detection-flagged-legitimate-windows-behavior-here-s-how-i-tuned-it-without-going-blind/03-persistence-detection.webp&quot; alt=&quot;Registry persistence query catching the planted entry&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The scheduled rule then raised a high-severity incident, MITRE-mapped to &lt;strong&gt;T1547.001&lt;/strong&gt;, with an attack-story graph linking the host. (I removed the fake entries afterward.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-3-my-detection-flagged-legitimate-windows-behavior-here-s-how-i-tuned-it-without-going-blind/03-persistence-incident.webp&quot; alt=&quot;Registry persistence incident, High severity, T1547.001&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;The takeaway that matters most&lt;/h3&gt;
&lt;p&gt;The first two detections taught me how to &lt;em&gt;write&lt;/em&gt; detections. This one taught me how to &lt;em&gt;operate&lt;/em&gt; them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Firing on benign activity is a failure, not a success.&lt;/strong&gt; A noisy rule trains people to ignore it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Allowlist narrowly.&lt;/strong&gt; Exclude the exact benign pattern, never a whole process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validate both sides.&lt;/strong&gt; A detection that stopped firing on noise but no longer catches attacks is worse than the noisy version—at least the noisy one worked.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;That third point is the difference between someone who writes queries and someone who runs detections. You can&apos;t claim a detection works until you&apos;ve watched it catch the real thing &lt;em&gt;and&lt;/em&gt; ignore the fake one.&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Full lab and all three detections on&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Building a Sentinel Detection Lab, Part 2: Suspicious PowerShell, and Why Some Detections Need No Threshold</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-2-suspicious-powershell-and-why-some-detections-need-no-threshold</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-2-suspicious-powershell-and-why-some-detections-need-no-threshold</guid><description>Hunting malicious PowerShell with Sysmon, and the difference between volume detections and signature detections.</description><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Part 2 of a series on building a detection engineering lab in Microsoft Sentinel. This post: hunting malicious PowerShell with Sysmon, and the difference between volume detections and signature detections.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;In &lt;a href=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-1-catching-brute-force-and-the-bug-that-flagged-the-wrong-account&quot;&gt;Part 1&lt;/a&gt; I built a brute-force detection that counted failed logins and fired when the count crossed a threshold. This one is different in a way that turns out to matter: it doesn&apos;t count anything. A single match is enough. Understanding &lt;em&gt;why&lt;/em&gt; is one of the more useful distinctions in detection engineering.&lt;/p&gt;
&lt;h3&gt;The threat&lt;/h3&gt;
&lt;p&gt;Attackers love PowerShell because it&apos;s everywhere and powerful. They tend to launch it with flags that legitimate users rarely combine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;enc&lt;/code&gt; / &lt;code&gt;-EncodedCommand&lt;/code&gt; — base64-encoded commands, to hide intent&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;w hidden&lt;/code&gt; / &lt;code&gt;-windowstyle hidden&lt;/code&gt; — run invisibly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ExecutionPolicy Bypass&lt;/code&gt; — skip PowerShell&apos;s safety controls&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;downloadstring&lt;/code&gt;, &lt;code&gt;iex&lt;/code&gt;, &lt;code&gt;invoke-expression&lt;/code&gt; — download-and-run &quot;cradles&quot; that pull remote code&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sysmon logs every process launch as &lt;strong&gt;Event ID 1&lt;/strong&gt;, including the full command line. That command line is what we hunt.&lt;/p&gt;
&lt;h3&gt;Extracting the command line&lt;/h3&gt;
&lt;p&gt;Same lesson as Part 1: the useful data lives inside &lt;code&gt;RenderedDescription&lt;/code&gt;, not a tidy column. A Sysmon process-creation event packs in the image path, command line, parent process, user, and hashes — all as text. So I extract the two fields I need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| extend CommandLine = extract(@&quot;CommandLine:\s+(.+?)\s+CurrentDirectory:&quot;, 1, RenderedDescription)    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CommandLine&lt;/code&gt; extraction uses a lazy &lt;code&gt;(.+?)&lt;/code&gt; and anchors on the next field (&lt;code&gt;CurrentDirectory:&lt;/code&gt;) so it grabs exactly the command line and nothing more.&lt;/p&gt;
&lt;h3&gt;The detection&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where Source == &quot;Microsoft-Windows-Sysmon&quot;
| where EventID == 1
| extend Image = extract(@&quot;Image:\s+(\S+)&quot;, 1, RenderedDescription)
| extend CommandLine = extract(@&quot;CommandLine:\s+(.+?)\s+CurrentDirectory:&quot;, 1, RenderedDescription)
| where Image has &quot;powershell&quot; or Image has &quot;pwsh&quot;
| where CommandLine has_any (&quot;-enc&quot;, &quot;-EncodedCommand&quot;, &quot;-nop&quot;, &quot;-noprofile&quot;, &quot;-w hidden&quot;, &quot;-windowstyle hidden&quot;, &quot;-exec bypass&quot;, &quot;-executionpolicy bypass&quot;, &quot;downloadstring&quot;, &quot;iex&quot;, &quot;invoke-expression&quot;, &quot;frombase64string&quot;)
| project TimeGenerated, Computer, Image, CommandLine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key operator is &lt;code&gt;has_any(...)&lt;/code&gt; — it matches if the command line contains &lt;em&gt;any&lt;/em&gt; of those suspicious strings.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-2-suspicious-powershell-and-why-some-detections-need-no-threshold/02-powershell-detection.webp&quot; alt=&quot;Suspicious PowerShell query showing flagged command lines&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Volume detection vs. signature detection&lt;/h3&gt;
&lt;p&gt;Here&apos;s the distinction that matters. The brute-force rule in Part 1 needed a &lt;strong&gt;threshold&lt;/strong&gt; (&lt;code&gt;count &amp;gt;= 5&lt;/code&gt;) because no single failed login is suspicious — &lt;em&gt;volume&lt;/em&gt; is the signal. This PowerShell rule needs no threshold at all. A &lt;em&gt;single&lt;/em&gt; encoded, hidden PowerShell launch is suspicious on its own — the &lt;em&gt;signature&lt;/em&gt; is the signal.&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Volume detections&lt;/strong&gt; aggregate and threshold (brute force, password spray, data exfil).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Signature detections&lt;/strong&gt; fire on a single match (known-bad command flags, known-bad file hashes, known-bad registry keys).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Knowing which kind you&apos;re writing tells you whether you need &lt;code&gt;summarize&lt;/code&gt; and a count or just a &lt;code&gt;where&lt;/code&gt; clause that matches the bad thing.&lt;/p&gt;
&lt;h3&gt;Validation&lt;/h3&gt;
&lt;p&gt;There was no malicious PowerShell in my logs to start with — so unlike the brute-force rule (where failed logins already existed), I had to &lt;em&gt;generate&lt;/em&gt; the test data first. I ran benign-but-suspicious-looking PowerShell on the VM:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;powershell.exe -nop -w hidden -enc SQBFAFgA
powershell.exe -ExecutionPolicy Bypass -Command &quot;Write-Host hello&quot;
powershell.exe -windowstyle hidden -command &quot;Get-Process&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are harmless — they carry the &lt;em&gt;flags&lt;/em&gt; attackers use but do nothing damaging. Importantly, the process &lt;em&gt;launch&lt;/em&gt; is the logged event even when the command itself errors. The detection caught all of them, and the scheduled rule raised incidents under the Execution category.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-2-suspicious-powershell-and-why-some-detections-need-no-threshold/02-powershell-incident.webp&quot; alt=&quot;Suspicious PowerShell incidents in Defender, Execution&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Known false positives (and tbeing honest about them)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;-nop&lt;/code&gt; and &lt;code&gt;-ExecutionPolicy Bypass&lt;/code&gt; are &lt;em&gt;occasionally&lt;/em&gt; used by legitimate admin scripts. In production I&apos;d tune this — require two or more suspicious flags together, or allowlist known-good signed scripts by hash. For a lab, single-flag matching is fine and demonstrates the technique. But pretending a detection has zero false positives is how you lose credibility; naming them is part of the job.&lt;/p&gt;
&lt;h3&gt;Takeaways&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not every detection needs a threshold.&lt;/strong&gt; Decide whether volume or signature is your signal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;has_any()&lt;/code&gt; &lt;strong&gt;is your friend&lt;/strong&gt; for matching against a list of known-bad strings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Generate test data first&lt;/strong&gt; for signature detections — the malicious thing has to exist before you can catch it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next post: registry persistence — and the false positive that taught me the single most important lesson in detection engineering.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Full lab and detections on&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item><item><title>Building a Sentinel Detection Lab, Part 1: Catching Brute Force (and the Bug That Flagged the Wrong Account)</title><link>https://knowngood.au/writing/building-a-sentinel-detection-lab-part-1-catching-brute-force-and-the-bug-that-flagged-the-wrong-account</link><guid isPermaLink="true">https://knowngood.au/writing/building-a-sentinel-detection-lab-part-1-catching-brute-force-and-the-bug-that-flagged-the-wrong-account</guid><description>Writing my first detection, and the parsing bug that taught me more than the detection itself.</description><pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Part 1 of a series on building a detection engineering lab in Microsoft Sentinel from scratch. This post: writing my first detection, and the parsing bug that taught me more than the detection itself.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Repo:&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;github.com/Kajal-Dhanjal/sentinel-detection-lab&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I&apos;m building a detection engineering lab in Microsoft Sentinel — a Windows VM shipping security and Sysmon logs into a Log Analytics workspace, where I write KQL detections against simulated attacks. This is the first detection I wrote, and the first one that taught me why detection engineering is harder than it looks. The goal was simple on paper: detect a brute-force login attack. Someone hammering a machine with wrong passwords should trigger an alert. What I learned is that &quot;simple on paper&quot; hides three problems that only show up when you actually build it.&lt;/p&gt;
&lt;h3&gt;The setup&lt;/h3&gt;
&lt;p&gt;My VM forwards Windows Security events into a Log Analytics table. A failed login writes Event ID 4625 — the single most important number in this detection. So my first query was the obvious one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where EventLog == &quot;Security&quot;
| where EventID == 4625
| where TimeGenerated &amp;gt; ago(2h)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To generate test data, I ran failed logins by hand on the VM:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;runas /user:fakeuser cmd #wrong password
runas /user:hacker cmd #wrong password
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wrong password each time. Each attempt wrote a 4625. The query returned them. So far, so good—I had failed-login events flowing in. But a list of failed logins isn&apos;t a detection. It&apos;s a search. And the moment I tried to turn it into something that names who was attacked, the first problem appeared.&lt;/p&gt;
&lt;h3&gt;Problem 1: there&apos;s no clean username column&lt;/h3&gt;
&lt;p&gt;I expected a tidy TargetUsername column. There wasn&apos;t one. In my table, the entire human-readable event sits inside a single field, RenderedDescription, as one long text blob:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An account failed to log on. Subject: … Account For Which Logon Failed: … Account Name: …&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To detect on the username, I couldn&apos;t just reference a column — I had to &lt;em&gt;extract&lt;/em&gt; it from text. That meant regex. KQL has &lt;code&gt;extract()&lt;/code&gt; for exactly this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;| extend TargetAccount = extract(@&quot;Account Name:\s+(\S+)&quot;, 1, RenderedDescription)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This says: search the text for &quot;Account Name:&quot; followed by whitespace, then capture the next chunk of non-space characters. The 1 means &quot;give me the first captured group.&quot; I ran it, looked at the TargetAccount column, and it said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;labadmin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which is my own admin account. Not the attacker. That&apos;s problem two.&lt;/p&gt;
&lt;h3&gt;Problem 2: the regex caught the wrong account&lt;/h3&gt;
&lt;p&gt;It turns out a 4625 event contains two &quot;Account Name:&quot; lines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Subject — the account that ran the process triggering the login attempt. That was labadmin (me, in my admin shell, running runas).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Account For Which Logon Failed — the account that was actually attacked. That was fakeuser.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My regex grabbed the first match it found — the Subject — so my detection was reporting the legitimate user instead of the attacker. In a real SOC, this is not a cosmetic bug. A brute-force rule that names the victim&apos;s own admin account instead of the attacker sends responders chasing the wrong lead. The fix was to anchor the regex on the right section first, then jump to the next &quot;Account Name:&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;| extend TargetAccount = extract(@&quot;Account For Which Logon Failed:[\s\S]*?Account Name:\s+(\S+)&quot;, 1, RenderedDescription)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;[\s\S]*?&lt;/code&gt; lazily skips everything (including newlines) between &quot;Account For Which Logon Failed:&quot; and the next &quot;Account Name:&quot;. Now &lt;code&gt;TargetAccount&lt;/code&gt; returned &lt;code&gt;fakeuser&lt;/code&gt;. The detection was finally looking at the attacker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lesson: when you parse log text, don&apos;t trust the first match. Know the structure of the event, and anchor on the part you actually want.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Turning a search into a detection&lt;/h3&gt;
&lt;p&gt;Extracting the username gives you data. A &lt;em&gt;detection&lt;/em&gt; needs a threshold — the logic that says &quot;this pattern is suspicious.&quot; For brute force, that&apos;s volume: many failures in a short window.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kusto&quot;&gt;Event
| where EventLog == &quot;Security&quot;
| where EventID == 4625
| extend TargetAccount = extract(@&quot;Account For Which Logon Failed:[\s\S]*?Account Name:\s+(\S+)&quot;, 1, RenderedDescription)
| where isnotempty(TargetAccount) and TargetAccount != &quot;-&quot;
| summarize FailedAttempts = count(), TargetedAccounts = make_set(TargetAccount) by Computer, bin(TimeGenerated, 5m)
| where FailedAttempts &amp;gt;= 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two lines that matter:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;summarize ... by Computer, bin(TimeGenerated, 5m)&lt;/code&gt; counts failures per machine in 5-minute buckets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;where FailedAttempts &amp;gt;= 5&lt;/code&gt; is the threshold. Below it, normal. Above it, flag it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;That threshold line is the difference between a log search and a detection. And it created problem three.&lt;/p&gt;
&lt;h3&gt;Problem 3: the detection didn&apos;t fire — and that was correct&lt;/h3&gt;
&lt;p&gt;I ran it. No results. My first instinct was that the query was broken. It wasn&apos;t. The issue was my test data. I had run runas only a couple of times, minutes apart, while reading documentation between attempts. So my failures were scattered across different 5-minute buckets — no single bucket ever reached 5.&lt;/p&gt;
&lt;p&gt;This is the central tension in detection tuning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Too tight a window misses slow, low-and-slow attacks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Too loose a window floods you with false positives from people fat-fingering their passwords.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A real brute-force bot makes dozens of attempts per minute, so a &quot;5 in 5 minutes&quot; threshold catches it instantly. My manual testing was slow and sparse, so it slipped through — not because the rule was wrong, but because my test didn&apos;t resemble a real attack&apos;s tempo. That distinction matters. The right move wasn&apos;t to lower the threshold to 1 (which would flag every typo in production). It was to recognize the rule was tuned correctly for real attacks, and that my test data simply needed to be a faster burst to validate it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctx = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(&apos;Machine&apos;)
1..8 | ForEach-Object { $ctx.ValidateCredentials(&quot;attacker$_&quot;,&quot;WrongPass123!&quot;) | Out-Null }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-1-catching-brute-force-and-the-bug-that-flagged-the-wrong-account/01-brute-force-detection.webp&quot; alt=&quot;Brute-force query returning 25 failed attempts with attacker accounts&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Shipping it&lt;/h3&gt;
&lt;p&gt;I saved the query as a scheduled analytics rule: runs every 5 minutes, mapped to MITRE &lt;strong&gt;T1110 (Brute Force)&lt;/strong&gt;, severity Medium, with the host mapped as an entity so any alert builds an investigation graph. It&apos;s now a live detection — not a query I ran once, but a rule watching for brute force on its own.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://knowngood.au/writing/building-a-sentinel-detection-lab-part-1-catching-brute-force-and-the-bug-that-flagged-the-wrong-account/01-brute-force-incident.webp&quot; alt=&quot;Brute-force incident raised in Defender, Credential Access&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;What I&apos;d do in v2&lt;/h3&gt;
&lt;p&gt;One refinement I left for later: right now the rule groups by &lt;em&gt;host&lt;/em&gt; and collects targeted accounts into a set. A more precise version groups by &lt;em&gt;individual account&lt;/em&gt; — flagging &quot;5+ failures against one specific user&quot; rather than &quot;5+ failures total on the box.&quot; That&apos;s a better detection (password-spray vs. single-account brute force look different), and it makes the account cleanly mappable as an entity. That&apos;s the next iteration.&lt;/p&gt;
&lt;h3&gt;Takeaways&lt;/h3&gt;
&lt;p&gt;If you&apos;re starting detection engineering, the three things this rule taught me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log data is messier than the column names suggest.&lt;/strong&gt; Be ready to parse text, and know the structure of your events before you write regex.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The first regex match is often the wrong one.&lt;/strong&gt; Anchor on the section you actually want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A detection not firing isn&apos;t always a bug.&lt;/strong&gt; Sometimes your test data just doesn&apos;t look like a real attack — and understanding &lt;em&gt;why&lt;/em&gt; is half the skill.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next up in the lab: a Sysmon-based detection for suspicious PowerShell. More on that soon.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Full lab and detections on&lt;/em&gt; &lt;a href=&quot;https://github.com/Kajal-Dhanjal/sentinel-detection-lab&quot;&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;This is part of a series on building a detection engineering lab in Microsoft Sentinel from scratch.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>Microsoft Sentinel</category></item></channel></rss>