CVE-2026-95864 vulnerability and BitFire protection

How BitFire Blocks CVE-2026-95864: Themify Builder Stored XSS

WordPress vulnerability research

BitFire FREE Bot Protection and WAF stop the unauthenticated AJAX request behind CVE-2026-95864 before it stores script on your site.

Unauthenticated High severity — CVSS 7.2 Stored XSS on every page view Stored cross-site scripting
BitFire · Vulnerability advisoryResearch published
AdvisoryCVE-2026-95864
ComponentThemify Builder
Relevant sourceclasses/class-themify-builder-stylesheet.php — save_builder_css() / sanitize_on_the_fly_css(); render sink: themify/class-themify-enqueue.php loadGoogleFonts()
Executive summary

What WordPress administrators need to know

CVE-2026-95864 lets an unauthenticated attacker turn Themify Builder's font-saving AJAX endpoint into a stored cross-site scripting weapon. The tb_generate_on_fly action, registered for logged-out users, copies the css[fonts] map from the request into the themify_builder_google_fonts option after the attacker reads a nonce printed in public page markup. Those stored strings are later interpolated into a <link href> attribute without escaping on every view of the targeted post. Themify Builder 7.8.2 sanitizes the map, validates each font at output, and escapes the href. BitFire's FREE Bot Protection blocks the scripted POST at delivery, and the FREE WAF blocks the XSS payload before it is ever stored.

At a glance

Key facts

  • Unauthenticated attack path: tb_generate_on_fly is registered via wp_ajax_nopriv_ and gated only by a nonce printed into public page markup.
  • css[fonts] and css[cf_fonts] pass through sanitize_on_the_fly_css() untouched and are stored in the themify_builder_google_fonts option.
  • Stored values render inside a double-quoted <link href> attribute with only a first-character word-character check — no output escaping.
  • Injected script executes for every visitor of the targeted published post, including administrators (CVSS scope: changed).
  • Fixed in 7.8.2: font-map sanitization at ingestion, allowlist validation and esc_url() at output.
  • BitFire FREE Bot Protection blocks the automated POST; the FREE WAF blocks the XSS payload before storage.
01
Vulnerability overview

Understand the exposure

The affected component, attack path, and practical risk for WordPress websites.

Affected componentThemify Builder
Potential reach5,000+ installations
Attack techniquestored cross-site scripting
Published2026-09-24
BitFire stops CVE-2026-95864 twice: FREE Bot Protection kills the automated AJAX delivery, and the FREE WAF blocks the XSS payload in the POST body before Themify Builder stores a single character of it.
02
Technical analysis

How the vulnerability works

Research details, affected versions, exploitation behavior, and remediation guidance.

From Public Nonce to Persistent Script Execution

Themify Builder registers tb_generate_on_fly through wp_ajax_nopriv_, so save_builder_css() in classes/class-themify-builder-stylesheet.php runs for logged-out visitors. The only gate is check_ajax_referer('tf_nonce'), and that nonce is printed into front-end HTML whenever a builder page's generated stylesheet is missing — a CSRF token, not an authentication barrier. sanitize_on_the_fly_css() whitelists breakpoint CSS but copies the attacker's fonts and cf_fonts maps through untouched. write_stylesheet() persists them in the themify_builder_google_fonts option, and loadGoogleFonts() later interpolates them into a double-quoted <link href> attribute guarded only by a first-character word check. The result is stored XSS that executes in every visitor's browser on the targeted published post — including administrators, which is what drives the changed CVSS scope.

BitFire FREE Stops the Delivery and the Payload

The attack starts as an automated, unauthenticated POST to /wp-admin/admin-ajax.php?action=tb_generate_on_fly — precisely the traffic BitFire FREE Bot Protection is built to stop. Restricted bots cannot POST data or call sensitive AJAX endpoints, and a client posing as a browser must pass JavaScript verification first, so the scripted delivery fails before WordPress loads the vulnerable handler. Allowlisted integrations bypass bot rules only — the WAF still inspects what they send. That second layer is decisive: the BitFire FREE WAF evaluates request content, and its cross-site scripting detection scans form fields and POST bodies, including the css[fonts] payload, blocking the malicious content before Themify Builder stores anything. Both controls ship in BitFire FREE for eligible non-commercial sites (commercial sites need the appropriate license), need no CVE-specific signature, and stop this exploit today.

Fixed in 7.8.2: Sanitized at Storage, Escaped at Output

