Brevo Sept 14 supply-chain attack: WordPress admin checklist

For four hours on 14 September 2026, Brevo’s own JavaScript served malware from Brevo’s own domains. Any WordPress site that embedded a Brevo tracker, a Brevo sign-up form, the Brevo Conversations chat widget, or a Brevo-hosted unsubscribe page loaded the payload without touching its own code. The malicious code sniffed the visitor’s WordPress login state and switched behaviour: an admin’s session was used to attempt a silent plugin install; a logged-out visitor got a full-screen ClickFix prompt. If Brevo is anywhere on your site, this needs a look.

The Sansec Forensics Team disclosed the attack on 16 September with full indicators.

What was served, when, from where

Sansec logged the malicious code in the wild between 16:05:18 and 20:12:53 UTC on 14 September 2026. Four Brevo asset classes carried it:

  • sdk-loader.js and brevo-conversations.js served from cdn.brevo.com (the tracker and the chat-widget loader). The chat widget’s iframe is served from conversations-widget.brevo.com; Brevo pages at brevo.com and meet.brevo.com were also compromised.
  • Hosted sign-up forms served from sibforms.com.
  • Unsubscribe pages linked from Brevo-sent newsletters, also served from sibforms.com.

Each of those loaded f.js from a cdn*.sendibt1.com subdomain the attacker created inside Brevo’s own DNS zone.

Two behaviours ran on top of the same payload. For a visitor whose browser held an authenticated WordPress admin session on the site embedding the Brevo asset, the payload attempted a plugin upload from https://cdn10.sendibt1.com/p/wm.zip using the admin’s session cookie. For logged-out visitors, the script drew a full-screen “verify you are human” overlay and instructed the reader to paste and run a command in their operating system’s Run box or terminal, the pattern known as ClickFix. Sansec did not recover wm.zip and calls it “likely a backdoor”; the disclosure carries no post-install analysis.

Sansec’s CSP monitors on twelve customer sites logged 2,549 blocked script-load attempts across the four-hour window. The malicious infrastructure stopped resolving on 15 September and Brevo restored the affected files at origin.

Your four-step audit

The four hours constrain the audit. If nobody on your team was logged into WordPress admin between 16:05 and 20:13 UTC on 14 September, the plugin-upload path had no session to abuse and this drops to a housekeeping task. If someone was, the audit is worth doing tonight.

1. Find every Brevo touchpoint. Grep your active theme and any custom plugins for the exact strings the payload rode on:

grep -rE 'sdk-loader\.js|brevo-conversations\.js|sibforms\.com|brevo\.com|sendinblue' \
  wp-content/themes/<your-theme>/ wp-content/plugins/

Then open the site in an incognito window with the browser’s DevTools Network panel filtering on brevo and sendinblue, and load the pages that would carry a marketing tag: the homepage, a landing page, a sign-up flow, a page that shows the chat widget. Anything served from cdn.brevo.com, conversations-widget.brevo.com, or sibforms.com is a touchpoint you had at the time of the attack. Newsletter unsubscribe pages count too if they are hosted at Brevo rather than on your domain.

2. Pull the touchpoint while you audit. The malicious CDN stopped resolving on 15 September, so a fresh page load today does not carry the payload. That is not the same as certainty that your site was untouched during the window. If you can drop the tracker script, the form block, or the chat-widget snippet for 24 to 48 hours, do it. The cost is a day or two of marketing telemetry; the benefit is a clean environment while you check step three.

