Security

CVE-2026-111497: The Brotli Bug One xmlrpc.php Pingback Can Reach, and Why Turning Off XML-RPC the Usual Way Does Not Close It

CVE-2026-111497 is a 9.2 Brotli decoder bug in CISA's KEV catalog. One unauthenticated pingback can reach it, and the common xmlrpc_enabled filter does not close that route. What to check, block and patch.

Varun DubeyVarun Dubey
·14 min read
Terminal-style card: xmlrpc_enabled false still lets pingback.ping reach the Brotli decoder in CVE-2026-111497, fixed in libbrotli 1.0.10, 1.1.1 or 1.2.1

CVE-2026-111497 is a memory-corruption bug in Google’s Brotli decompression library, and a stock WordPress site can reach it through one unauthenticated pingback request to xmlrpc.php. Disabling XML-RPC with the popular xmlrpc_enabled filter does not close that route, because pingback never asks for a login. The real fix is a patched Brotli package on the server. Until you have it, unset the pingback methods, block xmlrpc.php at the web server, and stop libcurl from decoding responses at all. Patchstack’s write-up of 30 October 2026 is the source for the WordPress-specific path, and the CVE, NVD and CISA records confirm the rest.

This is a server-side problem with a WordPress-shaped door. Nothing in WordPress core, and no plugin, ships the vulnerable code. That is why this one slips past the usual “update everything” reflex, and why it deserves its own checklist. Related reading on this site: the WordPress 7.1.4 security release and how to prove your site really updated covers a separate set of fixes. Updating to 7.1.4 does not touch this library.

What is CVE-2026-111497?

It is an out-of-bounds write in the Brotli decoder, the code that unpacks data compressed with the Brotli algorithm. The CVE record names Google as the assigning organisation. The NVD entry describes a buffer overflow in c/dec/decode.c in Google Brotli versions 1.0.3 through 1.2.0, on all platforms. A remote, unauthenticated attacker who can feed the decoder a crafted compressed stream can corrupt adjacent decoder state memory, disclose memory contents, crash the process, or potentially run code.

Fact

What the primary record says

Where to check

Affected versions

Brotli 1.0.3 through 1.2.0

NVD entry for CVE-2026-111497

Fixed versions

1.0.10, 1.1.1 and 1.2.1

NVD description and the google/brotli v1.2.1 release

Severity

CVSS v4.0 base score 9.2, Critical, network, no privileges, no user interaction

NVD metrics, supplied by the CNA

Weakness types

CWE-787 (out-of-bounds write) and CWE-252 (unchecked return value)

NVD weakness list

CISA KEV

Added 27 October 2026, due date 10 November 2026, ransomware use listed as “Unknown”

CISA Known Exploited Vulnerabilities JSON feed

CVE record published

22 October 2026

cve.org record

One detail worth reading twice: CISA’s catalog names the entry “Google Brotli Unchecked Return Value Vulnerability” and lists it against the product “Brotli” from vendor “Google”. It says nothing about WordPress. The KEV entry confirms the library bug is known to be exploited somewhere. It does not say WordPress sites are being hit through pingback, and nobody should write it that way. Patchstack makes the same careful split: the denial of service on the WordPress path is easy to reproduce, and code execution there is not demonstrated.

Is CVE-2026-111497 really in the CISA KEV catalog?

Yes. We checked the catalog feed directly. The entry carries a date added of 27 October 2026 and a required-action due date of 10 November 2026. The “known ransomware campaign use” field reads “Unknown”. The notes field points to the Brotli v1.2.1 release as the vendor reference.

Two things follow from that. First, the due date binds US federal agencies, not you. Treat it as a signal of urgency rather than a deadline you owe anyone. Second, KEV inclusion is about the underlying library. The catalog says this affects “an open-source component, third-party library, protocol, or proprietary implementation that could be used by different products”. For a WordPress owner the practical reading is simple: the library is on a list of bugs with real-world exploitation, and your server probably has a copy of it.

How does one xmlrpc.php pingback reach the Brotli decoder?

The route has five steps, and the dangerous one happens after the request has already been accepted.

  1. An attacker sends an unauthenticated POST to /xmlrpc.php calling pingback.ping. The first parameter is a “source” URL on a server the attacker controls. The second is a post on your site.
  2. WordPress runs its SSRF check on that source URL. A public HTTPS address on port 443 passes. The check is there to block internal addresses, not to judge what the remote server will send back.
  3. WordPress fetches the URL with wp_safe_remote_get(). Every outbound request WordPress makes goes through its HTTP layer and the bundled Requests library with the cURL transport.
  4. The attacker’s server replies with Content-Encoding: br and a malformed Brotli stream.
  5. libcurl decodes the body inside curl_exec(), before PHP sees a byte. On the vulnerable library versions, the decoder writes past the end of its buffer.

