We tried to prevent ClickFix in the lab — and measured whether it actually worked

In Delete the category we argued that the strongest move against a whole class of attacks is to make the mistake impossible rather than catchable. clickfix-fake-captcha is the clean test case for that idea: the lure delivers no file. A fake CAPTCHA or error tells the victim to paste a command and run it themselves — almost always through a script interpreter (powershell, mshta, wscript, cscript). The prevention claim is blunt: deny those interpreters to the standard user and the pasted command has nothing to run in. No judgement call per command, no detection race — the execution path is simply gone.
That was a hypothesis. This is us measuring it.
The A/B
Same victim, same user, same pasted command. The only thing that changes between the two runs is the control:
| run | control | what we expected |
|---|---|---|
| OFF (baseline) | interpreter allowed for the user | the command runs |
| ON | interpreter denied for the user via AppLocker | the command is blocked, and the denial is logged |
The "payload" is benign by construction — it only writes a short, run-scoped marker file, standing in for "the interpreter executed what the lure told the user to paste." Two signals decide the outcome, and both come from the SIEM (Wazuh), not from anyone's say-so:
- Reached — the marker's success event fires (lab rule
100320). The interpreter ran. - Blocked — an AppLocker
EventID 8004denial for the interpreter fires (lab rule100321). The control bit.
What actually happened
- OFF (
run-2026-10-26-021): the interpreter ran, the marker event fired, rule100320matched. Objective reached — the baseline works, so the test isn't rigged to pass. - ON (
run-2026-10-27-001): no marker, and an AppLocker8004denial fired, rule100321matched. Objective blocked.
Reached with the control off, blocked with it on. On the evidence, the pair grades proven: the control deletes the execution path for this attack, measured end to end on an instrumented host rather than asserted from a rule count.
Three things we're saying out loud, because they're the honest part
A prevention claim is only as good as what it refuses to hide. Three caveats shaped this result:
We measured the execution control, not the Win+R toggle. The pattern names two controls — disabling the Run dialog (a UI control) and denying the interpreters (an execution control). Our harness launches the command through VMware Tools, never through the Run dialog, so it can only honestly measure the execution control: interpreter denial via AppLocker. That's the one that neutralises the payload regardless of how it was pasted. The UI toggle is a complementary hardening step, checked by hand, not by this run.
The measured interpreter was cscript, not mshta. mshta's HTML-application engine writes nothing under our non-interactive automation session, so measuring it would have proven nothing either way. We ran the same one-liner through cscript, which executes it headless — and which is denied by the same AppLocker control and caught by the same block rule. mshta stays the canonical lure in the human runbook; cscript is what we could measure faithfully.
The control only bites after a reboot, and our first ON run proved it by failing. AppLocker caches its policy at boot, so an earlier ON attempt (run-2026-10-26-022) — control applied but no reboot — produced neither a marker nor a denial. By our own rule that is unverifiable, not a win: a silent run never counts as a block. We rebooted, re-ran, and only then got the logged denial above. We're keeping the unverifiable run on the record because it's the reason we trust the one that worked.
Why it matters
Most of security spends its effort on detecting the thing after it starts. This is a small, concrete data point for the opposite bet: for an attack whose entire mechanism is "get a human to run an interpreter," taking the interpreter away doesn't need to win a race — there's no race left. One attack, one measured control, one honest caveat list. That's the unit we want to build a lot more of.
Grounded in the clickfix-prevention-windows A/B pair (OFF run-2026-10-26-021, ON run-2026-10-27-001; inconclusive first attempt run-2026-10-26-022), graded by lab/harness/grade.py's prevention verdict. See the experiment spec (lab/prevention/clickfix-run-box-experiment.md) for the full runbook.