3. Audit for plugin uploads in the four-hour window. The payload targeted POST /wp-admin/update.php?action=upload-plugin. Any admin who loaded a page carrying a compromised Brevo asset in that window with an active WordPress session may have triggered one. The audit runs in three places:

  • Access logs. Grep for the plugin-upload endpoint scoped to the window:

    grep 'POST /wp-admin/update.php?action=upload-plugin' access.log \
      | awk '$4 >= "[14/Sep/2026:16:05:00" && $4 <= "[14/Sep/2026:20:13:00"'

    Adjust the date format to your log’s convention. A hit that no member of the team can explain is the strongest evidence of an attempted install.

  • The Plugins screen. Sort by “Installed on” or activation date. Any plugin dated 14 September that nobody on the team remembers installing is suspect. Do not activate or deactivate it while auditing; that changes timestamps.

  • The filesystem. find wp-content/plugins -maxdepth 2 -newermt '2026-09-14' -not -newermt '2026-09-15' lists directories with an mtime inside the day. A directory whose contents match a WordPress plugin structure but whose name does not appear in your team’s install history is the same kind of hit as the log line above.

Because Sansec never recovered wm.zip, there is no filename, hash, or code pattern to match against. The audit checks for the fact of an upload rather than the fingerprint of a specific plugin.

4. Match your findings against Brevo’s public record. Brevo’s status page has two entries relevant here. 10 September 08:30 UTC, “Attacker gained access to client accounts”, records the antecedent breach: per Sansec, six Brevo customer accounts. 14 September 21:00 UTC, “Security Incident – ClickFix”, records the supply-chain event and states that a post-mortem has been published. Neither entry gives a root cause. That is worth knowing before you decide how much weight to put on Sansec’s Cloudflare hypothesis in a report to a client or a manager.

What Brevo has confirmed, and what Sansec suspects

The two published accounts do not fully agree.

Brevo confirms, on its status page, that the incident happened, that it was resolved, and that a post-mortem exists. It has not published a root cause, an IoC list, or an official list of affected assets. Four days on from the disclosure, Brevo’s public record consists of the two status-page entries and the linked post-mortem; no root-cause statement, no IoC list, no affected-customer count beyond Sansec’s six.

Sansec suspects, but does not confirm, that Brevo’s Cloudflare account was compromised. The paper names three indicators: the Last-Modified header on the modified cdn.brevo.com assets stayed identical before, during, and after the attack window, which is what account-level CDN access would allow; five Brevo apex domains that share the same Cloudflare DNS, “suggesting a single Cloudflare account”; and attacker-created cdn* records in the sendibt1.com zone behind Cloudflare’s proxy. That trio is compatible with Cloudflare-account access; it is also compatible with other paths that would allow the same DNS and CDN behaviour. The distinction is not academic. If a client asks “was Brevo hacked?”, the honest answer today is “Brevo’s Sept 14 status page confirms a supply-chain incident and a post-mortem; Sansec’s Cloudflare-compromise theory is the leading external hypothesis and is not yet confirmed by Brevo.”

After the audit

If your site was embedding Brevo but no admin held an active session in the four-hour window, and step three’s audit is clean, restore the embeds when you are ready. Nothing needs to change on your end.

If step three surfaces an unexplained plugin, treat the site as compromised. Rotate WordPress admin passwords and application passwords, invalidate active sessions (wp user reset-password <user> and wp user session destroy <user> --all on WP-CLI; the bulk form is wp user list --field=ID | xargs -n 1 wp user session destroy --all), rotate any credentials the site holds (SMTP, API keys, database passwords if reachable from PHP), and consider restoring from a backup taken before 14 September 16:00 UTC. A backdoor with no recovered sample is the case that most rewards over-response.

Brevo’s transactional SMTP is fine to keep using once your site is clean; the compromised assets were the front-end tracker, forms, chat widget, and unsubscribe pages, not the mail-sending path. Nothing in the disclosed telemetry suggests the SMTP relay itself served anything unexpected. But if you carried the embeds and had an admin logged in on 14 September between 18:05 and 22:13 CEST (17:05-21:13 BST, 12:05-16:13 EDT, 09:05-13:13 PDT), take the audit seriously; the four-hour window and the specific WordPress-admin behaviour make this an unusually targeted supply-chain event for WordPress operators.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *