Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : June 28, 2026

CVE-2026-54834: Object Cache 4 everyone <= 2.3.2 Information Exposure PoC, Patch Analysis & Rule

Severity Medium (CVSS 5.3)
CWE 200
Vulnerable Version 2.3.2
Patched Version 2.3.3
Disclosed June 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-54834:
Object Cache 4 everyone <= 2.3.2 contains an information exposure vulnerability that allows unauthenticated attackers to extract sensitive data from the Memcached server, including cache hit ratios, uptime, and item counts. The CVSS score is 5.3, indicating a moderate severity risk due to confidentiality impact without authentication.

Root Cause:
The vulnerability resides in the plugin's admin notification function oc4everyone_plugin_action_links() in object-cache-4-everyone.php (lines 254-267 of the vulnerable version). This function builds an HTML string containing Memcached statistics including uptime, current items, and total items, then appends it to the plugin action links displayed on the WordPress plugins page. The function only checked for nonce presence in the flush operation but exposed the statistics unconditionally to any user viewing the plugins page, including unauthenticated visitors if the plugins page was accessible. The memcached_server global variable and associated stats were used directly without proper authorization checks.

Exploitation:
An unauthenticated attacker can access the WordPress /wp-admin/plugins.php page. If the attacker has network access to any WordPress admin page, they can view the plugin row action links which contain the Memcached server address (OC4EVERYONE_PREDEFINED_SERVER) and performance statistics: cache hit ratio, uptime, current unique items, and total items. The attacker does not need any special parameters or nonces. The plugin displays this data in the admin interface, accessible to users with list_plugins capability (subscriber level or higher) but also exposed if the admin screen is publicly accessible through misconfiguration.

Patch Analysis:
The patch introduces proper input validation for the OC4EVERYONE_MEMCACHED_SERVER constant using preg_match('/^[A-Za-z0-9.-]+:[0-9]{1,5}$/', …) to ensure it's a valid host:port format. It also sanitizes output using esc_html() and int casting for all displayed values. The patch adds HMAC signing (sha256) to disk cache files via hash_hmac() and verifies the signature on read using hash_equals() to prevent tampering. Additionally, the protect_dir() method drops index.php and .htaccess in cache directories to block direct web access. The flush endpoint now requires manage_options capability as an authorization check.

Impact:
Successful exploitation reveals the Memcached server IP address and port, along with cache performance metrics. This information exposure aids attackers in identifying and targeting the caching infrastructure. The disclosed server address enables direct network attacks on the Memcached service, which typically lacks authentication and can be used for data extraction or cache poisoning. The uptime and item counts provide reconnaissance data about server activity and scale.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/object-cache-4-everyone/object-cache-4-everyone.php
+++ b/object-cache-4-everyone/object-cache-4-everyone.php
@@ -5,7 +5,7 @@
  * Description: Memcached or disk backend support for the WP Object Cache. Memcached server running and PHP Memcached class needed for better performance. No configuration needed, runs automatically
  * Plugin URI: https://wordpress.org/plugins/object-cache-4-everyone
  * Author: fpuenteonline
- * Version: 2.3.2
+ * Version: 2.3.3
  * Author URI: https://twitter.com/fpuenteonline
  * License:     GPLv2 or later
  * License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
@@ -133,7 +133,7 @@
                 '127.0.0.1:20001'
             );

