§ AT

ClickFix Detection: Watching the Run Dialog Instead of the Payload

ClickFix is annoying precisely because it sidesteps the part of the kill chain most SOCs instrumented first. There’s no malicious attachment for the mail gateway to detonate, no drive-by exploit for the EDR’s memory-protection module to catch, no signed-but-evil binary for your allowlist to argue about. The user reads a web page that says “verify you are human” or “your browser needs to be fixed,” copies a string the page helpfully placed on the clipboard, opens the Run dialog, pastes, and presses Enter. The victim runs the command. You detect the consequences, or you detect the moment of execution. The delivery is invisible to most of the stack.

That shifts the detection problem in a way a lot of teams get wrong on the first pass. They write a rule for the payload — the base64 PowerShell, the mshta reaching out to a C2 domain — and they ship it feeling good. Then they find out the interesting telemetry was upstream, in an artifact almost nobody monitors by default.

The mechanism, briefly

The social-engineering wrapper changes monthly. The execution path is stable. A page (or a fake CAPTCHA, or a “meeting failed to load” overlay) instructs the user to press Win+R, Ctrl+V, Enter. The clipboard contents are the command. Because the user typed it into the Run dialog themselves, Windows writes it to a registry key that exists specifically to remember what people type there:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU

Each entry is a single-letter value (a, b, c…) whose data is the literal string typed, suffixed with \1. So if the user pasted a PowerShell one-liner, the full command text is sitting in RunMRU in plaintext, regardless of how obfuscated the actual payload is once it executes. That’s the gift. The encoded blob in the process command line might be a pain to parse; the RunMRU value is the thing the human saw and ran.

The second artifact is the process lineage. When the Run dialog launches something, the parent is explorer.exe. So you get explorer.exepowershell.exe (or cmd.exe, mshta.exe, wscript.exe, increasingly curl.exe piped into something). That parent-child pair is not rare on its own — people launch things from Explorer constantly — but explorer.exe spawning a hidden-window PowerShell that immediately reaches the internet is a different animal.

What to actually detect

Two signals, and you want both because each has a blind spot the other covers.

RunMRU registry writes. With Sysmon you’re looking at Event ID 13 (registry value set), TargetObject ending in \RunMRU\ plus a letter, and the command in Details. The SwiftOnSecurity baseline Sysmon config captures RunMRU out of the box; if you’ve forked it and trimmed registry monitoring for volume reasons, check that you didn’t cut this — it’s a common casualty when someone is fighting EID 13 noise. In Splunk, roughly:

sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=13
  TargetObject="*\\RunMRU\\*"
  Details IN ("*powershell*","*cmd*","*mshta*","*curl*","*\\\\*","*http*","*FromBase64*","*iwr*","*-enc*")

The keyword list is the part you’ll fight with. Too broad and every admin who pastes a UNC path into Run lights you up. Too narrow and the next campaign’s living-off-the-land binary slides past. Start with the interpreters and the network-fetch verbs, and treat \\ (UNC) as a separate, lower-severity bucket rather than folding it into the high-confidence rule.

Suspicious children of explorer.exe. Sysmon EID 1 (or your EDR’s process-creation telemetry), ParentImage is explorer.exe, Image is one of the script interpreters, and the command line carries the usual tells: -w hidden, -nop, -enc, FromBase64String, iex, an http string, mshta with a URL argument. Defender for Endpoint and CrowdStrike both expose the parent process tree, so you don’t strictly need Sysmon for this leg — but the RunMRU leg is much weaker without it, because most EDRs don’t surface that specific registry write as a queryable field by default (Defender’s DeviceRegistryEvents will have it if you query for the RunMRU key path, but it’s not in any default analytic I’d trust to fire).

The reason you want both: RunMRU only populates if the user actually used the Run dialog. Newer ClickFix variants have moved the instruction to the File Explorer address bar (which writes to TypedPaths, a different key — go capture that one too), to the PowerShell window directly, or to a terminal the page tells the user to open. The lineage rule catches the execution even when the registry breadcrumb is in a different key or missing entirely. Conversely, if the payload is something your EDR doesn’t flag as a suspicious child — a plain cmd.exe that does the network fetch two steps later — the RunMRU plaintext is what saves you.

The first week of tuning

Here’s the shape of it the first time you deploy. In a 5,000-seat environment that’s a mix of standard users and a chunk of developers and IT, the lineage rule will be noisy and the RunMRU rule will be quieter but not silent.