Patchstack’s key observation is about step 3 and 5. The Requests cURL transport sets CURLOPT_ENCODING to an empty string on every handle. In libcurl, an empty value means “accept any compression you support and unpack it for me”. If the operating system’s libcurl was built with Brotli, a reply marked br goes straight into the system Brotli library. Setting decompress => false in a WordPress request does not help, because that flag only controls what WordPress does with a body libcurl has already unpacked.

WordPress does not ship Brotli. It uses whatever your operating system provides through libcurl. That is the whole reason there is nothing to update in WordPress.

Why does turning off XML-RPC the usual way not close it?

Because the usual way only switches off the methods that need a login. Most “disable XML-RPC” snippets and plugin toggles use this one line:

add_filter( 'xmlrpc_enabled', '__return_false' );

That filter gates the XML-RPC methods that authenticate a user. pingback.ping is registered in the default method map and needs no account, so it never reaches the check. Patchstack states plainly that a site running only this filter answers a pingback exactly the way an untouched site does. If your hardening checklist says “XML-RPC disabled” and the evidence is this filter, you have closed the login-based methods, which is still useful against brute force, and left the pingback method open.

“XML-RPC is disabled” can mean three different things on a real server:

What was done

Pingback route closed?

Notes

xmlrpc_enabled returns false (plugin toggle or snippet)

No

Only login-based methods are gated

pingback.ping removed with the xmlrpc_methods filter

Yes

Closes the method for this path only

xmlrpc.php denied at Apache or nginx

Yes

Request dropped before PHP starts

Host or CDN blocks the file at the edge

Usually yes

Test it, do not assume it

None of these closes the other routes into the decoder, which we cover below. And none of them patches the bug.

How do I test whether pingback is open on my site?

Patchstack published a safe probe. The source address is a loopback address that WordPress rejects before it fetches anything, so you can point it at your own site without sending a request anywhere. Replace example.com with your domain.

curl -s -i -X POST https://example.com/xmlrpc.php \
  -H 'Content-Type: text/xml' \
  --data '<?xml version="1.0"?>
<methodCall>
  <methodName>pingback.ping</methodName>
  <params>
    <param><value><string>http://127.0.0.1/</string></value></param>
    <param><value><string>https://example.com/?p=1</string></value></param>
  </params>
</methodCall>'

Read the result like this, following Patchstack’s guidance:

  • A 403, a 404, or anything that is not XML means the file is blocked. The pingback path does not apply.
  • A fault that says requested method pingback.ping does not exist means the method was unregistered. That also closes it.
  • Any other methodResponse, even a fault, means pingback_ping() ran and the route is open.

Run it once from outside your network, not from the server itself. A host-level firewall rule can behave differently for requests that originate on the machine.

How do I check whether my server has the vulnerable Brotli library?

The version string alone can mislead you. Debian and Ubuntu backport fixes into their existing package instead of moving to the upstream number. Patchstack gives this example for Debian 13: a vulnerable package reads 1.1.0-2+b7 and a patched one reads 1.1.0-2+deb13u1. A fully patched box still reports Brotli 1.1.0 and still has a file named libbrotlidec.so.1.1.0 on disk. So check the package revision against your distribution’s security notice, not the upstream version.

# Debian and Ubuntu: package revision
dpkg-query -W -f='${Version}\n' libbrotli1

# Is the library loaded by the PHP process that serves your site?
grep -i brotli /proc/$(pgrep -o php-fpm)/maps | head

# Does the curl binary on this box have Brotli support?
curl --version | head -n 2

The second command shows whether a running PHP worker has the decoder mapped into memory. Adjust the process name for your stack (php-fpm8.3, apache2, httpd). The third command inspects the command-line curl, which is a different build from the libcurl that PHP uses on some servers, so treat it as a hint and not as proof. On RPM-based systems, read your vendor’s advisory for the fixed package revision. We have not confirmed specific fixed revisions for every distribution, and you should not trust a number from a blog post, this one included.

What about Docker and the official WordPress image?

