Reverse-Engineering the Brevo ClickFix Injection: How a Trusted Marketing Script Became a Dropper

Reverse-Engineering the Brevo ClickFix Injection: How a Trusted Marketing Script Became a Dropper

pr0h0
supply-chain-attackclickfixthird-party-scriptscontent-security-policybrevo
AI Usage (75%)

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

  1. The attacker obtains write access to something that reaches customer-facing script output at the vendor.
  2. The vendor serves the modified script from its existing trusted CDN origin — no new domain, no certificate change.
  3. On a customer page, the script renders the ClickFix lure for a subset of visitors.
  4. 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, and etag would 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.write path many injected loaders use
  • constraining connect-src and form-action, which limits where the lure page can send data
  • base-uri 'self' and object-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

ControlWhat it stopsWhat it does not stop
SRI with pinned hashAny byte change in the pinned scriptVendors whose URL content changes on every release; a script served from a different origin
CSP script-src allowlistInline injected script without a valid nonce; scripts from unlisted originsAnything served from the allowlisted vendor origin, including compromised bytes
CSP connect-src / form-actionExfiltration to arbitrary domainsExfiltration through the vendor origin itself
Vendor script hash monitoringSilent content changes between deploysThe first change, if your baseline is already poisoned
Removing the third partyThe entire class of vendor-side riskThe 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

  1. Delete third parties you don't need. Free, and the only fix with no residual risk.
  2. Add hash monitoring on the ones that remain, so a change becomes an alert.
  3. 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

Share this post

More posts

Comments