Themify Builder 7.8.2 closes the chain at both ends. sanitize_on_the_fly_css() and write_stylesheet() now route every family and weight through themify_sanitize_builder_font_map(), which strips tags and control characters and rejects quotes, markup, and pipes outright — and the write-time re-check also neutralizes values already stored by 7.8.1. On the render side, loadGoogleFonts() replaces the one-character preg_match guard with the themify_is_safe_google_font_request() allowlist and wraps the link URL in esc_url(). Update immediately: running 7.8.1 leaves the injection path open on every published builder page. This is the defensive pattern BitFire enforces at the request layer even before you patch.

If Your Site Was Affected, Investigate for Persistence

Patching removes the known path; it does not undo a compromise that already happened. Injected script from CVE-2026-95864 executes in your visitors' browsers — including logged-in administrators — so treat any window on 7.8.1 as suspect: patch now, rotate administrator credentials and WordPress authentication salts, and hunt for persistence. BitFire Threat Hunter performs a thorough post-compromise investigation that surfaces backdoor WordPress administrator accounts, hidden database triggers, long-running PHP processes, and droppers capable of restoring malware or reinfecting the site. Absence of an obvious malicious file is not evidence you are clean — investigate, remove every persistence mechanism Threat Hunter finds, and re-verify.

Deploy BitFire, Patch, and Verify You Are Clean

Themify Builder trusted client input twice — at storage and at render — and version 7.8.1 paid for it with persistent script execution on public pages. BitFire breaks attacks in this class without waiting for a vendor patch: FREE Bot Protection stops the automated AJAX delivery, and the FREE WAF blocks the XSS payload in the request body before vulnerable code runs. If your site ran 7.8.1, update now, then investigate with BitFire Threat Hunter. Deploy BitFire FREE today and keep injected scripts off your pages.

03
Source review

Vulnerable and fixed code

The relevant source is located in classes/class-themify-builder-stylesheet.php — save_builder_css() / sanitize_on_the_fly_css(); render sink: themify/class-themify-enqueue.php loadGoogleFonts().

BeforeVulnerable behavior
// classes/class-themify-builder-stylesheet.php (7.8.1) — fonts copied through unsanitized
foreach (array('fonts', 'cf_fonts') as $k) {
    if (!empty($data[$k]) && is_array($data[$k])) {
        $out[$k] = $data[$k]; // attacker-controlled family/weight strings stored verbatim
    }
}

// themify/class-themify-enqueue.php (7.8.1) — first-character guard only, unescaped href
if (preg_match('/^\w/', $font)) {
    $path = '://fonts.googleapis.com/css?family=' . $fonts . '&display=swap';
    echo '<link ... id="themify-google-fonts-css" rel="stylesheet" href="' . $path_optional . '">';
}
AfterCorrected behavior
// classes/class-themify-builder-stylesheet.php (7.8.2) — font map sanitized at ingestion
foreach (array('fonts', 'cf_fonts') as $k) {
    if (!empty($data[$k]) && is_array($data[$k])) {
        $out[$k] = themify_sanitize_builder_font_map($data[$k]); // quotes, markup, pipes rejected
    }
}

// themify/class-themify-enqueue.php (7.8.2) — allowlist validation plus output escaping
if (themify_is_safe_google_font_request($font)) {
    $path = '://fonts.googleapis.com/css?family=' . $fonts . '&display=swap';
    echo '<link ... id="themify-google-fonts-css" rel="stylesheet" href="' . esc_url($path_optional) . '">';
}
04
Zero-day protection

Protection from the first exploit request

BitFire protects WordPress servers on day zero—before a vulnerability is publicly known and before other vendors have time to develop signatures or patches.

01 · VerifyStop unknown clients

Bot controls and browser verification stop untrusted automated clients before previously unknown exploit code reaches WordPress.

02 · DetectBlock malicious behavior

General WAF protections identify dangerous request behavior and hostile payloads without waiting for a vulnerability-specific signature.

03 · PreventContain attacks at runtime

RASP follows execution inside PHP and prevents unauthorized changes to protected files, accounts, and database content.

BitFire · WordPress protectionZero-day ready
BitFire zero-day WordPress vulnerability protection
BitFire combines verified-client controls, behavior-based WAF detection, and runtime RASP enforcement to protect WordPress before an exploit has a name, CVE, signature, or vendor patch.
About the author

Cory Marsh

Cory has more than 20 years of internet security experience and is a lead developer on the BitFire project.

Read BitFire security research →
Protect your WordPress website

Add protection before the next exploit arrives.

BitFire combines bot controls, request inspection, malware detection, and runtime protection in one WordPress security platform.

Protect my site free →