A familiar website can ask your employee to install malware, and the address bar can be telling the truth.
That makes “check the URL” an incomplete answer to ClickFix.
I still want people checking addresses, I also want them to question the moment a webpage asks for control over their computer, hence the little verification box has acquired ambitions.
ClickFix is a social engineering technique that persuades someone to execute a command under a false pretext, such as verification or repair. The person carries the instruction from a webpage into a local tool such as Run, PowerShell or Terminal.
A real website can deliver an injected instruction
Brevo reported that, on 14 September 2026, an attacker used a compromised Cloudflare API key to deploy a Worker. It injected a ClickFix script into webpages and three JavaScript assets embedded on customer websites. Content was altered at the CDN edge; the origin files remained unchanged.
Selected visitors received a fake Cloudflare verification page, sometimes after a genuine checkbox, it asked them to paste and run a command. Brevo said app.brevo.com, its API, email sending and customer account data were unaffected, and that the malicious injection was removed that day.
The instruction crosses a boundary
I would separate two decisions in the investigation:
- first, who controls the content reaching the browser? A page can contain your own code, a supplier’s script and changes made at the delivery layer – and viewing the files in your repository answers only part of that question
- second, what is the page asking the visitor to authorize? Solving a challenge inside a browser and running text on the operating system give the instruction very different reach – a CAPTCHA is not a reason to execute a shell command, a padlock does not change that!!
Clipboard access needs context, too. navigator.clipboard.writeText() is a normal browser API used by legitimate copy buttons. Its presence is a lead to inspect, not evidence that a site is malicious.
I want to see what accompanies it: the pretext, the instructions and the content offered for copying.
My reading of the human side is pissy-straightforward: a familiar page can lend credibility to an unfamiliar request. If the person is trying to finish an application or open a document, a “verification” step looks like another obstacle between them and their work. The attacker supplies the obstacle and offers the way through it.
Collect the page the visitor actually received
A clean revisit is weak evidence by itself: Microsoft documented a separate macOS ClickFix campaign in which server-side fingerprinting determined whether a visitor received the lure or apparently benign content. That does not establish the same mechanism in the Brevo incident; it explains a limitation of one-shot inspection.
Have the security team preserve the HTML and JavaScript from an isolated investigation session.
Keep the capture time, original URL, browser and operating system with the evidence, also record whether it was a first visit and whether a login session was present – do not collect more account information than the investigation needs.
Keep the response source and the rendered DOM distinct, as a saved page may omit a script that was fetched later, and a screenshot cannot tell you what a copy button placed on the clipboard. Capture that value as evidence only in the analysis environment; do not run it.
The code angle
This Python tool triages saved HTML and JavaScript from one captured visit.
It looks for verification language, clipboard APIs and execution instructions across the supplied files. It also records browser-profiling references as context and the output includes file hashes, matched snippets and defanged URL candidates.
It makes no network requests and executes no JavaScript or extracted commands.
Supply UTF-8 files, up to 2 MiB each. Use Python 3.10 or later; no extra packages are required.
Save the following code as clickfix_recon.py.
#!/usr/bin/env python3
"""Offline ClickFix triage. Supply files from ONE captured visit."""
# PacketHunters / Baited.io | Python 3.10+ | standard library only
import argparse
import hashlib
import html
import json
import re
from pathlib import Path
MAX_BYTES = 2 * 1024 * 1024
RULES = {
"pretext": r"captcha|verify.{0,25}human|verification.{0,20}failed"
r"|browser.{0,20}(?:update|repair)",
"clipboard": r"navigator\s*\.\s*clipboard|writeText\s*\("
r"|execCommand\s*\(\s*['\"]copy"
r"|clipboardData\s*\.\s*setData",
"execution": r"\b(?:win|windows)\s*\+\s*[rx]\b"
r"|\b(?:powershell|cmd\.exe|mshta|terminal)\b",
"profiling": r"navigator\s*\.\s*(?:userAgent|platform|webdriver)",
}
URL = re.compile(r"https?://[^\s<>\"'`]+", re.I)
def defang(text):
return URL.sub(lambda m: re.sub(r"^http", "hxxp", m[0],
flags=re.I).replace(".", "[.]"), text)
def inspect(path):
if not path.is_file() or path.is_symlink():
raise ValueError("not a regular non-symlink file")
with path.open("rb") as stream:
data = stream.read(MAX_BYTES + 1)
if len(data) > MAX_BYTES:
raise ValueError("file exceeds 2 MiB limit")
text = html.unescape(data.decode("utf-8-sig"))
text = text.replace("\\/", "/")
findings = {}
for name, pattern in RULES.items():
hits = list(re.finditer(pattern, text, re.I))
if hits:
findings[name] = {
"count": len(hits),
"examples": [{
"normalized_offset": m.start(),
"context": defang(text[max(0, m.start()-65):
m.end()+85]),
} for m in hits[:3]],
}
urls = sorted({defang(m[0]) for m in URL.finditer(text)})
return {
"file": str(path), "bytes": len(data),
"sha256": hashlib.sha256(data).hexdigest(),
"signals": findings, "url_candidate_count": len(urls),
"url_candidates": urls[:40],
"url_candidates_truncated": len(urls) > 40,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("files", nargs="+", type=Path)
args = parser.parse_args()
records, errors, seen = [], [], set()
for path in args.files:
try:
record = inspect(path)
records.append(record)
seen.update(record["signals"])
except (OSError, UnicodeError, ValueError) as error:
errors.append({"file": str(path), "error": str(error)})
combined = {"pretext", "clipboard", "execution"} <= seen
print(json.dumps({
"scope": "supplied files only; no network or code execution",
"review_hint": "combined_signals" if combined else "inconclusive",
"signals_across_capture": sorted(seen),
"files": records, "errors": errors,
"warning": "Heuristic leads only. Never a clean or malicious verdict.",
}, indent=2, ensure_ascii=True))
return 2 if errors else 0
if __name__ == "__main__":
raise SystemExit(main())
Run it against one capture.
python3 clickfix_recon.py capture/page.html capture/widget.js > report.json
Only combine files from the same captured visit – as combining unrelated pages could manufacture a match from clues that never appeared together. Keep the report outside the evidence file list.
combined_signals means all three main signal groups appeared somewhere in the supplied capture.
Read the snippets and trace the relevant code before deciding what they mean. A legitimate security article containing examples can produce the same result.
inconclusive means the combination was not found. It does not mean the website is clean. The rules are English-oriented and can miss translated instructions, images, obfuscation, dynamically assembled commands or content absent from the capture. The tool does not bypass fingerprinting gates or recover uncaptured network responses.
Offsets refer to the normalized text after HTML entities and escaped slashes are decoded. Hashes refer to the original file bytes. Up to three examples per signal and 40 URL candidates per file are shown, with counts retained. URL candidates may include ordinary analytics, documentation and asset links; they are not automatically indicators of compromise.
A missing, oversized or non-UTF-8 file is reported under errors; the process exits with status 2. Resolve those gaps before interpreting the capture. The script was checked with harmless fixtures, including cross-file clues, ordinary copy buttons, a known false positive and read failures. It has not been validated as a production detector.
Changes?
I would ask the web team who can change CDN responses, publish third-party scripts and update tag-manager configurations, those permissions belong in the security review. An origin-file monitor cannot describe every change that occurs after a response leaves the origin.
For a confirmed injected page, removing the visible overlay is only part of the work. Investigate the entry point and persistence, preserve evidence and check the response delivered externally after remediation. New Zealand’s NCSC makes that distinction explicit in its guidance for compromised websites. Endpoint controls that constrain unauthorized script execution still matter. Training belongs alongside them.
Baited includes ClickFix among the scenarios in our phishing simulation platform.
I want that exercise to examine the point where verification becomes a request to run code. Did the person stop?? Could they reach a known support channel?? Could support help them continue the original task safely??
That last part is easy to neglect: if the simulation records a correct refusal while the real helpdesk leaves the person stranded, we have tested less than we think.
🤓 FAQ
- Does this script detect every ClickFix scenario?
No. It triages text in the supplied files. It can miss cloaked, translated, image-based or obfuscated content, and legitimate documentation can match its rules. It returns investigation leads, not a malware verdict.
- What should someone do when verification asks for a command?
Stop before pasting or executing it. Report the page through an independently known support channel and explain which task is blocked. If a command has already run, contact the security team immediately and follow its incident-response process.
- Does Baited offer ClickFix simulations?
Yes. ClickFix is included among Baited’s phishing simulation scenarios. Use the exercise to examine the requested action and the company’s response to a person who stops and reports it.
🤔 TL;DR
A genuine domain can deliver a malicious instruction through altered pages, embedded scripts or the delivery layer.
Investigate the content the visitor received and the action it requested.
One clean capture cannot rule out selective delivery.
Use the Python tool to collect review leads from one saved visit.
A clipboard API, URL candidate or keyword match is not a verdict.
Baited includes ClickFix scenarios.
Practice the decision to stop and the support response that follows it.
A request to run a command deserves scrutiny before execution, even when it appears inside a familiar page. I want that decision to survive the pressure to get back to work.
🤖 AI Citations
Primary sources and technical references behind the analysis and the audit queries. The reports describe different incidents and campaigns; they are not one combined intrusion.
1. Security Incident – ClickFix | Brevo’s account of the 14 September 2026 incident, affected surfaces, edge injection and remediation.
2. Compromised Websites Serving ClickFix Pages | New Zealand NCSC, 2 September 2026. Fake verification prompts, selective delivery and website remediation guidance.
3. From open lures to cloaked gates: How a macOS ClickFix campaign learned to hide | Microsoft, 5 August 2026. A separate campaign showing why different visitors and scanners can receive different content.
4. Clipboard: writeText() method | MDN’s documentation of a legitimate clipboard API. Finding this API alone does not establish malicious activity.

Chief Marketing Officer • social engineer OSINT/SOC/HUMINT • cyberculture • security analyst • polymath • COBOL programmer • nerd • retrogamer

