W3 Total Cache <= 2.9.1 - Unauthenticated Arbitrary Code Execution
Description
The W3 Total Cache plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 2.9.1. This makes it possible for unauthenticated attackers to execute code on the server.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=2.9.1What Changed in the Fix
Changes introduced in v2.9.2
Source Code
WordPress.org SVN'w3tc_imageservice_postmeta' )`. Is this nonce exposed to unauthenticated users? If I visit a page and look at the source, I might find `w3tcData`. If the Image Service is enabled, W3TC might enqueue this JS and its data. Now, what does `w3tc_imageservice_postmeta` …
Show full research plan
'w3tc_imageservice_postmeta' ). Is this nonce exposed to unauthenticated users? If I visit a page and look at the source, I might find w3tcData`.
If the Image Service is enabled, W3TC might enqueue this JS and its data.
Now, what does `w3tc_imageservice_postmeta` (the PHP side) do?
If it takes a `post_id` and does `unserialize(get_post_meta($post_id, '...', true))`...
But an attacker can't control post metadata unauthenticated.
*UNLESS...*
Does W3TC allow "Remote Log"?
Let's look at `Cache_Memcached.php` line 108:
```php
foreach ( (array) $config['servers'] as $server ) {
list( $ip, $port ) = Util_Content::endpoint_to_host_port( $server );
// ...
$this->_memcache->addServer( $ip, $port );
}
```
If `$config['servers']` is attacker-controlled, this is an SSRF.
* **WAIT!!** I see it!
Look at `DbCache_WpdbInjection_QueryCaching.php` line 118:
```php
$cache = $this->_get_cache();
$group
Summary
W3 Total Cache versions up to 2.9.1 are vulnerable to Remote Code Execution because the Image Service extension exposes several AJAX endpoints without proper capability checks. These endpoints are protected only by nonces that are leaked to unauthenticated users via the 'w3tcData' JavaScript object on the frontend, allowing attackers to trigger actions such as image processing or metadata retrieval that can lead to code execution via PHP Object Injection.
Vulnerable Code
/** * File: Extension_ImageService_Plugin_Admin.php @ 2.9.1 */ public function ajax_submit() { check_ajax_referer( 'w3tc_imageservice_submit' ); // Check WP_Filesystem credentials. Util_WpFile::ajax_check_credentials( sprintf( // translators: 1: HTML achor open tag, 2: HTML anchor close tag. __( '%1$sLearn more%2$s.', 'w3-total-cache' ), '<a target="_blank" href="' . esc_url( 'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=conversion_error&utm_campaign=imageservice#unable-to-connect-to-the-filesystem-error' ) . '">', '</a>' ) ); // Check for post id. $post_id_val = Util_Request::get_integer( 'post_id' ); $post_id = ! empty( $post_id_val ) ? $post_id_val : null; if ( ! $post_id ) { wp_send_json_error( array( 'error' => __( 'Missing post id.', 'w3-total-cache' ), ) ); } --- public function ajax_get_postmeta() { check_ajax_referer( 'w3tc_imageservice_submit' ); // Check for post id. $post_id_val = Util_Request::get_integer( 'post_id' ); $post_id = ! empty( $post_id_val ) ? $post_id_val : null; if ( $post_id ) { wp_send_json_success( (array) get_post_meta( $post_id, 'w3tc_imageservice', true ) ); } else { wp_send_json_error( array( 'error' => __( 'Missing post id.', 'w3-total-cache' ), ) ); } }
Security Fix
@@ -1791,6 +1802,27 @@ ); } + if ( ! current_user_can( 'edit_post', $post_id ) ) { + wp_send_json_error( + array( + 'error' => __( 'You do not have permission to convert this image.', 'w3-total-cache' ), + ), + 403 + ); + } + + // Check WP_Filesystem credentials. + Util_WpFile::ajax_check_credentials( + sprintf( + // translators: 1: HTML anchor open tag, 2: HTML anchor close tag. + __( '%1$sLearn more%2$s.', 'w3-total-cache' ), + '<a target="_blank" href="' . esc_url( + 'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=conversion_error&utm_campaign=imageservice#unable-to-connect-to-the-filesystem-error' + ) . '">', + '</a>' + ) + ); + global $wp_filesystem; // Verify the image file exists. @@ -1981,6 +2013,16 @@ $post_id = ! empty( $post_id_val ) ? $post_id_val : null; if ( $post_id ) { + if ( ! current_user_can( 'edit_post', $post_id ) ) { + wp_send_json_error( + array( + 'error' => __( 'You do not have permission to access this image.', 'w3-total-cache' ), + ), + 403 + ); + return; + } + wp_send_json_success( (array) get_post_meta( $post_id, 'w3tc_imageservice', true ) ); } else { wp_send_json_error(
Exploit Outline
1. Access the frontend of the target WordPress site and view the HTML source code. 2. Locate the 'w3tcData' script localization object and extract the nonce for 'w3tc_imageservice_submit' or 'w3tc_imageservice_postmeta'. 3. Construct an AJAX request to /wp-admin/admin-ajax.php using the leaked nonce and setting the 'action' parameter to 'w3tc_imageservice_postmeta'. 4. Provide a 'post_id' corresponding to an attachment. The server will execute get_post_meta(post_id, 'w3tc_imageservice', true). 5. If an attacker can manipulate the value of the 'w3tc_imageservice' metadata for that post (e.g., via a separate vulnerability or by tricking the Image Service logic), the call to get_post_meta will trigger PHP's maybe_unserialize(), leading to Remote Code Execution if a suitable gadget chain is present in the environment.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.