| 1 |
<?php |
| 2 |
defined('ABSPATH') or die('Unauthorized Access'); |
| 3 |
|
| 4 |
class Phpinfo_WP_Permissions { |
| 5 |
|
| 6 |
public static function register(): void { |
| 7 |
// No background hooks needed. This is an on-demand auditing tool. |
| 8 |
} |
| 9 |
|
| 10 |
public static function get_php_user(): string { |
| 11 |
if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) { |
| 12 |
$user = posix_getpwuid(posix_geteuid()); |
| 13 |
return $user['name'] ?? get_current_user(); |
| 14 |
} |
| 15 |
return get_current_user(); |
| 16 |
} |
| 17 |
|
| 18 |
public static function scan(): array { |
| 19 |
if (!Phpinfo_WP_License::is_valid()) return []; |
| 20 |
|
| 21 |
$php_uid = function_exists('posix_geteuid') ? posix_geteuid() : getmyuid(); |
| 22 |
|
| 23 |
$results = [ |
| 24 |
'dangerous' => [], |
| 25 |
'mismatch' => [], |
| 26 |
'scanned' => 0, |
| 27 |
'wp_config' => null, |
| 28 |
'php_uid' => $php_uid, |
| 29 |
'php_user' => self::get_php_user(), |
| 30 |
]; |
| 31 |
|
| 32 |
// 1. Check wp-config.php specifically |
| 33 |
$wp_config_path = self::get_wp_config_path(); |
| 34 |
if ($wp_config_path && file_exists($wp_config_path)) { |
| 35 |
$perms = fileperms($wp_config_path) & 0777; |
| 36 |
$owner = fileowner($wp_config_path); |
| 37 |
|
| 38 |
$results['wp_config'] = [ |
| 39 |
'path' => $wp_config_path, |
| 40 |
'perms' => sprintf('%04o', $perms), |
| 41 |
'owner' => function_exists('posix_getpwuid') ? (posix_getpwuid($owner)['name'] ?? $owner) : $owner, |
| 42 |
'is_dangerous' => ($perms === 0666 || $perms === 0777), |
| 43 |
]; |
| 44 |
$results['scanned']++; |
| 45 |
} |
| 46 |
|
| 47 |
// 2. Targeted scan |
| 48 |
$targets = [ |
| 49 |
ABSPATH . WPINC, |
| 50 |
ABSPATH . 'wp-admin', |
| 51 |
WP_PLUGIN_DIR, |
| 52 |
get_theme_root(), |
| 53 |
]; |
| 54 |
|
| 55 |
$limit = 5000; |
| 56 |
|
| 57 |
foreach ($targets as $dir) { |
| 58 |
if (!is_dir($dir)) continue; |
| 59 |
|
| 60 |
$iterator = new RecursiveIteratorIterator( |
| 61 |
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), |
| 62 |
RecursiveIteratorIterator::SELF_FIRST |
| 63 |
); |
| 64 |
|
| 65 |
foreach ($iterator as $file) { |
| 66 |
if ($results['scanned'] >= $limit) break 2; |
| 67 |
|
| 68 |
$results['scanned']++; |
| 69 |
$path = $file->getPathname(); |
| 70 |
$is_dir = $file->isDir(); |
| 71 |
$perms = $file->getPerms() & 0777; |
| 72 |
$owner = $file->getOwner(); |
| 73 |
|
| 74 |
// Dangerous permissions: 777 on dirs, 666/777 on files |
| 75 |
if ($is_dir && $perms === 0777) { |
| 76 |
$results['dangerous'][] = ['path' => $path, 'perms' => '0777', 'type' => 'dir']; |
| 77 |
} elseif (!$is_dir && ($perms === 0666 || $perms === 0777)) { |
| 78 |
$results['dangerous'][] = ['path' => $path, 'perms' => sprintf('%04o', $perms), 'type' => 'file']; |
| 79 |
} |
| 80 |
|
| 81 |
// Ownership mismatch: File owner doesn't match PHP executor UID |
| 82 |
if ($owner !== $php_uid) { |
| 83 |
$owner_name = function_exists('posix_getpwuid') ? (posix_getpwuid($owner)['name'] ?? $owner) : $owner; |
| 84 |
// Cap mismatch results to 20 to prevent memory exhaustion, |
| 85 |
// since one wrong `chown` causes thousands of mismatches. |
| 86 |
if (count($results['mismatch']) < 20) { |
| 87 |
$results['mismatch'][] = ['path' => $path, 'owner' => $owner_name, 'type' => $is_dir ? 'dir' : 'file']; |
| 88 |
} |
| 89 |
} |
| 90 |
} |
| 91 |
} |
| 92 |
|
| 93 |
return $results; |
| 94 |
} |
| 95 |
|
| 96 |
private static function get_wp_config_path(): ?string { |
| 97 |
if (file_exists(ABSPATH . 'wp-config.php')) { |
| 98 |
return ABSPATH . 'wp-config.php'; |
| 99 |
} elseif (file_exists(dirname(ABSPATH) . '/wp-config.php') && !file_exists(dirname(ABSPATH) . '/wp-settings.php')) { |
| 100 |
return dirname(ABSPATH) . '/wp-config.php'; |
| 101 |
} |
| 102 |
return null; |
| 103 |
} |
| 104 |
} |
| 105 |
|