configs->get_value('aiowps_fcd_filename'); if (empty($fcd_filename)) { // means that we haven't done a scan before, or, // the fcd file containing the results doesn't exist $random_suffix = AIOWPSecurity_Utility::generate_alpha_numeric_random_string(10); $fcd_filename = 'aiowps_fcd_data_' . $random_suffix; $aio_wp_security->configs->set_value('aiowps_fcd_filename', $fcd_filename, true); } $fcd_data = self::get_fcd_data(); // get previous scan data if any if (false === $fcd_data) { // an error occurred so return return false; } $scanned_data = $this->do_file_change_scan(); if (empty($fcd_data)) { $this->save_fcd_data($scanned_data); $scan_result['initial_scan'] = '1'; return $scan_result; } else { $scan_result = $this->compare_scan_data($fcd_data['file_scan_data'], $scanned_data); $scan_result['initial_scan'] = ''; $this->save_fcd_data($scanned_data, $scan_result); if (!empty($scan_result['files_added']) || !empty($scan_result['files_removed']) || !empty($scan_result['files_changed'])) { //This means there was a change detected $aio_wp_security->configs->set_value('aiowps_fcds_change_detected', true, true); $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - change to filesystem detected!"); $this->aiowps_send_file_change_alert_email($scan_result); //Send file change scan results via email if applicable } else { //Reset the change flag $aio_wp_security->configs->set_value('aiowps_fcds_change_detected', false, true); } return $scan_result; } } /** * Send email with notification about file changes detected by last scan. * * @global AIO_WP_Security $aio_wp_security * @param array $scan_result Array with scan result returned by compare_scan_data() method. */ public function aiowps_send_file_change_alert_email($scan_result) { global $aio_wp_security; if ('1' == $aio_wp_security->configs->get_value('aiowps_send_fcd_scan_email')) { $subject = __('All In One WP Security - File change detected', 'all-in-one-wp-security-and-firewall') . ' ' . date('l, F jS, Y \a\\t g:i a', current_time('timestamp')); //$attachment = array(); $message = __('A file change was detected on your system for site URL', 'all-in-one-wp-security-and-firewall') . ' ' . network_site_url() . __('. Scan was generated on', 'all-in-one-wp-security-and-firewall') . ' ' . date('l, F jS, Y \a\\t g:i a', current_time('timestamp')); $message .= "\r\n\r\n".__('A summary of the scan results is shown below:', 'all-in-one-wp-security-and-firewall'); $message .= "\r\n\r\n"; $message .= self::get_file_change_summary($scan_result); $message .= "\r\n".__('Login to your site to view the scan details.', 'all-in-one-wp-security-and-firewall'); // Get the email address(es). $addresses = AIOWPSecurity_Utility::get_array_from_textarea_val($aio_wp_security->configs->get_value('aiowps_fcd_scan_email_address')); // If no explicit email address(es) are given, send email to site admin. $to = empty($addresses) ? array(get_site_option('admin_email')) : $addresses; if (!wp_mail($to, $subject, $message)) { $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - File change notification email failed to send.", 4); } } } public function aiowps_scheduled_fcd_scan_handler() { global $aio_wp_security; if ($aio_wp_security->configs->get_value('aiowps_enable_automated_fcd_scan')=='1') { $aio_wp_security->debug_logger->log_debug_cron(__METHOD__ . " - Scheduled fcd_scan is enabled. Checking now to see if scan needs to be done..."); $time_now = current_time('mysql'); $current_time = strtotime($time_now); $fcd_scan_frequency = $aio_wp_security->configs->get_value('aiowps_fcd_scan_frequency'); //Number of hours or days or months interval $interval_setting = $aio_wp_security->configs->get_value('aiowps_fcd_scan_interval'); //Hours/Days/Months switch ($interval_setting) { case '0': $interval = 'hours'; break; case '1': $interval = 'days'; break; case '2': $interval = 'weeks'; break; } $last_fcd_scan_time_string = $aio_wp_security->configs->get_value('aiowps_last_fcd_scan_time'); if (null != $last_fcd_scan_time_string) { $last_fcd_scan_time = strtotime($last_fcd_scan_time_string); $next_fcd_scan_time = strtotime("+".abs($fcd_scan_frequency).$interval, $last_fcd_scan_time); if ($next_fcd_scan_time <= $current_time) { //It's time to do a filescan $result = $this->execute_file_change_detection_scan(); if (false === $result) { $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - Scheduled filescan operation failed!", 4); } else { $aio_wp_security->configs->set_value('aiowps_last_fcd_scan_time', $time_now, true); } } } else { //Set the last scan time to now so it can trigger for the next scheduled period $aio_wp_security->configs->set_value('aiowps_last_fcd_scan_time', $time_now, true); } } } /** * Get the last filechange detection data which is stored in the special file. * * @global AIO_WP_Security $aio_wp_security * @return bool|array - false on failure, array on success */ public static function get_fcd_data() { global $aio_wp_security; $aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME; $fcd_filename = $aio_wp_security->configs->get_value('aiowps_fcd_filename'); $results_file = $aiowps_backup_dir. '/'. $fcd_filename; if (!file_exists($results_file)) { $fp = @fopen($results_file, 'w'); //open for write - will create file if doesn't exist return array(); } if (empty(filesize($results_file))) { return array(); // if newly created file return empty array } $fp = @fopen($results_file, 'r'); //open for read and write - will create file if doesn't exist if (false === $fp) { // Error $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - fopen returned false when opening fcd data file"); return false; } $contents = fread($fp, filesize($results_file)); fclose($fp); if (false === $contents) { // Error $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - fread returned false when reading fcd data file"); return false; } else { $fcd_file_contents = json_decode($contents, true); if (isset($fcd_file_contents['file_scan_data'])) { return $fcd_file_contents; } else { return array(); } } } /** * Recursively scan the entire $start_dir directory and return file size * and last modified date of every regular file. Ignore files and file * types specified in file scanner settings. * * @global AIO_WP_Security $aio_wp_security * @param string $start_dir * @return array */ public function do_file_change_scan($start_dir = ABSPATH) { global $aio_wp_security; $filescan_data = array(); // Iterator key is absolute file path, iterator value is SplFileInfo object, // iteration skips '..' and '.' records, because we're not interested in directories. $dit = new RecursiveDirectoryIterator($start_dir, FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS); $rit = new RecursiveIteratorIterator($dit, RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD); // Grab files/directories to skip $files_to_skip = AIOWPSecurity_Utility::explode_trim_filter_empty($aio_wp_security->configs->get_value('aiowps_fcd_exclude_files')); // Grab (lowercased) file types to skip $file_types_to_skip = AIOWPSecurity_Utility::explode_trim_filter_empty(strtolower($aio_wp_security->configs->get_value('aiowps_fcd_exclude_filetypes'))); $start_dir_length = strlen($start_dir); foreach ($rit as $filename => $fileinfo) { if (!file_exists($filename) || is_dir($filename)) { continue; // if file doesn't exist or is a directory move on to next iteration } if ($fileinfo->getFilename() == 'wp-security-log-cron-job.txt' || $fileinfo->getFilename() == 'wp-security-log.txt') { continue; // Skip AIOS log files } // Let's omit any file types from the scan which were specified in the settings if necessary if (!empty($file_types_to_skip)) { //$current_file_ext = strtolower($fileinfo->getExtension()); //getExtension() only available on PHP 5.3.6 or higher $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (in_array($ext, $file_types_to_skip)) { continue; } } // Let's omit specific files or directories from the scan which were specified in the settings if (!empty($files_to_skip)) { $skip_this = false; foreach ($files_to_skip as $f_or_dir) { // Expect files/dirs to be specified relatively to $start_dir, // so start searching at $start_dir_length offset. if (strpos($filename, $f_or_dir, $start_dir_length) !== false) { $skip_this = true; break; // ! } } if ($skip_this) { continue; } } $filescan_data[$filename] = array( 'last_modified' => $fileinfo->getMTime(), 'filesize' => $fileinfo->getSize(), ); } return $filescan_data; } public function compare_scan_data($last_scan_data, $new_scanned_data) { // Identify new files added: get all files which are in the new scan but not present in the old scan $files_added = @array_diff_key($new_scanned_data, $last_scan_data); // Identify files deleted: get all files which are in the old scan but not present in the new scan $files_removed = @array_diff_key($last_scan_data, $new_scanned_data); // Identify existing files: get all files which are in new scan, but were not added $files_kept = @array_diff_key($new_scanned_data, $files_added); $files_changed = array(); // Loop through existing files and determine, if they have been changed foreach ($files_kept as $filename => $new_scan_meta) { $last_scan_meta = $last_scan_data[$filename]; // Check filesize and last_modified values if (($new_scan_meta['last_modified'] !== $last_scan_meta['last_modified']) || ($new_scan_meta['filesize'] !== $last_scan_meta['filesize'])) { $files_changed[$filename] = $new_scan_meta; } } // Create single array of all changes return array( 'files_added' => $files_added, 'files_removed' => $files_removed, 'files_changed' => $files_changed, ); } public static function get_file_change_data() { global $wpdb, $aio_wp_security; //Let's get the results array from the DB $tbl_name = AIOWPSEC_TBL_GLOBAL_META_DATA; $key = 'file_change_detection'; $sql_prep = $wpdb->prepare("SELECT * FROM $tbl_name WHERE meta_key1 = %s", $key); $scan_db_data = $wpdb->get_row($sql_prep, ARRAY_A); if (null === $scan_db_data) { $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - DB query for scan results data from global meta table returned null!", 4); return false; } $scan_results_unserialized = maybe_unserialize($scan_db_data['meta_value5']); if (empty($scan_results_unserialized['files_added']) && empty($scan_results_unserialized['files_removed']) && empty($scan_results_unserialized['files_changed'])) { //No file change detected return false; } else { return $scan_results_unserialized; } } public static function get_file_change_summary($scan_result) { $scan_summary = ""; if (!empty($scan_result['files_added'])) { //Output of files added $scan_summary .= "\r\n".__('The following files were added to your host', 'all-in-one-wp-security-and-firewall').":\r\n"; foreach ($scan_result['files_added'] as $key => $value) { $scan_summary .= "\r\n".$key.' ('.__('modified on: ', 'all-in-one-wp-security-and-firewall').date('Y-m-d H:i:s', $value['last_modified']).')'; } $scan_summary .= "\r\n======================================\r\n"; } if (!empty($scan_result['files_removed'])) { //Output of files removed $scan_summary .= "\r\n".__('The following files were removed from your host', 'all-in-one-wp-security-and-firewall').":\r\n"; foreach ($scan_result['files_removed'] as $key => $value) { $scan_summary .= "\r\n".$key.' ('.__('modified on: ', 'all-in-one-wp-security-and-firewall').date('Y-m-d H:i:s', $value['last_modified']).')'; } $scan_summary .= "\r\n======================================\r\n"; } if (!empty($scan_result['files_changed'])) { //Output of files changed $scan_summary .= "\r\n".__('The following files were changed on your host', 'all-in-one-wp-security-and-firewall').":\r\n"; foreach ($scan_result['files_changed'] as $key => $value) { $scan_summary .= "\r\n".$key.' ('.__('modified on: ', 'all-in-one-wp-security-and-firewall').date('Y-m-d H:i:s', $value['last_modified']).')'; } $scan_summary .= "\r\n======================================\r\n"; } return $scan_summary; } /** * Saves file change detection data into a special file * * @global AIO_WP_Security $aio_wp_security * @param type $scanned_data * @param type $scan_result * @return boolean */ public function save_fcd_data($scanned_data, $scan_result = array()) { global $aio_wp_security; $date_time = current_time('mysql'); $data = array('date_time' => $date_time, 'file_scan_data' => $scanned_data, 'last_scan_result' => $scan_result); $fcd_filename = $aio_wp_security->configs->get_value('aiowps_fcd_filename'); $aiowps_backup_dir = WP_CONTENT_DIR.'/'.AIO_WP_SECURITY_BACKUPS_DIR_NAME; if (!AIOWPSecurity_Utility_File::create_dir($aiowps_backup_dir)) { $aio_wp_security->debug_logger->log_debug(__METHOD__ . " - Creation of DB backup directory failed!", 4); return false; } $results_file = $aiowps_backup_dir. '/'. $fcd_filename; $fp = fopen($results_file, 'w'); fwrite($fp, json_encode($data)); fclose($fp); } }