Patchstack reported on 30 October that wordpress:latest still installed a vulnerable libbrotli1, so pulling the image again would not help until the base image was rebuilt. Their stopgap is to upgrade the package in your own layer:

FROM wordpress:latest
RUN apt-get update \
 && apt-get install -y --no-install-recommends --only-upgrade libbrotli1 \
 && rm -rf /var/lib/apt/lists/*

That statement was true when Patchstack published it. Check the image yourself before relying on it, because base images get rebuilt.

What should I do first if I cannot patch the server today?

Work in this order. None of the first three fixes the bug. They cut off the ways to reach it while you arrange the real update.

1. Stop libcurl from decoding responses

This is the broadest option, and it covers pingback, oEmbed, feed fetching and plugin fetches in one go. Put it in a must-use plugin at wp-content/mu-plugins/no-curl-decode.php. The code is Patchstack’s published mitigation.

<?php
/**
 * Plugin Name: Disable libcurl response decoding
 * Description: Stops the system Brotli decoder from receiving attacker-controlled bodies.
 */
add_action( 'http_api_curl', function ( $handle ) {
	curl_setopt( $handle, CURLOPT_ENCODING, 'identity' );
	curl_setopt( $handle, CURLOPT_HTTP_CONTENT_DECODING, 0 );
}, 10, 1 );

The cost is small. WordPress stops asking for compressed responses on its own outbound requests. It also fails softly: if a server sends a compressed body anyway, WordPress still unpacks gzip itself in PHP, well away from the vulnerable code. A mu-plugin loads before regular plugins and cannot be deactivated from the dashboard, which is the right property for a security stopgap.

To confirm it took effect, make WordPress request a page that echoes request headers and look at Accept-Encoding. Before the filter it should list the encodings libcurl supports. After it should say identity.

wp eval 'echo wp_remote_retrieve_body( wp_remote_get( "https://httpbin.org/headers" ) );'

2. Unset the pingback methods

This removes the unauthenticated path. Unsetting the method is better than matching the method name in a firewall rule. WordPress decodes XML character references and CDATA before it dispatches the call, so an attacker can spell pingback.ping in ways a plain string match will not catch.

add_filter( 'xmlrpc_methods', function ( $methods ) {
	unset( $methods['pingback.ping'] );
	unset( $methods['pingback.extensions.getPingbacks'] );
	return $methods;
} );

3. Block xmlrpc.php at the web server

This drops the request before PHP starts. Apache 2.4:

<Files xmlrpc.php>
    Require all denied
</Files>

And nginx:

location = /xmlrpc.php { deny all; }

Before you block the file, check whether anything you run depends on it. The WordPress mobile app, Jetpack and some publishing tools use XML-RPC. If you need it for those, use step 2 instead and keep the file reachable. We cover the wider hardening angle in how to disable XML-RPC and block REST API user enumeration, which is still sound advice for the login-based methods.

4. Install the patched Brotli package

This is the only change that reaches the vulnerable code. Take your distribution’s security update for libbrotli1 (or the equivalent package name), then restart PHP-FPM or Apache so running workers drop the old library from memory. A package upgrade does not fix a process that already has the old file mapped. Managed hosting customers should ask the host in writing which package revision they run and when it was patched. Our guide to testing your host’s security claims has questions that work for this.

Which other WordPress paths reach the same decoder?

Pingback is the easiest door, and it is not the only one. Because the flaw sits in the transport layer, any server-side fetch of an address someone else chose lands in the same place. Patchstack lists four routes:

Route into the decoder

What the attacker needs

Closed by xmlrpc block?

pingback.ping over xmlrpc.php

Nothing, plus a pingable published post (a default install has one)

Yes

A plugin or theme that calls wp_remote_get() or wp_safe_remote_get() on a user-supplied address

Whatever that endpoint demands, often nothing

No

oEmbed discovery on save, or the oEmbed proxy endpoint

A Contributor account or higher

No

Feed fetching through SimplePie

An administrator-configured feed URL

No

The plugin route is the one most people forget. Link previews, image importers, webhook verifiers and avatar proxies all fetch a URL on behalf of a visitor. The “safe” wrapper does not protect them. All wp_safe_remote_get() adds over wp_remote_get() is reject_unsafe_urls, an SSRF filter that rejects internal IP addresses and any port other than 80, 443 and 8080. It never looks at Content-Encoding, so an attacker’s server on port 443 walks straight through.

To find candidates in your own code, search for the functions and read what feeds them:

