
Reverse-Engineering the Brevo ClickFix Injection: How a Trusted Marketing Script Became a Dropper
The Brevo incident isn't interesting because of the malware. It's interesting because of how it got in: a trusted marketing script — the kind of thing that gets pasted into a template during a growth sprint, reviewed once, and then never looked at again. This teardown walks through what the public reporting actually confirms, how the ClickFix delivery chain likely worked, why SRI and CSP allowlisting did not stop it, and which third-party script governance controls genuinely reduce blast radius.
What the Brevo Reports Confirm — and What They Do Not
Cybernews and The420.in both covered this on 17 September 2026. Before the claims get restated as established fact, it's worth pinning down what those reports actually say — and where reverse-engineering ends and inference begins.
Facts the reporting supports
- Brevo, a marketing and email platform, was compromised in what the reporting describes as a supply-chain attack.
- A malicious script was injected so it was served to customer websites.
- The reporting says 100,000+ sites were affected.
- Exposure lasted hours, not minutes.
- The payload stage was ClickFix — a fake verification flow that pushes the visitor into running a command themselves.
Facts the reporting does not establish
This part tends to disappear first:
- the exact injection point inside Brevo
- whether this was stolen credentials, an abused API token, a compromised deploy pipeline, or something else
- precise start and end timestamps
- whether anything was exfiltrated from customer sites
- the exact body of the injected script, and how it decided which visitors saw the lure
Why the gap matters before any technical claim gets repeated downstream
"A script was served from a vendor origin" and "the attacker had write access into the vendor's production path" are different problems with different fixes. The first is a blast-radius problem you can design around. The second is a vendor incident-response problem you mostly cannot. Collapse those two into one sentence and teams buy the wrong control — usually a scanner pointed at their own templates, which would have seen nothing.
The Trust Boundary That Turned One Vendor Script Into a Mass Compromise
First-party HTML loading third-party JavaScript from a vendor origin
A tag like this is a same-page, full-privilege inclusion:
<script src="https://cdn.vendor.example/loader.js" async></script>
The browser has no concept of "vendor script." The response body runs in your origin, with your origin's DOM access, your cookies on same-site requests, your localStorage, and your CSP budget. It cannot tell hostile bytes from benign ones. Only a hash match distinguishes them, and almost nobody pins a hash on a marketing tag.
Why marketing and email SaaS scripts are the worst case
They stack every bad property at once: loaded early so they can measure page views, loaded on every route because the tag lives in the layout, cached at the edge and in the browser, owned by a team that isn't the security team, and rarely in anyone's review checklist. They're also usually the largest single script on the page. When I audit for these, the marketing tag is often the only third-party script with a write path into the DOM above the fold.
Why "we trust the vendor" is an architecture decision, not a security control
Trusting the vendor is fine as a business statement. As a security statement it means: if this origin serves different bytes at 03:00, my pages serve them too, and I'll hear about it from a customer. That's an accepted risk written down nowhere. Brevo is a legitimate platform; this incident isn't evidence it was reckless. It's evidence the trust was transitive and unmonitored.
ClickFix Is the Payload Stage, Not the Initial Exploit
ClickFix depends on social engineering
ClickFix isn't an exploit. It's a page that looks like a CAPTCHA or a "verify you are human" gate, with a multi-line instruction telling the visitor to open a run dialog or terminal and paste a command. The command is the malware. Nothing gets exploited; the user gets persuaded.
That is why endpoint tooling sees a user-initiated action
That's the design goal. explorer.exe spawning a child process from a user-pasted command looks a lot more like legitimate admin activity than a browser process launching a binary. The parent is the shell, the initiation is interactive, and the chain never includes the classic browser-to-executable transition heuristics flag hardest.
The injected script only needs to render the lure
The vendor-side script doesn't need to be a dropper. It needs to be a UI. All it has to do is inject an overlay with the right branding and the command text. That's a small amount of JavaScript — which is exactly why it blended in.
Reconstructing the ClickFix Delivery Chain (inference, not verified fact)
Likely delivery sequence
- The attacker obtains write access to something that reaches customer-facing script output at the vendor.
- The vendor serves the modified script from its existing trusted CDN origin — no new domain, no certificate change.
- On a customer page, the script renders the ClickFix lure for a subset of visitors.
- The visitor runs the pasted command; the payload lands outside the browser entirely.
What I would need before stating this as fact
- the injected script body, so the loader logic and targeting are visible
- the response headers on the vendor CDN —
cache-control,age, andetagwould tell you how far the poisoned bytes propagated and how long they persisted - the publish/deploy audit trail at the vendor
I have none of these. The public source material doesn't include them.
Loader mechanics are unverified
The exact loader mechanics aren't public. Version targeting, geofencing, time-of-day gating, and the choice of which visitors got the lure are all plausible and all unverified. I'm flagging it because the reconstruction above reads more confident than the evidence supports, and I'd rather label it as inference than have it cited back as a finding.
Why SRI and CSP Allowlisting Did Not Stop the Injected Script
SRI pins a hash to one specific script body
Subresource Integrity is the right control in principle:
<script
src="https://cdn.vendor.example/loader.js"
integrity="sha384-<hash>"
crossorigin="anonymous"></script>
It also breaks the moment the vendor ships a content update, which for a marketing tag is a weekly event. So it rarely gets deployed for these vendors, and the industry's practical answer has been to skip it. That's a real trade-off, not laziness.
CSP allowlisting a vendor origin permits whatever that origin serves today
script-src https://cdn.vendor.example is a promise about an origin, not about content. If the allowlisted origin starts serving hostile JavaScript, the policy is satisfied. This is the most misunderstood property of allowlist CSP, and it's the reason CSP is not a supply-chain control.
Where CSP still helps
- blocking injected inline script when a nonce or hash policy is in place — which closes the
innerHTML/document.writepath many injected loaders use - constraining
connect-srcandform-action, which limits where the lure page can send data base-uri 'self'andobject-src 'none', which remove some redirect and plugin paths- capping the post-lure payload, if the lure tries to fetch a second stage
Control comparison: what each defense actually stops
| Control | What it stops | What it does not stop |
|---|---|---|
| SRI with pinned hash | Any byte change in the pinned script | Vendors whose URL content changes on every release; a script served from a different origin |
CSP script-src allowlist | Inline injected script without a valid nonce; scripts from unlisted origins | Anything served from the allowlisted vendor origin, including compromised bytes |
CSP connect-src / form-action | Exfiltration to arbitrary domains | Exfiltration through the vendor origin itself |
| Vendor script hash monitoring | Silent content changes between deploys | The first change, if your baseline is already poisoned |
| Removing the third party | The entire class of vendor-side risk | The functionality you actually wanted |
Third-Party Script Governance That Actually Reduces Blast Radius
Inventory every third-party origin and what it is allowed to do
Not just the URL. Record whether it can touch the DOM, read cookies, open sockets, and write storage. A vendor that only needs to receive a page-view beacon shouldn't sit in the same risk bucket as a tag manager.
Move non-essential vendors behind consent or a delay
If a tag isn't required for the page to function, it shouldn't block first render or run on checkout, login, and password-reset routes. That's a real reduction in exposed surface, not a compliance ritual.
Prefer one mediated tag manager with an explicit allowlist
A single reviewed loader with an explicit list of approved destinations beats fifteen <script> tags scattered across templates. One place to audit, one place to revoke, one place to monitor.
Add SRI only where the vendor publishes versioned, immutable URLs
If the vendor publishes /v3.2.1/loader.js with frozen content, pin the hash. If they publish /loader.js that changes weekly, SRI isn't available to you — say so in the risk register instead of pretending otherwise.
Monitor vendor script hashes and origins over time
This is the control I'd push hardest, because it turns a silent change into an alert. A cron job that fetches each approved script and diffs the hash:
curl -sS -o /tmp/loader.js \
-H 'User-Agent: script-baseline/1.0' \
'https://cdn.vendor.example/loader.js'
sha256sum /tmp/loader.js
## 9f2a... /tmp/loader.js
Compare that against a committed baseline and page someone when it differs. Expected output on a change looks like this — illustrative, I did not run this against Brevo's CDN, so treat the digest as a placeholder:
expected 9f2a1c... loader.js
actual c41e08... loader.js
DIFF: content changed 4h ago (age: 14387, etag: "8b1c-9f")
The age and etag headers matter more than the digest alone; they tell you when the change appeared and whether edge caches still hold the old copy.
If You Might Have Been Serving the Injected Script: Response Steps
Preserve evidence before you remove anything
Pull CDN and origin access logs for the script path, snapshot the rendered HTML, and capture the vendor response with headers and hash:
curl -sS -D headers.txt -o vendor.js \
'https://cdn.vendor.example/loader.js'
sha256sum vendor.js && cat headers.txt
Do this first. Once the vendor rotates the file, that artifact is gone.
Remove the vendor script, then check the rest of the surface
Removing the tag from the layout isn't the end. Check other templates, cached edge copies, older deployments, and any server-side injection of the same tag.
Purge caches and verify with a fresh, cache-busting fetch
curl -sS -o /dev/null -w '%{http_code}\n' \
-H 'Cache-Control: no-cache' \
'https://www.example.com/checkout?cb=20260917'
Confirm the tag is absent from the served HTML, not just from your repository.
Customer comms and endpoint guidance, not password resets
The real risk here is to end users, not to your server. Anyone who clicked and pasted the command needs endpoint checks, not a password reset. Your server was never the target.
What I Would Fix First: Fewer Trusted Script Origins
Clear position
The highest-value fix is reducing the number of trusted script origins. Not another scanner, not a CSP report-only rollout nobody reads. Every third-party origin in your <head> is a permanent, transitive write path into your users' sessions, and the only control that reliably removes it is not having it.
Rank order of fixes
- Delete third parties you don't need. Free, and the only fix with no residual risk.
- Add hash monitoring on the ones that remain, so a change becomes an alert.
- Tighten CSP for defense in depth — nonces,
connect-src,form-action,base-uri.
Ranked that way because SRI is mostly unavailable for marketing tags and CSP allowlisting doesn't stop a compromised allowlisted origin. Monitoring is the only middle layer that catches this scenario.
Honest limitation
None of the above would have stopped a vendor-side compromise of a script you genuinely need. If you must run a vendor's script on every page, you've accepted that their compromise is your incident. What you control is how many vendors that sentence applies to, and how fast you find out.
What I Confirmed vs What I Did Not Test
Confirmed from the reporting: the Brevo compromise, malicious script injection into customer sites, 100,000+ sites affected, hours of exposure, ClickFix as the payload stage, and reporting dates of 17 September 2026 via Cybernews and The420.in.
Not established and not tested by me: the injection vector, exact timestamps, whether customer data was exfiltrated, the injected script body, CDN response headers, and any visitor-targeting logic. The delivery-chain sequence above is my reconstruction, explicitly marked as inference. The commands are written to be runnable, but I did not run them against Brevo infrastructure, and the sample output shown is illustrative rather than captured.
Further Reading
- Brevo hack: 100,000+ websites serve malware for hours in a supply chain attack — Cybernews, 17 September 2026 (aggregator link as provided in the source material)
- Brevo Supply-Chain Attack Injects ClickFix Malware Into Customer Websites — The420.in, 17 September 2026 (aggregator link as provided in the source material)
- Subresource Integrity — W3C Recommendation — the spec behind
integrityon script tags - Content Security Policy Level 3 — W3C Working Draft —
script-src,connect-src,form-action,base-urisemantics - MDN: Content Security Policy — practical CSP reference for the defensive examples
- MDN: Subresource Integrity — SRI usage and
crossoriginrequirements