-            if (defined('OC4EVERYONE_MEMCACHED_SERVER')) {
+            if (defined('OC4EVERYONE_MEMCACHED_SERVER') && is_string(OC4EVERYONE_MEMCACHED_SERVER) && preg_match('/^[A-Za-z0-9.-]+:[0-9]{1,5}$/', OC4EVERYONE_MEMCACHED_SERVER)) {
                 $memcached_servers =  array(OC4EVERYONE_MEMCACHED_SERVER);
             } else {
                 // Try SG Memcached server first.
@@ -188,8 +188,8 @@
  *
  */
 ?>" . $template;
-                $template .= '//Detected memcached server - ' . gmdate('d/m/Y G:i:s', current_time('timestamp', 0)) . PHP_EOL;
-                $template .= "define('OC4EVERYONE_PREDEFINED_SERVER', '$found_server');" . PHP_EOL;
+                $template .= '//Detected memcached server - ' . gmdate('d/m/Y G:i:s', time()) . PHP_EOL;
+                $template .= 'define('OC4EVERYONE_PREDEFINED_SERVER', ' . var_export($found_server, true) . ');' . PHP_EOL;

                 $template .= "if (! defined('WP_CACHE_KEY_SALT')) {" . PHP_EOL;
                 global $wpdb;
@@ -231,8 +231,8 @@
  */
 ?>" . $template;

-        $template .= '//No detected memcached server - ' . gmdate('d/m/Y G:i:s', current_time('timestamp', 0)) . PHP_EOL;
-        $template .= "define('OC4EVERYONE_PREDEFINED_SERVER', '');" . PHP_EOL;
+        $template .= '//No detected memcached server - ' . gmdate('d/m/Y G:i:s', time()) . PHP_EOL;
+        $template .= 'define('OC4EVERYONE_PREDEFINED_SERVER', ' . var_export('', true) . ');' . PHP_EOL;

         $template .= "if (! defined('WP_CACHE_KEY_SALT')) {" . PHP_EOL;
         $template .= "define('WP_CACHE_KEY_SALT', '" . filemtime(__FILE__) . "');" . PHP_EOL;
@@ -254,31 +254,40 @@
     global $wp_object_cache;

     if (strpos($plugin_file_name, basename(__FILE__)) && class_exists('Memcached') && method_exists($wp_object_cache, 'getStats') && file_exists(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'object-cache.php')) {
+        // Read the stats once; every value below comes from the Memcached server, so it is treated as untrusted and escaped on output.
+        $all_stats = $wp_object_cache->getStats();
+
         // Extra check.
-        if(!defined('OC4EVERYONE_PREDEFINED_SERVER') || !array_key_exists(OC4EVERYONE_PREDEFINED_SERVER, $wp_object_cache->getStats())){
-            return $links_array;
+        if (!defined('OC4EVERYONE_PREDEFINED_SERVER') || !array_key_exists(OC4EVERYONE_PREDEFINED_SERVER, $all_stats)) {
+            return $links_array;
         }

-        $hits = $wp_object_cache->getStats()[OC4EVERYONE_PREDEFINED_SERVER]['get_hits'];
+        $stats = $all_stats[OC4EVERYONE_PREDEFINED_SERVER];
+
+        $hits = isset($stats['get_hits']) ? (int) $stats['get_hits'] : 0;
         // Extra check.
-        if($hits == 0) {
-            return $links_array;
+        if ($hits === 0) {
+            return $links_array;
         }
-        $misses = $wp_object_cache->getStats()[OC4EVERYONE_PREDEFINED_SERVER]['get_misses'];
-        $total = $hits + $misses;
-        $found = @round((100 / $total) * $hits, 2);
+        $misses = isset($stats['get_misses']) ? (int) $stats['get_misses'] : 0;
+        $total  = $hits + $misses;
+        $found  = $total > 0 ? round((100 / $total) * $hits, 2) : 0;

         if (defined('WP_DEBUG') && WP_DEBUG) {
-            error_log('Object Cache 4 everyone::Memcached Server running');
+            error_log('Object Cache 4 everyone::Memcached Server running'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
         }
         $nonce = wp_create_nonce('flush_memcached_nonce');

-        $links_array[] = '<a href="' . esc_url(admin_url('admin-post.php?action=oc4flush_memcached&nonce=' . $nonce)) . '"><strong>' . esc_html__('Flush cache', 'object-cache-4-everyone') . '</strong></a>' .
+        $uptime      = isset($stats['uptime']) ? (int) $stats['uptime'] : 0;
+        $curr_items  = isset($stats['curr_items']) ? $stats['curr_items'] : 0;
+        $total_items = isset($stats['total_items']) ? $stats['total_items'] : 0;
+
+        $links_array[] = '<a href="' . esc_url(admin_url('admin-post.php?action=oc4flush_memcached&nonce=' . $nonce)) . '"><strong>' . esc_html__('Flush cache', 'object-cache-4-everyone') . '</strong></a>' .
             '<br/><br/>' .
-            esc_html__('Memcached Server running:', 'object-cache-4-everyone') . ' <strong><code style="background-color: inherit;">' . OC4EVERYONE_PREDEFINED_SERVER . '</code></strong>' . '<br/>' .
-            esc_html__('Cache Hit Ratio', 'object-cache-4-everyone') . ' <strong><code style="background-color: inherit;">' . $found . '%</code></strong>' . '<br/>' .
-            esc_html__('Uptime:', 'object-cache-4-everyone')  . ' <strong><code style="background-color: inherit;">' . secondsToHumanReadable($wp_object_cache->getStats()[OC4EVERYONE_PREDEFINED_SERVER]['uptime']) . '</strong></code>' . '<br/>' .
-            esc_html__('Current Unique Items / Total Items:', 'object-cache-4-everyone') . ' <strong><code style="background-color: inherit;">' . $wp_object_cache->getStats()[OC4EVERYONE_PREDEFINED_SERVER]['curr_items'] . ' / ' . $wp_object_cache->getStats()[OC4EVERYONE_PREDEFINED_SERVER]['total_items'] . '</strong></code>' . '<br/>';
+            esc_html__('Memcached Server running:', 'object-cache-4-everyone') . ' <strong><code style="background-color: inherit;">' . esc_html(OC4EVERYONE_PREDEFINED_SERVER) . '</code></strong>' . '<br/>' .
+            esc_html__('Cache Hit Ratio', 'object-cache-4-everyone') . ' <strong><code style="background-color: inherit;">' . esc_html($found) . '%</code></strong>' . '<br/>' .
+            esc_html__('Uptime:', 'object-cache-4-everyone')  . ' <strong><code style="background-color: inherit;">' . esc_html(secondsToHumanReadable($uptime)) . '</code></strong>' . '<br/>' .
+            esc_html__('Current Unique Items / Total Items:', 'object-cache-4-everyone') . ' <strong><code style="background-color: inherit;">' . absint($curr_items) . ' / ' . absint($total_items) . '</code></strong>' . '<br/>';
     }

     return $links_array;
@@ -288,6 +297,10 @@

 add_action('admin_post_oc4flush_memcached', 'oc4flush_memcached');
 function oc4flush_memcached() {
+    // Authorization: only administrators may flush the cache. The nonce alone is not an authorization check.
+    if (!current_user_can('manage_options')) {
+        wp_die(esc_html__('Access denied.', 'object-cache-4-everyone'), '', array('response' => 403));
+    }
     if (!class_exists('Memcached')) {
         wp_die(esc_html__('Failed to flush Memcached server', 'object-cache-4-everyone'));
     }
@@ -295,7 +308,6 @@
     if (isset($_GET['nonce']) && wp_verify_nonce(sanitize_key($_GET['nonce']), 'flush_memcached_nonce')) {
         // Flush cache.
         global $wp_object_cache;
-        error_log(print_r($wp_object_cache->getStats()[OC4EVERYONE_PREDEFINED_SERVER], true));
         $wp_object_cache->flush();

         $memcached = new Memcached();
@@ -304,7 +316,7 @@

         if ($memcached->flush(0)) {
             if (defined('WP_DEBUG') && WP_DEBUG) {
-                error_log('Object Cache 4 everyone::Memcached server flushed.');
+                error_log('Object Cache 4 everyone::Memcached server flushed.'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
             }
             wp_redirect(admin_url('plugins.php'));
             exit;
--- a/object-cache-4-everyone/object-cache-disk-template.php
+++ b/object-cache-4-everyone/object-cache-disk-template.php
@@ -33,9 +33,49 @@
                 return;
             }
         }
+
+        // Block direct web access and directory listing of the cache tree.
+        $this->protect_dir($this->local_path);
+
         $this->result_code = self::RES_SUCCESS;
     }

+    /**
+     * Drops an index.php and a deny-all .htaccess in a cache directory so the
+     * serialized cache files cannot be listed or served over the web.
+     *
+     * @param string $dir Directory to protect (with trailing separator).
+     */
+    private function protect_dir($dir)
+    {
+        global $wp_filesystem;
+
+        if (!$wp_filesystem->exists($dir . 'index.php')) {
+            $wp_filesystem->put_contents($dir . 'index.php', '<?php // Silence is golden.');
+        }
+
+        if (!$wp_filesystem->exists($dir . '.htaccess')) {
+            $htaccess = "<IfModule mod_authz_core.c>nRequire all deniedn</IfModule>n<IfModule !mod_authz_core.c>nOrder allow,denynDeny from alln</IfModule>n";
+            $wp_filesystem->put_contents($dir . '.htaccess', $htaccess);
+        }
+    }
+
+    /**
+     * Returns the secret used to sign cache files on disk.
+     *
+     * @return string
+     */
+    private function hmac_key()
+    {
+        if (defined('AUTH_SALT') && AUTH_SALT) {
+            return AUTH_SALT;
+        }
+        if (defined('SECURE_AUTH_SALT') && SECURE_AUTH_SALT) {
+            return SECURE_AUTH_SALT;
+        }
+        return 'oc4everyone-disk-cache';
+    }
+
     public function quit()
     {
         return false;
@@ -125,7 +165,11 @@
             $value = clone $value;
         }

-        $return = $wp_filesystem->put_contents($path, @serialize($value));
+        // Sign the serialized payload so a tampered cache file is rejected before unserialize().
+        $serialized = @serialize($value);
+        $blob       = hash_hmac('sha256', $serialized, $this->hmac_key()) . $serialized;
+
+        $return = $wp_filesystem->put_contents($path, $blob);
         if (!$return) {
             $this->result_code = self::RES_FAILURE;
             return false;
@@ -146,12 +190,22 @@
         }

         $objData = $wp_filesystem->get_contents($path);
-        if ($objData === false) {
+        if ($objData === false || strlen($objData) < 64) {
             $this->result_code = self::RES_FAILURE;
             return false;
         }

-        $data = unserialize($objData);
+        // Verify the HMAC signature before unserializing. A tampered or unsigned
+        // file fails the constant-time comparison and is treated as a cache miss,
+        // so attacker-controlled bytes never reach unserialize().
+        $stored_hmac = substr($objData, 0, 64);
+        $serialized  = substr($objData, 64);
+        if (!hash_equals(hash_hmac('sha256', $serialized, $this->hmac_key()), $stored_hmac)) {
+            $this->result_code = self::RES_FAILURE;
+            return false;
+        }
+
+        $data = unserialize($serialized);

         $this->result_code = self::RES_SUCCESS;
         return $data;
@@ -164,13 +218,9 @@
         $array_hash = str_split($hash, 8); //8 name based

         $path = $this->local_path . implode(DIRECTORY_SEPARATOR, $array_hash);
-
-        // Ensure directory exists with an index.php to prevent directory listing
-        if (!file_exists($path . DIRECTORY_SEPARATOR . 'index.php')) {
-            @file_put_contents($path . DIRECTORY_SEPARATOR . 'index.php', '<?php // Silence is golden.');
-        }

-        $path .= DIRECTORY_SEPARATOR . '.object.php';
+        // Cache payload stored with a non-PHP extension so it is never executed even if served directly.
+        $path .= DIRECTORY_SEPARATOR . '.object.cache';
         return $path;
     }
 }

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School