The noise comes from exactly who you’d guess. Sysadmins and helpdesk paste UNC paths and \\server\share references into Run all day. Developers launch PowerShell from Explorer constantly, often with flags that overlap your tells (-nop shows up in plenty of legitimate tooling). Software deployment and login scripts can spawn interpreters under contexts that look adjacent if your parent-process normalization is sloppy.

First cut: suppress the UNC-path-only RunMRU hits into a low-priority feed, don’t page on them. Second cut: carve out your admin and developer OUs for the lineage rule, but — and this matters — keep the RunMRU-with-network-keyword rule firing for them, because the whole point of ClickFix targeting is that it hits whoever clicks, including admins. You don’t want to blanket-exempt your most privileged users from the higher-fidelity signal just because their workstations are noisy on the lower-fidelity one. Resist the urge to allowlist by user; allowlist by command pattern.

Expect the high-confidence rule (interpreter or mshta or network-fetch verb written to RunMRU) to settle into single digits per day across a fleet that size once the UNC and known-tooling patterns are carved out. If it’s still firing dozens of times a day after a week, you’ve got a software-distribution or scripting practice that’s writing to RunMRU programmatically, and you need to find that source rather than widen the suppression — because a blanket suppression on a programmatic source is exactly the gap the next campaign walks through.

The false-positive that’ll burn you isn’t volume, it’s the plausible-looking single event: an engineer legitimately pastes a curl | iex-style bootstrap a vendor’s install doc told them to run. That is genuinely indistinguishable from the attack at the telemetry layer, because it is the same technique. The page told the victim to do it; the vendor doc told the engineer to do it. The only discriminator is reputation of the destination and context of the user, which means this rule produces a triage decision, not an automatic verdict. Build it that way. Enrich the alert with the destination domain’s age and reputation up front so the analyst isn’t pivoting to VirusTotal manually on every hit.

Where the environment changes the answer

If you’ve disabled the Run dialog via the NoRun policy (HKCU\...\Policies\Explorer\NoRun), the classic Win+R path dies and so does the RunMRU artifact — but the campaigns already pivoted to the address-bar and terminal-paste variants, so don’t mistake a quiet RunMRU rule for a solved problem. On heavily locked-down workstations with constrained-language-mode PowerShell and AppLocker in enforce, the PowerShell leg gets a lot harder for the attacker, which pushes them toward mshta, curl, and signed-binary proxy execution. Your detection emphasis should shift accordingly; the lineage rule earns its keep more than the keyword rule in those shops.

Time skew is worth a mention if you’re correlating the RunMRU write against the process spawn to raise confidence. The registry write and the EID 1 land within the same second normally, but if your Sysmon hosts are drifting (and some always are — the agent that’s been quietly failing NTP sync since a reimage), a tight correlation window will miss legitimate pairs. Widen the join window to a few seconds rather than demanding same-timestamp.

Agent coverage is the unglamorous limiter. This entire detection assumes Sysmon or an EDR with registry and parent-process telemetry is actually reporting from the endpoint. On the laptop that’s been off the VPN for three weeks, or the contractor BYOD box that never got the agent, none of this exists. ClickFix’s whole value proposition is that it works on the human, and humans use the unmanaged device.

Control mapping

Control Relevance
SI-4 System monitoring — the RunMRU and process-lineage analytics are the instrumentation
SI-3 Malicious code protection — the downstream payload, where EDR catches what slipped the user-execution gate
AT-2 / AT-3 Awareness training — ClickFix is a user-execution technique; “never paste into Run from a web page” belongs in the phishing program
CM-7 Least functionality — NoRun, constrained language mode, AppLocker reduce the available execution paths
AU-6 Audit review and correlation — the registry-write-to-process-spawn join is exactly this
IR-4 Incident handling — the triage decision this rule produces feeds straight into response

The honest framing for an ATO conversation: this is an SI-4 detective control with an AT-2 dependency, and it is weaker than your assessor will assume because the attack relies on the authorized user doing the thing. You can’t fully engineer your way out of a technique whose payload is “a trusted person pressed Enter.” You can make it loud, fast, and triagable. That’s the realistic bar.

Instrument RunMRU and TypedPaths, keep the high-fidelity rule firing even for privileged users, enrich destination reputation at alert time, and stop pretending the payload-side rule was ever the important one.