cd wp-content/plugins
grep -rn "wp_remote_get\|wp_safe_remote_get\|wp_remote_post" --include=*.php . \
  | grep -i "\$_\(GET\|POST\|REQUEST\)\|\$url\|\$link" | head -50

That search is noisy by design. You are looking for a URL that came from a request, a form field, or a post meta value a visitor can set. A plugin that only calls its own vendor’s API with a fixed address is not exposed in this way.

Why can a firewall not see this attack coming?

A web application firewall decides from what the visitor sent. In this attack the visitor sends a pingback with a URL, which looks like any other pingback. The harmful part arrives later, in the reply from the attacker’s own server, on an outbound connection the firewall usually does not inspect. Patchstack walks through it step by step: the inbound request is allowed by design, the SSRF check passes, the fetch is outbound, the poisoned reply is “too late” for any inbound rule, and the decode happens below PHP, so even a PHP-level security plugin has nothing left to intercept.

The only step a rule can touch is the first one, and there the attack is indistinguishable from a real pingback. So a rule has to remove the method entirely rather than match the attack. That is worth doing, and it is what steps 2 and 3 above do. It does not reach the bug.

The practical lever is egress control. If your server can only reach hosts you know, a pingback pointed at an attacker’s domain fails at the network edge. Pushing WordPress outbound HTTP through a forward proxy with an allow-list is a larger project, but it narrows every route in the table above at once. Define WP_HTTP_BLOCK_EXTERNAL and WP_ACCESSIBLE_HOSTS in wp-config.php if your site makes only a handful of known outbound calls:

define( 'WP_HTTP_BLOCK_EXTERNAL', true );
define( 'WP_ACCESSIBLE_HOSTS', 'api.wordpress.org,*.wordpress.org,your-payment-gateway.example' );

Test this on a staging copy first. Plugin licence checks, update checks, payment gateways and mail APIs all make outbound calls, and a too-tight list breaks them quietly.

How bad is it? Crash, memory leak or remote code execution?

Here the sources need to be separated carefully, because headlines have run ahead of the evidence.

What is established for WordPress. A malformed Brotli stream makes the decoder write past its buffer. Crashing a PHP worker is easy and works on any vulnerable build. Patchstack points to a public lab published on 27 October that drives the bug through pingback and documents two outcomes: a worker killed outright, and a worker pinned at 100 percent CPU by a payload under a hundred bytes. The lab’s author states that no code execution is included and that blind remote code execution is not established. A few hundred requests can keep a small site down, because each one takes a worker with it.

What is harder. Memory disclosure and code execution are what the 9.2 score accounts for. Researchers have working shells against stock Node.js and Bun binaries, and the NVD references include a Node.js security release page for October 2026. Patchstack says those chains depend on a fixed offset in one specific non-PIE build and on guessing a socket number, and that they fail on other targets. PHP behind Apache or FPM is not that target, and Patchstack says no public chain aims at it yet.

What not to conclude. “Hard” is not “impossible”. Patchstack makes the point that offsets have to be worked out once per build, not once per victim, and whoever does that work is not obliged to publish it. Plan around crashed workers today, and treat code execution as a real but unproven risk that a patch removes. Search results also show some public proof-of-concept repositories claiming code execution against one pinned Docker image digest. We did not test those, we do not link to them, and a lab that targets one exact build says little about your server.

How can I tell if someone has already tried it on my site?

Outbound traffic is where this shows up best. A WordPress install receiving a reply with Content-Encoding: br is already unusual, because Brotli is a browser encoding and WordPress’s own requests do not normally get it. A tiny compressed body that balloons when unpacked, or a chunked reply that stalls halfway, is stranger still. A logging forward proxy will show all of it.

At the edge, look for unauthenticated POST /xmlrpc.php requests carrying pingback.ping whose source URL points somewhere your site has no reason to contact, particularly in bursts or toward recently registered domains.

# Pingback attempts in the access log (adjust the path)
grep "POST /xmlrpc.php" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# Worker crashes: Apache and PHP-FPM
grep -i "exit signal Segmentation fault" /var/log/apache2/error.log | tail -20
grep -i "exited on signal 11" /var/log/php*-fpm.log | tail -20

Apache logs AH00051: child pid N exit signal Segmentation fault (11) and PHP-FPM logs a child exiting on signal 11. A core dump with libbrotlidec.so in the backtrace settles it. Not every hit leaves a crash: the public lab also produces a worker stuck at full CPU with no crash at all, so pair the segfault check with a look at pinned workers and requests that time out.

