SECURITY ADVISORY / 01

CVE-2025-7526 Exploit & Vulnerability Analysis

Complete CVE-2025-7526 security advisory with proof of concept (PoC), exploit details, and patch analysis for wp-travel-engine.

wp-travel-engine products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An unauthenticated attacker can delete arbitrary files on the server by crafting a POST request to the form handler with a malicious image_url parameter containing path traversal sequences.

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded

action=wte_set_user_profile_image&image_url=../../wp-config.php&user_id=1

The server processes the request without validating that image_url stays within the uploads directory. The rename() call executes against the traversal path, moving or deleting the target file. If wp-config.php exists at that traversed location, it is removed; the attacker observes a 200 response with success output, and the WordPress installation immediately becomes non-functional — database credentials are gone.

What the Patch Did

Before:

$img_upload_dir = wp_upload_dir();
$wte_image_dir  = trailingslashit( $img_upload_dir[ 'basedir' ] ) . 'wp-travel-engine/images/users';
$img_filetype   = wp_check_filetype( $image_url, null );
$img_file_ext   = isset( $img_filetype[ 'ext' ] ) ? $img_filetype[ 'ext' ] : '.jpg';

$img_file_name = $wte_image_dir . '/wte_users_' . $user_id . '.' . $img_file_ext;
if ( wp_mkdir_p( $wte_image_dir ) ) :
    if ( file_exists( $image_url ) || file_exists( $wte_image_dir . '/wte_users_' . $user_id . '.' . $img_file_ext ) ) :
        if ( file_exists( $wte_image_dir . '/wte_users_' . $user_id . '.' . $img_file_ext ) ) :
            unlink( $wte_image_dir . '/wte_users_' . $user_id . '.' . $img_file_ext );
        endif;
        if ( file_exists( $image_url ) ) :
            rename( $image_url, $wte_image_dir . '/wte_users_' . $user_id . '.' . $img_file_ext );
        endif;
    endif;
endif;

After:

$uploads  = wp_get_upload_dir();
$base_real = ( $uploads['basedir'] ?? false ) ? realpath( $uploads['basedir'] ) : false;
if ( ! $base_real ) {
    return false;
}

$wte_image_dir  = trailingslashit( $uploads[ 'basedir' ] ) . 'wp-travel-engine/images/users';
$img_filetype   = wp_check_filetype( $image_url, null );

if ( ! ( $img_filetype[ 'ext' ] ?? true ) || ! ( $img_filetype[ 'type' ] ?? true ) ) {
    return false;
}

$img_file_ext	= $img_filetype[ 'ext' ] ?? '.jpg';
$img_file_name 	= $wte_image_dir . '/wte_users_' . $user_id . '.' . $img_file_ext;

if ( wp_mkdir_p( $wte_image_dir ) ) :
    if ( file_exists( $img_file_name ) ) :
        wp_delete_file( $img_file_name );
    endif;
    if ( file_exists( $image_url ) ) :
        $src_real = realpath( $image_url );
        if ( $src_real && is_file( $src_real ) && strpos( wp_normalize_path( $src_real ), wp_normalize_path( $base_real ) ) === 0 ) {
            @rename( $src_real, $img_file_name );
        }
    endif;
endif;

The patch added three load-bearing security controls. First, it canonicalizes both the source file and the uploads base directory using realpath(), which resolves all symlinks and .. sequences to their true filesystem paths. Second, it validates that the resolved source file path begins with the resolved base directory path using a substring check (strpos()), ensuring the source is confined to the uploads tree. Third, it added a type guard (is_file()) to confirm the source is a regular file, not a directory or special file. These controls work together to implement path confinement — the attacker cannot escape the intended directory, even with traversal sequences.

Root Cause

The vulnerability is CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') in the set_user_profile_image() function (lines 479–555 of class-wp-travel-engine-form-handler.php). The image_url parameter, received from an unauthenticated AJAX request via $_POST, is passed directly to file_exists() and rename() without canonicalization or boundary validation. An attacker supplies a path like ../../wp-config.php, which the vulnerable code resolves at runtime through PHP's directory traversal logic, crossing the trust boundary into protected areas of the filesystem.

Why It Works

The single load-bearing line is:

if ( $src_real && is_file( $src_real ) && strpos( wp_normalize_path( $src_real ), wp_normalize_path( $base_real ) ) === 0 ) {

Without the strpos() boundary check, an attacker could still pass a canonicalized symlink or hardlink to an arbitrary file outside the uploads directory, and the rename() would execute. The realpath() calls alone are insufficient because they resolve symlinks after the attacker controls the path string — a symlink inside /uploads can point outside it. The substring validation is the critical control: it enforces that the real, resolved path of the source begins with the real, resolved path of the safe base directory. No directory traversal or symlink can bypass this. The is_file() type guard and the early realpath() validation of $base_real are defence-in-depth: they prevent edge cases like directory deletion and ensure the base directory itself is valid before the boundary check runs.

Hardening Checklist

  • Always call realpath() on user-supplied file paths before passing them to file_exists(), unlink(), rename(), or copy(), then compare the resolved path against a whitelist or a safe base directory using strpos() or str_starts_with().
  • Use wp_normalize_path() on both the source and base paths before string comparison to handle Windows backslashes and symbolic links consistently across platforms.
  • Add is_file() or is_dir() type guards before filesystem operations on user-controlled paths to prevent unintended deletion of unexpected file types.
  • Validate file MIME types and extensions early using wp_check_filetype(), then reject files that fail validation rather than defaulting to a safe extension like .jpg.
  • Use wp_delete_file() instead of unlink() — it logs deletions and applies additional WordPress-level safety filters.

References

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

Frequently asked questions about CVE-2025-7526

What is CVE-2025-7526?

CVE-2025-7526 is a security vulnerability identified in wp-travel-engine. 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-7526?

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

How does CVE-2025-7526 get exploited?

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

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

CVE-2025-7526 affects wp-travel-engine. 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-7526?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for wp-travel-engine.

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

The severity rating and CVSS scoring for CVE-2025-7526 affecting wp-travel-engine is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.