Back to Newsroom
Breach

The files at the origin never changed

Attackers rewrote Brevo's JavaScript at the CDN edge and stripped the security headers on the way out. Every file on the server stayed exactly as it should be, and integrity checks saw nothing.

The files at the origin never changed

The most useful sentence in Brevo's post-mortem is an admission about detection, not about the breach.

Because the malicious Cloudflare Worker rewrote responses at the edge and removed security headers such as Content-Security-Policy, the origin servers and files remained unmodified — and standard integrity checks did not detect the change.

Everything on the server was correct. The problem was in transit.

What happened

Attackers obtained a long-lived Cloudflare API key with full account permissions that had been hardcoded in Brevo's application source code. The key may have been exposed as early as late August, though Brevo reports no evidence of earlier malicious use.

On 14 September they used it to create a Cloudflare Worker, along with routes and DNS records across Brevo's zones, without triggering an alert. For roughly five and a half hours the Worker modified content as it left the CDN.

Affected assets included pages on brevo.com, sendinblue.com, the login, account and onboarding subdomains, and sibforms.com — plus the Brevo forms script, the Conversations widget and the SDK loader that customers embed on their own websites.

Sansec observed the malicious activity between 16:05 and 20:13 UTC and estimates that more than 100,000 customer websites could have loaded an affected component.

That figure deserves care. It is the number of sites that may have served the file, not the number compromised. What actually happened to a given visitor depended on who they were.

Two payloads, selected by visitor

If you were a logged-in WordPress administrator, the script attempted to install a plugin, fetched from an attacker-controlled Brevo CDN subdomain.

It presents itself as "Web Media Optimizer". Once installed it hides itself from the WordPress plugin list, copies itself into the must-use plugins directory so it loads automatically and cannot be deactivated from the interface, and periodically contacts an attacker server for a Base64-encoded URL of JavaScript to inject into visitors' pages. It keeps a backup copy of the last working URL so it continues functioning if that server goes down.

It also plants a hardcoded authentication key allowing the attacker to generate a valid administrator session without knowing any password.

If you were anyone else, you got a full-screen fake Cloudflare verification page, followed by ClickFix instructions telling you to paste a command into Windows.

That overlay was shown to people browsing customer sites — and, notably, to people who clicked an unsubscribe link in a campaign email sent through Brevo.

The malware also contained checks to avoid crawlers, developers and automated scanners, which is why some people reported seeing nothing wrong.

Why this is the interesting failure mode

Most website integrity monitoring watches files on the origin server. Checksums, version control, file change detection. None of it fires here, because nothing on the origin was touched.

The same applies to the security headers. Content-Security-Policy is a defence delivered through the same response as the content it is meant to protect. If something can rewrite the response, it can remove the header first.

A site's own defences travel over the same channel as its content. Compromise the channel and you get both.

The control that would have helped for the embedded third-party scripts is Subresource Integrity — a hash on the <script> tag so the browser refuses a file that does not match.

It is worth being honest about why almost nobody uses it here. SRI requires the hash to change every time the vendor updates the file, and marketing widgets update frequently and without notice. The cost of using it is breakage; the cost of not using it is this. That is a real trade-off, not an oversight, and it is worth deciding deliberately rather than by default.

What to check, specifically

Sansec published concrete hunting instructions. If your site embeds the Brevo tracker, chat widget or a hosted Brevo form, it was serving an affected file during that window.

Access log, 14 September POST /wp-admin/update.php?action=upload-plugin GET /wp-admin/plugins.php?action=activate (shortly after)

Plugins Anything installed or activated on 14 September Compare the plugin directory on disk against the admin screen — the plugin hides itself from the list Check the must-use plugins directory

If found Rotate administrator passwords and invalidate sessions. The hardcoded auth key means a password change alone is not sufficient until the plugin is gone.

And for people rather than servers: anyone who saw a full-page "verify you are human" prompt and followed its instructions ran a command on their own machine. That device needs a scan, and any credentials used on it should be treated as exposed.

The second incident in a week

On 10 September Brevo disclosed a separate compromise, in which an attacker exploited improperly scoped SAML SSO access to reach 138 customer accounts, exporting contacts from 43 and sending phishing from six.

One of those downstream victims was Trezor, which reported that the resulting phishing reached 347,000 email addresses and successfully compromised at least 2,500 users.

Brevo has not publicly established whether the two incidents are connected, and did not answer that question when asked.

Two things follow.

A supplier having two distinct security incidents in four days is a signal in itself, independent of whether they share a root cause. It is reasonable for customers to ask what changed in between.

And Trezor is now the second entry in this newsroom to have been reached through a third party in a single month — first through a logistics provider's exposed reporting platform, now through its email marketing vendor. The company's own systems were not breached in either case. That is the shape of the risk: your security posture is partly a function of decisions made in companies you have a contract with and no visibility into.

What to take from it

  1. Long-lived, full-permission API keys in source code. This is the third article this month where that was the root cause. Scope keys narrowly, give them expiry, and scan repositories and build pipelines for them.
  2. Know what third-party JavaScript your site loads. Marketing widgets, chat tools, analytics, consent banners. Each one executes with your site's privileges, on your visitors' browsers, under your domain's reputation.
  3. Decide about Subresource Integrity deliberately. For scripts that change rarely, use it. For those that do not, write down that you accepted the risk and why.
  4. Origin integrity monitoring is not website integrity monitoring. If you have a CDN or an edge platform, the configuration of that platform is part of your attack surface, and changes to it should be alerted on.
  5. Ask your vendors how their CDN and DNS credentials are scoped. It is an awkward question and it is exactly the one that matters here.

4Tify reviews third-party script exposure and edge configuration — the parts of a website that are not on the server. If you want yours assessed, get in touch.

SHARE
4Tify — Brevo Supply Chain: Edge Injection That Integrity Checks Missed