SECURITY ADVISORY / 01

CVE-2025-13377 Exploit & Vulnerability Analysis

Complete CVE-2025-13377 security advisory with proof of concept (PoC), exploit details, and patch analysis for tenweb-speed-optimizer.

tenweb-speed-optimizer products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An authenticated WordPress user with Subscriber role or higher can delete arbitrary directories on the server by calling the cache-clearing endpoint with a crafted permalink parameter containing directory traversal sequences.

GET /wp-admin/admin-ajax.php?action=two_clear_page_cache&permalink=../../&_wpnonce=<valid_nonce> HTTP/1.1
Host: target.example.com
Cookie: wordpress_logged_in=<subscriber_session>

The attacker observes a successful cache deletion response (HTTP 200) or silence. Within seconds, the server's non-cache directories begin vanishing — WordPress configuration files, plugin directories, or application data outside wp-content/cache cease to exist. A recursive deletion operation consumes the filesystem traversal, and by the time logs surface anomalies, critical application state is gone.

What the Patch Did

Before:

public static function delete_page_cache($dir, $is_home_url)
{
    if (is_dir($dir)) {
        foreach (scandir($dir) as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            $path = $dir . $file;

            if (is_file($path)) {
                unlink($path); // phpcs:ignore
            } else {

After:

public static function delete_page_cache($dir, $is_home_url)
{
    // Validate directory path before deletion
    if (empty($dir)) {
        return;
    }

    // Ensure the directory is within the allowed cache directory
    $real_dir = realpath($dir);
    $real_allowed_dir = realpath(TENWEB_SO_PAGE_CACHE_DIR);

    if ($real_dir === false || $real_allowed_dir === false) {
        return;
    }

    // Prevent directory traversal - ensure path is within cache directory
    if (strpos($real_dir, $real_allowed_dir) !== 0) {
        return;
    }

    if (is_dir($dir)) {
        foreach (scandir($dir) as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }

            $path = $dir . $file;

            // Additional safety check for each path
            $real_path = realpath($path);

            if ($real_path === false || strpos($real_path, $real_allowed_dir) !== 0) {
                continue;
            }

            if (is_file($path)) {
                unlink($path); // phpcs:ignore

The patch introduces realpath() normalization and prefix validation using strpos(). The load-bearing security control is the comparison strpos($real_dir, $real_allowed_dir) !== 0 — this enforces that the resolved canonical path begins with the allowed cache directory constant TENWEB_SO_PAGE_CACHE_DIR. Without this check, ../../sensitive_directory resolves to an absolute path outside the cache root, and deletion proceeds unrestricted.

The patch also hardens the AJAX handler in OptimizerWebPageCacheWP.php by adding explicit capability checks (current_user_can('manage_options')), correct nonce verification with proper parameter passing to wp_verify_nonce(), and a new validate_cache_path() function that re-enforces the prefix check before any deletion is triggered.

Root Cause

CWE-22 (Path Traversal) and CWE-20 (Improper Input Validation) converge here. The attacker controls the permalink GET parameter in the AJAX request. This value flows into OptimizerWebPageCache::delete_cache_by_url($permalink), which calls get_cache_dir_for_page_from_url($url) without validating that the returned $dir stays within the cache directory. The function wp_parse_url() extracts the host and path from the URL, but hostile paths like ../../wp-config.php are never checked for directory traversal patterns. The $dir value lands directly in delete_page_cache($dir, ...), where scandir() and unlink() operate on it recursively without boundary enforcement.

The missing trust boundary is between user input ($_GET['permalink']) and the filesystem operation (unlink($path)). Early versions of the code assumed that URL parsing would implicitly constrain the output, but URL parsing only extracts components — it does not reject ../ sequences.

Why It Works

The line if (strpos($real_dir, $real_allowed_dir) !== 0) { return; } is load-bearing. If removed, an attacker's ../../ path still resolves via realpath() to an absolute path (e.g., /var/www/html), and without the prefix check, that path is accepted and deleted. The surrounding lines exist for defense-in-depth: realpath() resolves symlinks and normalizes . and .. sequences into their true targets, preventing obfuscation tricks. The === false guards on realpath() catch non-existent or inaccessible paths early, reducing the surface for edge cases. The per-file validation strpos($real_path, $real_allowed_dir) !== 0 inside the loop catches any files an attacker might have created outside the cache directory via symlinks or race conditions. Together, these layers ensure that no deletion escapes the intended root.

Hardening Checklist

  • Use realpath() + prefix matching for all path-based operations. Normalize any user-influenced filesystem path with realpath(), then enforce that it begins with an allowed root directory using strpos() or str_starts_with() (PHP 8+). Apply this rule at every recursion level or function boundary that touches the filesystem.

  • Verify capabilities before AJAX actions that modify state. Call current_user_can('manage_options') or a custom capability early in any AJAX handler that deletes, creates, or modifies files. Do not rely on role names; use current_user_can() with explicit capability checks.

  • Validate nonce parameters correctly. Extract the nonce from $_GET, $_POST, or $_REQUEST with sanitize_text_field(), then pass it to wp_verify_nonce($nonce, 'action_name') and check the return value strictly (if (!wp_verify_nonce(...)) { wp_die(...); }). The old pattern !== null is insufficient and masks failures.

  • Reject dangerous URL patterns before passing to filesystem logic. Before a user-supplied URL reaches get_cache_dir_for_page_from_url(), validate that it does not contain encoded traversal sequences (%2e%2e, %252f) or literal ones (.., ..\\). Implement a dedicated allowlist or denylist function like is_url_safe_for_cache_clear() that rejects these patterns outright.

  • Validate component extraction results. After wp_parse_url() or similar parsing functions, check that expected keys exist (e.g., isset($parsed_url['host'], $parsed_url['path'])) before using them. An empty or missing component can lead to unexpected paths or default fallbacks.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2025-13377

Frequently asked questions about CVE-2025-13377

What is CVE-2025-13377?

CVE-2025-13377 is a security vulnerability identified in tenweb-speed-optimizer. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2025-13377?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2025-13377. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2025-13377 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting tenweb-speed-optimizer. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2025-13377?

CVE-2025-13377 affects tenweb-speed-optimizer. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2025-13377?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for tenweb-speed-optimizer.

What is the CVSS score for CVE-2025-13377?

The severity rating and CVSS scoring for CVE-2025-13377 affecting tenweb-speed-optimizer is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.