Follow Cyber Kendra on Google News! | WhatsApp | Telegram

Add as a preferred source on Google

Three Patches, Zero Fix: WordPress Cache Plugin's Persistent RCE Nightmare

W3 Total Cache RCE flaw bypassed in versions 2.8.13-2.8.15, putting 1M+ WordPress sites at risk despite vendor patches.

CVE-2025-9501

Security researchers have uncovered a security bug with three successive patches for same vulnerability in W3 Total Cache—one of WordPress's most popular plugins—can all be bypassed using surprisingly simple techniques. The flaw (CVE-2025-9501) threatens over one million websites with remote code execution, and the vendor's easy bypass fix it raises more concerns.

The vulnerability centers on W3 Total Cache's dynamic content processing, specifically in its _parse_dynamic_mfunc function that uses PHP's eval() to execute code embedded in cached page comments. When researcher "wcraft" first disclosed the flaw to WPScan, it affected all versions before 2.8.13 with a CVSS score of 9.0—categorized as critical.

But here's where things get messy. Security firm RCE Security decided to test the vendor's fixes and discovered what amounts to a patch security circus. Version 2.8.13's attempt to strip malicious tags failed because it used str_replace after pattern matching—meaning attackers could embed the security token within itself (like "rcercesecsec" for a token "rcesec"). The str_replace would reconstruct the valid token, making the exploit work again.

Version 2.8.14 added more checks but left the same vulnerability intact. Version 2.8.15 tried a different approach, looking for whitespace after the mfunc tag (\s+). Clever, except the original vulnerable code uses \s* (zero or more spaces). Removing the space between the tag and token—<!--mfuncrcercesecsec-->—bypasses the 2.8.15 sanitization while still triggering code execution.

"We didn't believe that it was so easy to exploit," RCE Security researchers wrote, highlighting the gap between security advisories and exploitability reality.

The exploit requires specific conditions: attackers must know the site's W3TC_DYNAMIC_SECURITY constant (a secret string administrators configure), comments must be enabled for unauthenticated users, and page caching must be active. While these requirements narrow the attack surface, they're common enough configurations to remain concerning—especially given the plugin's million-plus installation base.

WordPress administrators using W3 Total Cache should verify they're running the absolute latest version, audit their W3TC_DYNAMIC_SECURITY constant for uniqueness, review security logs for suspicious comment activity since October 2025, and consider restricting comments to authenticated users only. With proof-of-concept exploits now public and three failed patches documented, attackers have a clear roadmap for exploitation.

Post a Comment

const config = { safeID = 'safelink', safeURL: ['/p/safelink.html'], timer: 15, redirect: true, text: { wait: 'The link will appear in 0 second', direct: 'You’ll be redirected to the download link in 0 second', shifted: 'Redirecting... [link] if you’re not redirected automatically.', click: 'Click here', btn: 'Direct to link.' } }; (() => { const randomURL = url => url[Math.floor(Math.random() * url.length)]; const safeLink = () => config.safeURL.some(path => location.pathname.endsWith(path)); const safeMessage = (text, time) => { const [start, end] = text.split('0'); return `

${start} ${time} ${end}.

`; }; const outboundLinks = () => { const links = document.querySelectorAll('a.safeurl[href]'); if (!links.length) return; links.forEach(anchor => { const encoded = encodeURIComponent(btoa(anchor.href)); Object.assign(anchor, { href: `${location.origin}${randomURL(config.safeURL)}?go=${encoded}`, target: '_self', rel: 'noopener' }) }) }; const handleLink = () => { const params = new URLSearchParams(location.search); const encoded = params.get('go'); if (!encoded) return; const link = atob(decodeURIComponent(encoded)) params.delete('go'); history.replaceState({}, '', location.pathname + (params.toString() ? '?' + params : '')); let counter = config.timer; const label = config.redirect ? config.text.direct : config.text.wait; const box = document.getElementById(config.safeID); if (!box) return; box.removeAttribute('hidden'); box.innerHTML = safeMessage(label, counter); const countdown = setInterval(() => { counter--; box.innerHTML = safeMessage(label, counter); if (counter > 0) return; clearInterval(countdown); if (config.redirect) { box.innerHTML = `

${config.text.shifted.replace('[link]', `${config.text.click}`)}

`; location.href = link; } else { box.innerHTML = ''; const btn = document.createElement('a'); //btn.className = 'btn'; btn.href = link; btn.target = '_blank'; btn.rel = 'nofollow noopener noreferrer'; btn.innerHTML = `${config.text.btn}`; box.appendChild(btn); } }, 1000) }; safeLink() ? handleLink() : outboundLinks(); })();