If you suspect more than a knock-down, the tell is the web server user spawning a shell or curl: a www-data process whose parent is PHP or Apache, talking to a host you do not recognise. Our hidden admin account audit is the right follow-up if you find anything.

How should an agency roll this out across many sites?

Treat it as an infrastructure ticket, not a plugin update. Group your sites by host and by operating system image, because the fix is per server, not per site.

  1. Inventory. For every server, record the OS release, the libbrotli package revision, and whether PHP runs under FPM or Apache.
  2. Probe. Run the pingback test against every domain. Record open or closed.
  3. Mitigate in bulk. Deploy the mu-plugin from step 1 to every site. It is one file and safe to ship fleet-wide.
  4. Patch. Update the package per server, restart PHP, and re-run the probe and the loaded-library check.
  5. Record the evidence. A screenshot of the package revision and a dated probe result is what you send a client who asks, “Are we safe?”
# Fleet probe sketch: one domain per line in domains.txt
while read d; do
  code=$(curl -s -o /tmp/xr.out -w '%{http_code}' -X POST "https://$d/xmlrpc.php" \
    -H 'Content-Type: text/xml' \
    --data '<?xml version="1.0"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>http://127.0.0.1/</string></value></param><param><value><string>https://'"$d"'/?p=1</string></value></param></params></methodCall>')
  if grep -q "does not exist" /tmp/xr.out; then s=method-removed
  elif grep -q "methodResponse" /tmp/xr.out; then s=OPEN
  else s=blocked-$code; fi
  echo "$d $s"
done < domains.txt

Anything that prints OPEN goes to the top of the queue. Anything that prints blocked still needs the library patch, because the plugin, oEmbed and feed routes remain.

Does this affect only WordPress?

No, and that is part of the reason the CVE moved fast. The NVD references include advisories from Node.js, Debian LTS, Siemens, the .NET project and the Go Brotli port, among others. Anything on your server that decodes Brotli from a source you do not control can be a route: a Node service behind your site, a reverse proxy, a headless front end, a build tool that fetches remote assets. If you run a headless WordPress setup with a Node or Bun front end, check that side with the same urgency. Whether a given product is affected depends on whether it bundles its own Brotli or uses the system one, so read each vendor’s advisory.

Frequently asked questions

Does updating WordPress fix CVE-2026-111497?

No. WordPress does not ship Brotli, so no core or plugin release contains the fix. The patch is the operating system’s Brotli package. Updating WordPress to the latest security release is still right for the other fixes in it.

Is a security plugin enough to block it?

Not for the library flaw. The malicious data arrives in the reply to an outbound request, and the decode happens in libcurl below PHP. A plugin can unset the pingback method, which closes one route, but it cannot reach the decoder.

Do I need to disable XML-RPC completely?

Only if nothing you use depends on it. Unsetting pingback.ping and pingback.extensions.getPingbacks with the xmlrpc_methods filter keeps the file available for apps that need it while removing the unauthenticated method.

Why does my host say we are not affected?

Some hosts block xmlrpc.php at the edge, and some have already patched the package. Ask which. A block closes only the pingback route, and a patch closes the bug. Run the probe and ask for the package revision instead of accepting a general statement.

Is my site being exploited right now?

We cannot tell from outside. CISA lists the library bug as exploited in the wild, but the catalog entry does not name WordPress as a target. The reproduced WordPress outcome is a crashed or stalled worker. Check your logs using the commands above.

Which Brotli versions are safe?

The upstream fixes are 1.0.10, 1.1.1 and 1.2.1. Distribution packages may keep an older version number with the fix backported, so compare the package revision with your distribution’s security notice.

The short version

CVE-2026-111497 is a Brotli decoder bug, scored 9.2, listed in CISA’s KEV catalog since 27 October 2026. WordPress reaches it through libcurl on any outbound fetch, and the easiest trigger is an unauthenticated pingback. The common xmlrpc_enabled filter does not stop that. Patch the library, restart PHP, probe each site with the safe test, and until then deploy the libcurl mu-plugin, unset the pingback methods and block xmlrpc.php where nothing needs it. Primary sources used: the NVD and cve.org records, the CISA KEV feed, and Patchstack’s article “WordPress doesn’t ship Brotli. It’s still exposed to CVE-2026-111497” of 30 October 2026.

Part of the Wbcom Designs family

The all-in-one WordPress community stack

Also ours: wbcomdesigns.comvapvarun.combrndle.com