From 14022a97c100b16c25407227609642f8a03d26ca Mon Sep 17 00:00:00 2001 From: svfcode Date: Fri, 21 Mar 2025 09:47:35 +0300 Subject: [PATCH 01/18] New. DBTriggerScan. Init. --- inc/spbc-settings.php | 84 ++++++++++++++ .../Scanner/DBTrigger/DBTriggerModel.php | 107 ++++++++++++++++++ .../SpbctWP/Scanner/ScannerQueue.php | 31 +++++ .../Stages/DBTriggerAnalysis.php | 34 ++++++ lib/CleantalkSP/SpbctWP/State.php | 1 + 5 files changed, 257 insertions(+) create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/DBTriggerAnalysis.php diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index 1fdce63b7..306cc7c77 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -689,6 +689,12 @@ function spbc_settings__register() 'description' => Scanner\OSCron\View\OSCronLocale::getInstance()->settings__option_description, 'long_description' => false, ), + 'scanner__db_trigger_analysis' => array( + 'type' => 'field', + 'title' => __('DB Trigger analysis', 'security-malware-firewall'), + 'description' => __('Will search for known malicious signatures in database triggers.', 'security-malware-firewall'), + 'long_description' => false, + ), 'scanner__dir_exclusions_view' => array( 'type' => 'field', 'input_type' => 'textarea', @@ -3090,6 +3096,52 @@ function spbc_scanner_oscron_prepare_data(&$table) $table = OSCronView::prepareTableData($table); } +/** + * Count found in db trigger. + * @return int + */ +function spbc_scanner_db_trigger_count_found() +{ + return count(get_option('spbc_db_triggers', [])); +} + +/** + * Get data for db trigger. + * @return array + */ +function spbc_scanner_db_trigger_get_scanned() +{ + return get_option('spbc_db_triggers', []); +} + +/** + * Prepare data for db trigger. + * @param $table + */ +function spbc_scanner_db_trigger_prepare_data(&$table) +{ + $table->items = get_option('spbc_db_triggers', []); + $table->items_count = count($table->items); + $table->columns = [ + 'about_trigger' => array( 'heading' => 'About trigger', 'width_percent' => 30 ), // name, table, time, action + 'code' => array( 'heading' => 'Code', 'width_percent' => 45 ), + 'signature' => array( 'heading' => 'Signature', 'width_percent' => 10 ), + 'analysis_status' => array( 'heading' => 'Verdict', 'width_percent' => 15 ), + ]; + foreach ($table->items as $key => $item) { + $table->items[$key]['about_trigger'] = sprintf( + '%s
%s
%s
%s', + __('Name', 'security-malware-firewall') . ': ' . $item['name'], + __('Table', 'security-malware-firewall') . ': ' . $item['table'], + __('Time', 'security-malware-firewall') . ': ' . $item['time'], + __('Action', 'security-malware-firewall') . ': ' . $item['action'] + ); + $table->items[$key]['code'] = $item['code']; + $table->items[$key]['signature'] = $item['signature']; + $table->items[$key]['analysis_status'] = $item['status']; + } +} + function spbc_field_scanner__prepare_data__files_quarantine(&$table) { global $spbc; @@ -3437,6 +3489,10 @@ function spbc_field_scanner() echo '' . __('OS Cron Analysis', 'security-malware-firewall') . ' -> '; } + if ($spbc->settings['scanner__db_trigger_analysis']) { + echo '' . __('DB Trigger Analysis', 'security-malware-firewall') . ' -> '; + } + echo '' . __('Updating statuses for the denied files', 'security-malware-firewall') . ' -> ' . '' . __('Updating statuses for the approved files', 'security-malware-firewall') . ' -> '; @@ -3695,6 +3751,10 @@ function spbc_field_scanner__show_accordion($direct_call = false) $tables_files['oscron'] = Scanner\OSCron\View\OSCronLocale::getInstance()->settings__accordion_tab_description; } + if ($spbc->settings['scanner__db_trigger_analysis']) { + $tables_files['db_trigger'] = __('DB Trigger Analysis', 'security-malware-firewall'); + } + if ($spbc->settings['scanner__list_approved_by_cleantalk']) { $company_name = $spbc->default_data['wl_company_name']; if ($spbc->data["wl_mode_enabled"]) { @@ -3750,6 +3810,13 @@ function spbc_field_scanner__show_accordion($direct_call = false) ), 'display' => (bool) $spbc->settings['scanner__os_cron_analysis'] ), + 'db_trigger_analysis' => array( + 'category_description' => __('DB Trigger Analysis', 'security-malware-firewall'), + 'types' => array( + 'db_trigger', + ), + 'display' => (bool) $spbc->settings['scanner__db_trigger_analysis'] + ), 'pages' => array( 'category_description' => __('Pages scan results', 'security-malware-firewall'), 'types' => array( @@ -4243,6 +4310,23 @@ function spbc_list_table__get_args_by_type($table_type) ); break; + case 'db_trigger': + $args = array( + 'func_data_total' => 'spbc_scanner_db_trigger_count_found', + 'func_data_get' => 'spbc_scanner_db_trigger_get_scanned', + 'func_data_prepare' => 'spbc_scanner_db_trigger_prepare_data', + 'if_empty_items' => '
' + . __('DB Trigger not found in the server environment or is unavailable to read/write.', 'security-malware-firewall') + . '
', + 'columns' => array( + 'about_trigger' => array( 'heading' => 'About trigger', 'width_percent' => 30 ), // name, table, time, action + 'code' => array( 'heading' => 'Code', 'width_percent' => 45 ), + 'signature' => array( 'heading' => 'Signature', 'width_percent' => 10 ), + 'analysis_status' => array( 'heading' => 'Verdict', 'width_percent' => 15 ), + ), + ); + break; + case 'approved': $args = array_replace_recursive( $accordion_default_args, diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php new file mode 100644 index 000000000..9a404e7c5 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -0,0 +1,107 @@ +getMessage(); + } + + return true; + } + + /** + * Get all triggers from the database. + * + * @return array The list of triggers. + */ + private static function getDBTriggers() + { + global $wpdb; + + $db_triggers = $wpdb->get_results( + $wpdb->prepare( + "SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_STATEMENT, ACTION_TIMING + FROM information_schema.TRIGGERS + WHERE TRIGGER_SCHEMA = %s", + $wpdb->dbname + ) + ); + + return $db_triggers; + } + + /** + * Analyze the triggers and return the bad ones. + * + * @param array $db_triggers The list of triggers. + * @return array The list of bad triggers. + */ + private static function analyzeDBTriggers($db_triggers) + { + $bad_triggers = array(); + + foreach ($db_triggers as $trigger) { + $trigger_code = $trigger->ACTION_STATEMENT; + $signature_name = self::isTriggerBad($trigger_code); + + if ($signature_name) { + $bad_triggers[] = array( + 'name' => $trigger->TRIGGER_NAME, + 'table' => $trigger->EVENT_OBJECT_TABLE, + 'time' => $trigger->ACTION_TIMING, + 'action' => $trigger->EVENT_MANIPULATION, + 'code' => $trigger_code, + 'signature' => $signature_name, + 'status' => 'vulnerable trigger', + ); + } + } + + return $bad_triggers; + } + + /** + * Check if the trigger is bad. + * + * @param string $trigger_code The code of the trigger. + * @return string|bool The signature name if the trigger is bad, false otherwise. + */ + private static function isTriggerBad($trigger_code) + { + $signatures = self::getSignatures(); + + foreach ($signatures as $signature_name => $signature) { + if (preg_match('/' . $signature . '/is', $trigger_code)) { + return $signature_name; + } + } + + return false; + } + + private static function getSignatures() + { + return [ + 'v1' => 'IF NEW.comment_content LIKE \'%.*?%\' THEN\s+SET @lastInsertWpUsersId = \(SELECT MAX\(id\) FROM.*\s+SET @nextWpUsersID = @lastInsertWpUsersId \+ 1;\s+INSERT INTO.*_users.*VALUES.*wpadmin.*wp-security@hotmail\.com', + 'v2' => 'INSERT INTO.*_users.*VALUES.*wpadmin.*wp-security@hotmail\.com.*\s+INSERT INTO.*administrator', + ]; + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 4fb6b3368..719cd4bbd 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -3,10 +3,12 @@ namespace CleantalkSP\SpbctWP\Scanner; use CleantalkSP\SpbctWP\Scanner\CureLog\CureLog; +use CleantalkSP\SpbctWP\Scanner\DBTrigger\DBTriggerModel; use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronModel; use CleantalkSP\SpbctWP\Scanner\ScanningLog\ScanningLogFacade; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\ScannerFileStatuses; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\ScanningStagesStorage; +use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\DBTriggerAnalysis; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\FileSystemAnalysis; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\FrontendAnalysis; use CleantalkSP\SpbctWP\Scanner\ScanningStagesModule\Stages\GetApprovedHashes; @@ -45,6 +47,7 @@ class ScannerQueue 'clean_results', 'file_system_analysis', 'os_cron_analysis', + 'db_trigger_analysis', 'get_denied_hashes', 'get_approved_hashes', 'signature_analysis', @@ -1061,6 +1064,34 @@ public function os_cron_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMethodN return array('end' => 1); } + /** + * Scan database triggers for malicious code + * + * @return array + */ + public function db_trigger_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps + { + $scanning_stages_storage = new ScanningStagesStorage(); + $scanning_stages_storage->converter->loadCollection(); + $stage_data_obj = $scanning_stages_storage->getStage(DBTriggerAnalysis::class); + + $result = DBTriggerModel::run(); + + if (true !== $result) { + ScanningLogFacade::writeToLog( + '' . $stage_data_obj::getTitle() . ' ' . $result + ); + + return array('end' => 1); + } + + ScanningLogFacade::writeToLog( + '' . $stage_data_obj::getTitle() . ' ' . $stage_data_obj->getDescription() + ); + + return array('end' => 1); + } + /** * Getting remote hashes of denied files * diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/DBTriggerAnalysis.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/DBTriggerAnalysis.php new file mode 100644 index 000000000..0f66dcb2e --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScanningStagesModule/Stages/DBTriggerAnalysis.php @@ -0,0 +1,34 @@ +scanned_count_files + . '.'; + } + + public function getName() + { + return __CLASS__; + } + + public function getData() + { + return array(); + } +} diff --git a/lib/CleantalkSP/SpbctWP/State.php b/lib/CleantalkSP/SpbctWP/State.php index eb11dd4c7..b97905a15 100644 --- a/lib/CleantalkSP/SpbctWP/State.php +++ b/lib/CleantalkSP/SpbctWP/State.php @@ -76,6 +76,7 @@ class State extends \CleantalkSP\Common\State 'scanner__signature_analysis' => 1, 'scanner__auto_cure' => 1, 'scanner__os_cron_analysis' => 1, + 'scanner__db_trigger_analysis' => 1, 'scanner__dir_exclusions' => '', 'scanner__dir_exclusions_view' => '', 'scanner__list_unknown' => 1, From 5d1e146948660ba5972a08e225a038b020cada97 Mon Sep 17 00:00:00 2001 From: svfcode Date: Tue, 25 Mar 2025 14:48:54 +0300 Subject: [PATCH 02/18] fix phpcs --- lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 9a404e7c5..15f50c9ca 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -4,8 +4,6 @@ class DBTriggerModel { - private static $spbc_marker = '# Disabled by Security by CleanTalk #'; - /** * Runs the DBTrigger model to find malicious database triggers. * @@ -19,7 +17,6 @@ public static function run() $bad_triggers = self::analyzeDBTriggers($db_triggers); update_option('spbc_db_triggers', $bad_triggers); - } catch (\Exception $error) { return $error->getMessage(); } @@ -44,7 +41,7 @@ private static function getDBTriggers() $wpdb->dbname ) ); - + return $db_triggers; } From e2fe9e71a80d54f9b1824091ebce995d85b2f567 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Thu, 10 Apr 2025 17:18:19 +0500 Subject: [PATCH 03/18] Mod. Scanner. DB Triggers. Classes refactored, description added, signatures table prepared for new types. --- inc/spbc-admin.php | 8 +- inc/spbc-settings.php | 114 +++++++----------- lib/CleantalkSP/SpbctWP/DB/SQLSchema.php | 2 +- .../Scanner/DBTrigger/DBTriggerModel.php | 36 +----- .../Scanner/DBTrigger/DBTriggerService.php | 84 +++++++++++++ .../Scanner/DBTrigger/DBTriggerView.php | 52 ++++++++ lib/CleantalkSP/SpbctWP/State.php | 1 + lib/CleantalkSP/Updater/UpdaterScripts.php | 12 ++ 8 files changed, 204 insertions(+), 105 deletions(-) create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php diff --git a/inc/spbc-admin.php b/inc/spbc-admin.php index 51e56fdca..703e29462 100644 --- a/inc/spbc-admin.php +++ b/inc/spbc-admin.php @@ -4,6 +4,7 @@ use CleantalkSP\SpbctWP\CleantalkSettingsTemplates; use CleantalkSP\SpbctWP\Cron; use CleantalkSP\SpbctWP\Escape; +use CleantalkSP\SpbctWP\Scanner\DBTrigger\DBTriggerService; use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\PluginReport; use CleantalkSP\SpbctWP\VulnerabilityAlarm\Dto\ThemeReport; use CleantalkSP\SpbctWP\VulnerabilityAlarm\VulnerabilityAlarmService; @@ -1160,14 +1161,17 @@ function spbc_set_malware_scan_warns() $oscron_has_dangerous = OsCronTasksStorage::getCountOfDangerousTasks(); + $triggers_has_dangerous = DBTriggerService::countTriggersStorage(); + $spbc->data['display_scanner_warnings'] = array( 'critical' => $critical_count, 'signatures' => $signatures_count, 'frontend' => $frontend_count, 'analysis' => $analysis_has_dangerous, 'oscron' => $oscron_has_dangerous, - 'analysis_all_safe' => !$analysis_has_uncheked && !$analysis_has_dangerous && !$oscron_has_dangerous, - 'warn_on_admin_bar' => $critical_count || $frontend_count || $analysis_has_dangerous || $oscron_has_dangerous, + 'db_triggers' => $triggers_has_dangerous, + 'analysis_all_safe' => !$analysis_has_uncheked && !$analysis_has_dangerous && !$oscron_has_dangerous && !$triggers_has_dangerous, + 'warn_on_admin_bar' => $critical_count || $frontend_count || $analysis_has_dangerous || $oscron_has_dangerous || $triggers_has_dangerous, ); $spbc->notice_critical_files_warning = !empty($critical_count); $spbc->save('data'); diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index 6227007fb..749160c65 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -10,6 +10,7 @@ use CleantalkSP\SpbctWP\LinkConstructor; use CleantalkSP\SpbctWP\ListTable; use CleantalkSP\SpbctWP\Scanner; +use CleantalkSP\SpbctWP\Scanner\DBTrigger\DBTriggerView; use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OsCronTasksStorage; use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronView; use CleantalkSP\SpbctWP\Scanner\ScanningLog\ScanningLogFacade; @@ -18,6 +19,7 @@ use CleantalkSP\SpbctWP\VulnerabilityAlarm\VulnerabilityAlarmView; use CleantalkSP\Variables\Post; use CleantalkSP\Variables\Server; +use CleantalkSP\SpbctWP\Scanner\DBTrigger\DBTriggerService; // Scanner AJAX actions require_once(SPBC_PLUGIN_DIR . 'inc/spbc-scanner.php'); @@ -1356,7 +1358,7 @@ function spbc_settings__draw_elements($elems_to_draw = null, $direct_call = fals echo ''; break; case 'section_banner': - spbc_settings__create_notice_on_tab('found_critical_files'); + spbc_settings__create_notice_on_tab(); break; case 'field': call_user_func($elem['callback'], $elem); @@ -2613,52 +2615,49 @@ function spbc_field_traffic_control_logs__prepare_data(&$table) /** * Creates a banner with a notification - * @param string $flag_text_banner which banner text to show * @return void */ -function spbc_settings__create_notice_on_tab($flag_text_banner) +function spbc_settings__create_notice_on_tab() { global $spbc; - switch ($flag_text_banner) { - case 'found_critical_files': // Creates a banner with a notification if important files are found and if the banner has not already been closed - if ($spbc->data['display_scanner_warnings']['critical'] > 0 && - Cookie::getString('spbct_notice-found_critical_files') != '1') { - $email = spbc_get_admin_email(); - $website = get_home_url(); - $text = __("There's a high probability that your website has been compromised, as critical files show signs of infection. Take action now by ordering malware removal from our experienced security specialists.", 'security-malware-firewall'); - //generate button - $landing_page_link = LinkConstructor::buildCleanTalkLink( - 'banner_link_for_treatment', - 'website-malware-removal', - array( - 'email' => esc_attr($email), - 'website' => esc_attr($website), - ), - $domain = 'https://l.cleantalk.org' - ); - $button_text = __('Request Malware removal', 'security-malware-firewall'); - $button_div = '
'; - $button_div .= ' - ' - . '  ' - . $button_text - . ' - '; - $button_div .= '
'; - $text .= $button_div; - } else { - return; - } + $flag_text_banner = ''; - break; + if ($spbc->data['display_scanner_warnings']['critical'] > 0 && + Cookie::getString('spbct_notice-found_critical_files') != '1') { + $flag_text_banner = 'found_critical_files'; + $text = __("There's a high probability that your website has been compromised, as critical files show signs of infection. Take action now by ordering malware removal from our experienced security specialists.", 'security-malware-firewall'); + } - default: - $text = false; - break; + if ( $spbc->data['display_scanner_warnings']['db_triggers'] > 0 && + Cookie::getString('spbct_notice-found_db_triggers') != '1') { + $flag_text_banner = 'found_db_triggers'; + $text = DBTriggerView::getWarningTextForMalwareRemovalBanner(); } - if ($text != false) { + if (!empty($text)) { + $email = spbc_get_admin_email(); + $website = get_home_url(); + $button_text = __('Request Malware removal', 'security-malware-firewall'); + $landing_page_link = LinkConstructor::buildCleanTalkLink( + 'banner_link_for_treatment', + 'website-malware-removal', + array( + 'email' => esc_attr($email), + 'website' => esc_attr($website), + ), + $domain = 'https://l.cleantalk.org' + ); + $button_div = '
'; + $button_div .= ' + ' + . '  ' + . $button_text + . ' + '; + $button_div .= '
'; + $text .= $button_div; + $template = '
@@ -2671,8 +2670,6 @@ function spbc_settings__create_notice_on_tab($flag_text_banner)
'; printf($template, $flag_text_banner, $flag_text_banner, $text); - } else { - return; } } @@ -3097,49 +3094,30 @@ function spbc_scanner_oscron_prepare_data(&$table) } /** - * Count found in db trigger. + * Settings function wrapper. Get count found in db trigger. * @return int */ function spbc_scanner_db_trigger_count_found() { - return count(get_option('spbc_db_triggers', [])); + return DBTriggerService::countTriggersStorage(); } /** - * Get data for db trigger. + * Settings function wrapper. Get data for db trigger. * @return array */ function spbc_scanner_db_trigger_get_scanned() { - return get_option('spbc_db_triggers', []); + return DBTriggerService::loadTriggersStorage(); } /** - * Prepare data for db trigger. + * Settings function wrapper. Modify data in triggers table. * @param $table */ function spbc_scanner_db_trigger_prepare_data(&$table) { - $table->items = get_option('spbc_db_triggers', []); - $table->items_count = count($table->items); - $table->columns = [ - 'about_trigger' => array( 'heading' => 'About trigger', 'width_percent' => 30 ), // name, table, time, action - 'code' => array( 'heading' => 'Code', 'width_percent' => 45 ), - 'signature' => array( 'heading' => 'Signature', 'width_percent' => 10 ), - 'analysis_status' => array( 'heading' => 'Verdict', 'width_percent' => 15 ), - ]; - foreach ($table->items as $key => $item) { - $table->items[$key]['about_trigger'] = sprintf( - '%s
%s
%s
%s', - __('Name', 'security-malware-firewall') . ': ' . $item['name'], - __('Table', 'security-malware-firewall') . ': ' . $item['table'], - __('Time', 'security-malware-firewall') . ': ' . $item['time'], - __('Action', 'security-malware-firewall') . ': ' . $item['action'] - ); - $table->items[$key]['code'] = $item['code']; - $table->items[$key]['signature'] = $item['signature']; - $table->items[$key]['analysis_status'] = $item['status']; - } + $table = DBTriggerView::prepareTableData($table); } function spbc_field_scanner__prepare_data__files_quarantine(&$table) @@ -3734,6 +3712,7 @@ function spbc_field_scanner__show_accordion($direct_call = false) 'quarantined' => __('Punished files.', 'security-malware-firewall'), 'analysis_log' => $analysis_log_description, 'cure_log' => $cure_log_description, + 'db_trigger' => DBTriggerView::getScannerTabDescription(), 'skipped' => __('List of files that were not checked by the scanner.', 'security-malware-firewall'), ); @@ -3751,10 +3730,6 @@ function spbc_field_scanner__show_accordion($direct_call = false) $tables_files['oscron'] = Scanner\OSCron\View\OSCronLocale::getInstance()->settings__accordion_tab_description; } - if ($spbc->settings['scanner__db_trigger_analysis']) { - $tables_files['db_trigger'] = __('DB Trigger Analysis', 'security-malware-firewall'); - } - if ($spbc->settings['scanner__list_approved_by_cleantalk']) { $company_name = $spbc->default_data['wl_company_name']; if ($spbc->data["wl_mode_enabled"]) { @@ -3860,6 +3835,7 @@ function spbc_field_scanner__show_accordion($direct_call = false) || ($type_name === 'frontend_malware' && $spbc->data['display_scanner_warnings']['frontend']) || ($type_name === 'analysis_log' && $spbc->data['display_scanner_warnings']['analysis']) || ($type_name === 'oscron' && $spbc->data['display_scanner_warnings']['oscron']) + || ($type_name === 'db_trigger' && $spbc->data['display_scanner_warnings']['db_triggers']) ) { $danger_dot = ''; } diff --git a/lib/CleantalkSP/SpbctWP/DB/SQLSchema.php b/lib/CleantalkSP/SpbctWP/DB/SQLSchema.php index 6748a7b2d..8fde7187f 100644 --- a/lib/CleantalkSP/SpbctWP/DB/SQLSchema.php +++ b/lib/CleantalkSP/SpbctWP/DB/SQLSchema.php @@ -187,7 +187,7 @@ class SQLSchema extends \CleantalkSP\Common\DB\SQLSchema array('field' => 'body', 'type' => 'text', 'null' => 'no',), array( 'field' => 'type', - 'type' => 'enum("FILE","CODE_PHP","CODE_HTML","CODE_JS","WAF_RULE")', + 'type' => 'enum("FILE","CODE_PHP","CODE_HTML","CODE_JS","WAF_RULE", "TRIGGER", "CRON")', 'null' => 'no', ), array( diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 15f50c9ca..e970dccb3 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -12,11 +12,11 @@ class DBTriggerModel public static function run() { try { - $db_triggers = self::getDBTriggers(); + $db_triggers = DBTriggerService::getDataBaseTriggers(); $bad_triggers = self::analyzeDBTriggers($db_triggers); - update_option('spbc_db_triggers', $bad_triggers); + DBTriggerService::saveTriggersStorage($bad_triggers); } catch (\Exception $error) { return $error->getMessage(); } @@ -24,27 +24,6 @@ public static function run() return true; } - /** - * Get all triggers from the database. - * - * @return array The list of triggers. - */ - private static function getDBTriggers() - { - global $wpdb; - - $db_triggers = $wpdb->get_results( - $wpdb->prepare( - "SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_STATEMENT, ACTION_TIMING - FROM information_schema.TRIGGERS - WHERE TRIGGER_SCHEMA = %s", - $wpdb->dbname - ) - ); - - return $db_triggers; - } - /** * Analyze the triggers and return the bad ones. * @@ -83,8 +62,7 @@ private static function analyzeDBTriggers($db_triggers) */ private static function isTriggerBad($trigger_code) { - $signatures = self::getSignatures(); - + $signatures = DBTriggerService::getSignaturesForTriggers(); foreach ($signatures as $signature_name => $signature) { if (preg_match('/' . $signature . '/is', $trigger_code)) { return $signature_name; @@ -93,12 +71,4 @@ private static function isTriggerBad($trigger_code) return false; } - - private static function getSignatures() - { - return [ - 'v1' => 'IF NEW.comment_content LIKE \'%.*?%\' THEN\s+SET @lastInsertWpUsersId = \(SELECT MAX\(id\) FROM.*\s+SET @nextWpUsersID = @lastInsertWpUsersId \+ 1;\s+INSERT INTO.*_users.*VALUES.*wpadmin.*wp-security@hotmail\.com', - 'v2' => 'INSERT INTO.*_users.*VALUES.*wpadmin.*wp-security@hotmail\.com.*\s+INSERT INTO.*administrator', - ]; - } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php new file mode 100644 index 000000000..7d5e458bd --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php @@ -0,0 +1,84 @@ +get_results( + $wpdb->prepare( + "SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_STATEMENT, ACTION_TIMING + FROM information_schema.TRIGGERS + WHERE TRIGGER_SCHEMA = %s", + $wpdb->dbname + ) + ); + + return is_array($db_triggers) ? $db_triggers : array(); + } + + /** + * Get cloud signatures for triggers from local database. + * @return array + */ + final public static function getSignaturesForTriggers() + { + global $wpdb; + $cloud_signatures = $wpdb->get_results( + $wpdb->prepare( + "SELECT name, body FROM " . SPBC_TBL_SCAN_SIGNATURES . " WHERE type = %s", + 'TRIGGER' + ), + ARRAY_N + ); + if (!is_array($cloud_signatures)) { + $cloud_signatures = array(); + } + foreach ($cloud_signatures as $key => $signature) { + $cloud_signatures[$signature[0]] = $signature[1]; + unset($cloud_signatures[$key]); + } + return $cloud_signatures; + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php new file mode 100644 index 000000000..2a49f3f2c --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php @@ -0,0 +1,52 @@ +items = DBTriggerService::loadTriggersStorage(); + $table->items_count = DBTriggerService::countTriggersStorage(); + $table->columns = [ + 'about_trigger' => array( 'heading' => 'About trigger', 'width_percent' => 30 ), // name, table, time, action + 'code' => array( 'heading' => 'Code', 'width_percent' => 45 ), + 'signature' => array( 'heading' => 'Signature', 'width_percent' => 10 ), + 'analysis_status' => array( 'heading' => 'Verdict', 'width_percent' => 15 ), + ]; + foreach ($table->items as $key => $item) { + $table->items[$key]['about_trigger'] = sprintf( + '%s
%s
%s
%s', + __('Name', 'security-malware-firewall') . ': ' . $item['name'], + __('Table', 'security-malware-firewall') . ': ' . $item['table'], + __('Time', 'security-malware-firewall') . ': ' . $item['time'], + __('Action', 'security-malware-firewall') . ': ' . $item['action'] + ); + $table->items[$key]['code'] = $item['code']; + $table->items[$key]['signature'] = $item['signature']; + $table->items[$key]['analysis_status'] = $item['status']; + } + return $table; + } + + final public static function getScannerTabDescription() + { + return '
' + . + __('This feature scans your WordPress database tables to find suspicious schema triggers that probably may cause malware infection.', 'security-malware-firewall') + . + '
'; + } + + final public static function getWarningTextForMalwareRemovalBanner() + { + return __("There's a high probability that your website has been compromised, as suspicious database triggers show signs of infection. Take action now by ordering malware removal from our experienced security specialists.", 'security-malware-firewall'); + } +} diff --git a/lib/CleantalkSP/SpbctWP/State.php b/lib/CleantalkSP/SpbctWP/State.php index b97905a15..ed413a6c5 100644 --- a/lib/CleantalkSP/SpbctWP/State.php +++ b/lib/CleantalkSP/SpbctWP/State.php @@ -203,6 +203,7 @@ class State extends \CleantalkSP\Common\State 'frontend' => false, 'analysis' => false, 'oscron' => false, + 'db_triggers' => false, 'warn_on_admin_bar' => false ), 'site_utc_offset_in_seconds' => 0, diff --git a/lib/CleantalkSP/Updater/UpdaterScripts.php b/lib/CleantalkSP/Updater/UpdaterScripts.php index 9830800c3..0e1050dea 100644 --- a/lib/CleantalkSP/Updater/UpdaterScripts.php +++ b/lib/CleantalkSP/Updater/UpdaterScripts.php @@ -1438,4 +1438,16 @@ public static function updateTo_2_149_1() //phpcs:ignore PSR1.Methods.CamelCapsM $spbc->save('data'); } + + /** + * @return void + */ + public static function updateTo_2_154_1() // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps + { + $sql_change_signatures_enum = + 'ALTER TABLE ' + . SPBC_TBL_SCAN_SIGNATURES + . " MODIFY COLUMN `type` ENUM('FILE','CODE_PHP','CODE_HTML','CODE_JS','WAF_RULE','TRIGGER','CRON') NOT NULL"; + DB::getInstance()->execute($sql_change_signatures_enum); + } } From d23c1c3436096a5a6d17c5fd23241c8627f8cb48 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Thu, 10 Apr 2025 18:24:27 +0500 Subject: [PATCH 04/18] Fix. Code. Removed redundant enqueue function. --- inc/spbc-admin.php | 269 ------------------ js/spbc-scanner-plugin.min.js | 2 +- js/spbc-scanner-plugin.min.js.map | 2 +- js/src/spbc-scanner-plugin.js | 1 + .../SpbctWP/Scanner/ScannerQueue.php | 1 + lib/CleantalkSP/SpbctWP/SpbcEnqueue.php | 4 + 6 files changed, 8 insertions(+), 271 deletions(-) diff --git a/inc/spbc-admin.php b/inc/spbc-admin.php index 703e29462..49eaec414 100644 --- a/inc/spbc-admin.php +++ b/inc/spbc-admin.php @@ -473,275 +473,6 @@ function changedPluginName(){ return $meta; } -/** - * Register stylesheet and scripts. - * @psalm-suppress InvalidArgument, UnusedParam - */ -function spbc_enqueue_scripts($hook) -{ - // If the user is not admin - if ( ! current_user_can('upload_files')) { - return; - } - - global $spbc; - - // For ALL admin pages - SpbcEnqueue::getInstance()->css('spbc-admin.css'); - SpbcEnqueue::getInstance()->css('spbc-icons.css'); - SpbcEnqueue::getInstance()->js('spbc-common.js', array('jquery')); - SpbcEnqueue::getInstance()->js('spbc-admin.js', array('jquery')); - SpbcEnqueue::getInstance()->js('spbc-react-bundle.js', array('wp-i18n'), ['in_footer']); - wp_set_script_translations('spbc-react-bundle-js', 'security-malware-firewall'); - - $vulnerability_show_install = ( - isset($spbc->settings['vulnerability_check__test_before_install']) && - $spbc->settings['vulnerability_check__test_before_install'] == true - ); - - $vulnerability_show_list = ( - isset($spbc->settings['vulnerability_check__enable_cron'], $spbc->settings['vulnerability_check__warn_on_modules_pages']) && - $spbc->settings['vulnerability_check__enable_cron'] == true && - $spbc->settings['vulnerability_check__warn_on_modules_pages'] == true - ); - wp_localize_script('spbc-common-js', 'spbcSettings', array( - 'wpms' => (int) is_multisite(), - 'is_main_site' => (int) is_main_site(), - 'img_path' => SPBC_PATH . '/images', - 'key_is_ok' => $spbc->key_is_ok, - 'critical' => $spbc->data['display_scanner_warnings']['critical'], - 'secfw_enabled' => $spbc->settings['secfw__enabled'], - 'ajax_nonce' => wp_create_nonce("spbc_secret_nonce"), - 'ajaxurl' => admin_url('admin-ajax.php', 'relative'), - //'debug' => !empty($debug) ? 1 : 0, - 'key_changed' => ! empty($spbc->data['key_changed']), - 'admin_bar__admins_online_counter' => $spbc->settings['admin_bar__admins_online_counter'] ? 1 : 0, - 'needToWhitelist' => ! Cookie::getString('spbc_secfw_ip_wl'), - 'frontendAnalysisAmount' => (defined('SPBCT_ALLOW_CURL_SINGLE') && SPBCT_ALLOW_CURL_SINGLE) ? 2 : 20, - 'spbctNoticeDismissSuccess' => \CleantalkSP\SpbctWP\AdminBannersModule\AdminBannersHandler::getJSONOfPostNotices(), - 'vulnerabilityShowInstall' => $vulnerability_show_install, - 'vulnerabilityShowList' => $vulnerability_show_list, - 'spbcSpinner' => array ( - 'imgSource' => SPBC_PATH . '/images/preloader2.gif', - 'altText' => esc_html__('Loading...', 'security-malware-firewall') - ), - 'wl_mode_enabled' => $spbc->data['wl_mode_enabled'], - 'wl_company_name' => $spbc->data['wl_company_name'], - 'wl_support_url' => $spbc->data['wl_support_url'], - 'default_wl_support_url' => $spbc->default_data['wl_support_url'], - )); - - SpbcEnqueue::getInstance()->js('spbc-cookie.js', array('jquery')); - - wp_localize_script( - 'spbc-cookie-js', - 'spbcPublic', - array ( - '_ajax_nonce' => wp_create_nonce('ct_secret_stuff'), - '_rest_nonce' => wp_create_nonce('wp_rest'), - '_ajax_url' => admin_url('admin-ajax.php', 'relative'), - '_rest_url' => esc_url(get_rest_url()), - // '_apbct_ajax_url' => APBCT_URL_PATH . '/lib/Cleantalk/ApbctWP/Ajax.php', - 'data__set_cookies' => $spbc->settings['data__set_cookies'], - 'data__set_cookies__alt_sessions_type' => $spbc->settings['data__set_cookies__alt_sessions_type'], - 'no_confirm_row_actions' => spbc_get_no_confirm_row_actions(), - ) - ); - - if ($spbc->settings['upload_checker__file_check'] && in_array($hook, array('upload.php', 'media-new.php'))) { - SpbcEnqueue::getInstance()->js('spbc-upload.js', array('jquery')); - } - - // Load UI (modal window) for profile pages - if ($hook === 'profile.php' || $hook === 'user-edit.php') { - SpbcEnqueue::getInstance()->custom( - 'jquery-ui', - SPBC_PATH . '/css/jquery-ui.min.css', - array(), - '1.12.1', - null, - 'all' - ); - wp_enqueue_script('jquery-ui-dialog'); - } - - // For settings page - if ($hook === 'settings_page_spbc') { - $button_template = ''; - - $button_template_send = sprintf($button_template, '', 'send', __('Send for analysys', 'security-malware-firewall')); - $button_template_delete = sprintf($button_template, '', 'delete', __('Delete', 'security-malware-firewall')); - $button_template_approve = sprintf($button_template, '', 'approve', __('Approve', 'security-malware-firewall')); - $button_template_view = sprintf($button_template, '', 'view', __('View', 'security-malware-firewall')); - $button_template_view_bad = sprintf($button_template, '', 'view_bad', __('View suspicious code', 'security-malware-firewall')); - $button_template_replace = sprintf($button_template, '', 'replace', __('Replace with original', 'security-malware-firewall')); - $button_template_compare = sprintf($button_template, '', 'compare', __('Show difference', 'security-malware-firewall')); - $actions_unknown = $button_template_send . $button_template_delete . $button_template_approve . $button_template_view; - $actions_modified = $button_template_approve . $button_template_replace . $button_template_compare . $button_template_view_bad; - - // CSS - SpbcEnqueue::getInstance()->css('spbc-settings.css'); - SpbcEnqueue::getInstance()->css('spbc-settings-media.css'); - SpbcEnqueue::getInstance()->css('spbc-table.css'); - wp_deregister_style('jquery-ui-style'); - SpbcEnqueue::getInstance()->custom( - 'jquery-ui', - SPBC_PATH . '/css/jquery-ui.min.css', - array(), - '1.12.1', - null, - 'all' - ); - // JS - SpbcEnqueue::getInstance()->custom( - 'jquery-ui', - SPBC_PATH . '/js/jquery-ui.min.js', - array('jquery'), - '1.13.1', - true, - null - ); - SpbcEnqueue::getInstance()->js('spbc-settings.js', array('jquery'), true); - SpbcEnqueue::getInstance()->js('spbc-table.js', array('jquery'), true); - SpbcEnqueue::getInstance()->js('spbc-scanner-plugin.js', array('jquery'), true); - - wp_localize_script('spbc-table-js', 'spbcTableLocalize', array( - 'scannerIsActive' => esc_html__('Scanner is active for now. Try later.', 'security-malware-firewall'), - )); - - SpbcEnqueue::getInstance()->js('spbc-modal.js', array('jquery'), true); - - wp_localize_script('spbc-settings-js', 'spbcSettingsSecLogs', array( - 'amount' => SPBC_LAST_ACTIONS_TO_VIEW, - 'clicks' => 0, - )); - - wp_localize_script('spbc-settings-js', 'spbcSettingsFWLogs', array( - 'moderate' => $spbc->moderate ? 1 : 0, - 'amount' => SPBC_LAST_ACTIONS_TO_VIEW, - 'clicks' => 0, - )); - - wp_localize_script('spbc-settings-js', 'spbcTable', array( - 'warning_bulk' => __('Are sure you want to perform these actions?', 'security-malware-firewall'), - - 'warning_default' => __('Do you want to proceed?', 'security-malware-firewall'), - - 'warning_h_approve' => __('Do you want to approve this file?', 'security-malware-firewall'), - 'warning_t_approve' => __('If you agree, this file will be approved.', 'security-malware-firewall'), - - 'warning_h_send' => __('Do you want to proceed?', 'security-malware-firewall'), - 'warning_t_send' => __('This file will be sent to the Cloud to analyze for a malware, usually processing takes up to 1 minute. The result will be shown in the Analysis log.', 'security-malware-firewall'), - - 'warning_h_delete' => __('This can\'t be undone and could damage your website. Are you sure?', 'security-malware-firewall'), - 'warning_t_delete' => __('If you agree, this file will be deleted.', 'security-malware-firewall'), - - 'warning_h_replace' => __('This can\'t be undone. Are you sure?', 'security-malware-firewall'), - 'warning_t_replace' => __('If you agree, this file will be replaced.', 'security-malware-firewall'), - - 'warning_h_quarantine' => __('This could damage your website, but you will have an option to restore the file.', 'security-malware-firewall'), - 'warning_t_quarantine' => __('If you agree, this file will be quarantined.', 'security-malware-firewall'), - )); - - // Getting scanner settings - $scanner_settings = array_filter( - (array) $spbc->settings, - function ($key) { - return strpos($key, 'scanner') === 0; - }, - ARRAY_FILTER_USE_KEY - ); - - wp_localize_script('spbc-settings-js', 'spbcScaner', array( - - // PARAMS - 'settings' => $scanner_settings, - 'states' => ScannerQueue::$stages, - 'timezone_shift' => $spbc->data['site_utc_offset_in_seconds'] ?: false, - - // Settings / Statuses - 'scaner_enabled' => $spbc->scaner_enabled ? 1 : 0, - 'check_links' => $spbc->settings['scanner__outbound_links'] ? 1 : 0, - 'check_heuristic' => $spbc->settings['scanner__heuristic_analysis'] ? 1 : 0, - 'check_signature' => $spbc->settings['scanner__signature_analysis'] ? 1 : 0, - 'auto_cure' => $spbc->settings['scanner__auto_cure'] ? 1 : 0, - 'check_frontend' => $spbc->settings['scanner__frontend_analysis'] ? 1 : 0, - 'check_listing' => $spbc->settings['scanner__important_files_listing'] ? 1 : 0, - 'wp_content_dir' => realpath(WP_CONTENT_DIR), - 'wp_root_dir' => realpath(ABSPATH), - - // Templates - 'row_template' => '%s%s%s%s%s', - 'row_template_links' => '%s%s%s', - 'actions_unknown' => $actions_unknown, - 'actions_modified' => $actions_modified, - 'page_selector_template' => '', - - //TRANSLATIONS - - //Confirmation - 'scan_modified_confiramation' => __('There is more than 30 modified files and this could take time. Do you want to proceed?', 'security-malware-firewall'), - 'warning_about_cancel' => __('Scan will be performed in the background mode soon.', 'security-malware-firewall'), - 'delete_warning' => __('Are you sure you want to delete the file? It can not be undone.'), - // Buttons - 'button_scan_perform' => __('Perform Scan', 'security-malware-firewall'), - 'button_scan_pause' => __('Pause scan', 'security-malware-firewall'), - 'button_scan_resume' => __('Resume scan', 'security-malware-firewall'), - // Progress bar - 'progressbar_get_cms_hashes' => __('Receiving hashes', 'security-malware-firewall'), - 'progressbar_get_modules_hashes' => __('Receiving plugins hashes', 'security-malware-firewall'), - 'progressbar_get_approved_hashes' => __('Updating statuses for the approved files', 'security-malware-firewall'), - 'progressbar_get_denied_hashes' => __('Updating statuses for the denied files', 'security-malware-firewall'), - 'progressbar_clean_results' => __('Preparing', 'security-malware-firewall'), - // Scanning core - 'progressbar_file_system_analysis' => __('Scanning for modifications', 'security-malware-firewall'), - 'progressbar_heuristic_analysis' => __('Heuristic analysis', 'security-malware-firewall'), - 'progressbar_schedule_send_heuristic_suspicious_files' => __('Schedule suspicious files to be automatically sent for analysis', 'security-malware-firewall'), - 'progressbar_signature_analysis' => __('Searching for signatures', 'security-malware-firewall'), - //Cure - 'progressbar_auto_cure_backup' => __('Creating a backup', 'security-malware-firewall'), - 'progressbar_auto_cure' => __('Cure', 'security-malware-firewall'), - // Links - 'progressbar_outbound_links' => __('Scanning links', 'security-malware-firewall'), - // Frontend - 'progressbar_frontend_analysis' => __('Scanning pages', 'security-malware-firewall'), - // Other - 'progressbar_important_files_listing' => __('Check pages for listing', 'security-malware-firewall'), - 'progressbar_send_results' => __('Sending results', 'security-malware-firewall'), - // Warnings - 'result_text_bad_template' => __('Recommend to scan all (%s) of the found files to make sure the website is secure.', 'security-malware-firewall'), - 'result_text_good_template' => __('No threats are found.', 'security-malware-firewall'), - //Misc - 'look_below_for_scan_res' => __('Look below for scan results.', 'security-malware-firewall'), - 'view_all_results' => sprintf( - __('
%sView all scan results for this website%s', 'security-malware-firewall'), - '', - '' - ), - 'last_scan_was_just_now' => __('The last scan of this website happened just now. Files scanned: %s.', 'security-malware-firewall'), - 'last_scan_was_just_now_links' => __('The last scan of this website happened just now. Files scanned: %s. Outbound links found: %s.', 'security-malware-firewall'), - 'copy_log_to_clipboard_hint' => __('Copied!', 'security-malware-firewall'), - 'copy_log_to_clipboard_hint_failed' => __('Failed to copy!', 'security-malware-firewall'), - 'copy_log_to_clipboard_hint_unsupported' => __('Clipboard API not supported in local environment', 'security-malware-firewall'), - )); - - wp_localize_script('spbc-settings-js', 'spbcDescriptions', array( - 'waf__enabled' => __('Bla bla', 'security-malware-firewall'), - 'waf__xss_check' => __('Cross-Site Scripting (XSS) — prevents malicious code to be executed/sent to any user. As a result malicious scripts can not get access to the cookie files, session tokens and any other confidential information browsers use and store. Such scripts can even overwrite content of HTML pages. CleanTalk WAF monitors for patterns of these parameters and block them.', 'security-malware-firewall'), - 'waf__sql_check' => __('SQL Injection — one of the most popular ways to hack websites and programs that work with databases. It is based on injection of a custom SQL code into database queries. It could transmit data through GET, POST requests or cookie files in an SQL code. If a website is vulnerable and execute such injections then it would allow attackers to apply changes to the website\'s MySQL database.', 'security-malware-firewall'), - 'upload_checker__file_check' => __('The option checks each uploaded file to a website for malicious code. If it\'s possible for visitors to upload files to a website, for instance a work resume, then attackers could abuse it and upload an infected file to execute it later and get access to your website.', 'security-malware-firewall'), - 'traffic_control__enabled' => __('It analyzes quantity of requests towards website from any IP address for a certain period of time. For example, for an ordinary visitor it\'s impossible to generate 2000 requests within 1 hour. Big amount of requests towards website from the same IP address indicates that there is a high chance of presence of a malicious program.', 'security-malware-firewall'), - 'scanner__outbound_links' => __('This option allows you to know the number of outgoing links on your website and website addresses they lead to. The option\'s purpose is to check your website and find hidden, forgotten and spam links. You should always remember if you have links to other websites which have a bad reputation, it could affect your visitors\' trust and your SEO.', 'security-malware-firewall'), - 'scanner__heuristic_analysis' => __('Often, authors of malicious code disguise their code which makes it difficult to identify it by their signatures. The malicious code itself can be placed anywhere on the site, for example the obfuscated PHP-code in the "logo.png" file, and the code itself is called by one inconspicuous line in "index.php". Therefore, the usage of plugins to search for malicious code is preferable. Heuristic analysis can indicate suspicious PHP constructions in a file that you should pay attention to.', 'security-malware-firewall'), - 'scanner__signature_analysis' => __('Code signatures — it\'s a code sequence a malicious program consists of. Signatures are being added to the database after analysis of the infected files. Search for such malicious code sequences is performed in scanning by signatures. If any part of code matches a virus code from the database, such files would be marked as critical.', 'security-malware-firewall'), - 'scanner__auto_cure' => __('It cures infected files automatically if the scanner knows cure methods for these specific cases. If the option is disabled then when the scanning process ends you will be presented with several actions you can do to the found files: Cure. Malicious code will be removed from the file. Replace. The file will be replaced with the original file. Delete. The file will be put in quarantine. Do nothing. Before any action is chosen, backups of the files will be created and if the cure is unsuccessful it\'s possible to restore each file.', 'security-malware-firewall'), - 'misc__backend_logs_enable' => __('To control appearing errors you have to check log file of your hosting account regularly. It\'s inconvenient and just a few webmasters pay attention to it. Also, errors could appear for a short period of time and only when one specific function is running, they can\'t be spotted in other circumstances so it\'s hard to catch them. PHP errors tell you that some of your website functionality doesn\'t work correctly, furthermore hackers may use these errors to get access to your website. The CleanTalk Scanner will check your website backend once per hour. Statistics of errors is available in your CleanTalk Dashboard.', 'security-malware-firewall'), - 'data__set_cookies' => __('Part of the CleanTalk FireWall functions depend on cookie files, so disabling this option could lead to deceleration of the firewall work. It will affect user identification who are logged in right now. Traffic Control will not be able to determine authorized users and they could be blocked when the request limit is reached. We do not recommend to disable this option without serious reasons. However, you should disable this option is you\'re using Varnish.', 'security-malware-firewall'), - '2fa__enable' => __('Two-Factor Authentication for WordPress admin accounts will improve your website security and make it safer, if not impossible, for hackers to breach your WordPress account. Two-Factor Authentication works via e-mail. Authentication code will be sent to your admin email. When authorizing, a one-time code will be sent to your email. While entering the code, make sure that it does not contain spaces. With your first authorization, the CleanTalk Security plugin remembers your browser and you won’t have to input your authorization code every time anymore. However, if you started to use a new device or a new browser then you are required to input your authorization code. The plugin will remember your browser for 30 days.', 'security-malware-firewall'), - )); - } -} - function spbc_admin_add_script_attribute($tag, $handle) { $async_scripts = array( diff --git a/js/spbc-scanner-plugin.min.js b/js/spbc-scanner-plugin.min.js index 4c672c6a1..363897337 100644 --- a/js/spbc-scanner-plugin.min.js +++ b/js/spbc-scanner-plugin.min.js @@ -1,2 +1,2 @@ -class SpbcMalwareScanner{first_start=!0;active=!1;root="";settings=[];states=["get_cms_hashes","get_modules_hashes","clean_results","file_system_analysis","get_approved_hashes","get_denied_hashes","signature_analysis","heuristic_analysis","schedule_send_heuristic_suspicious_files","auto_cure_backup","auto_cure","os_cron_analysis","outbound_links","frontend_analysis","important_files_listing","send_results"];state=null;offset=0;amount=0;amount_coefficient=1;total_scanned=0;scan_percent=0;percent_completed=0;paused=!1;button=null;spinner=null;progress_overall=null;progressbar=null;progressbar_text=null;timeout=6e4;state_timer=0;constructor(t){for(var e in console.log("init"),jQuery("#spbcscan-results-log-module").length&&jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),void 0!==t.settings.auto_cure&&(t.settings.scanner__auto_cure_backup="1"),t)void 0!==this[e]&&(this[e]=t[e])}actionControl(){null===this.state?this.start():this.paused?(this.resume(),this.controller()):this.pause()}start(){this.active=!0,this.state_timer=Math.round((new Date).getTime()/1e3),this.state=this.getNextState(null),this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold"),this.progressbar.show(500),this.progress_overall.show(500),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),setTimeout(()=>{this.controller()},1e3)}pause(t,e,s){console.log("PAUSE"),this.button.html(spbcScaner.button_scan_resume),this.spinner.css({display:"none"}),this.paused=!0,this.active=!1}resume(t){console.log("RESUME"),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),this.paused=!1,this.active=!0}end(t){this.progressbar.hide(500),this.progress_overall.hide(500),this.button.html(spbcScaner.button_scan_perform),this.spinner.css({display:"none"}),this.state=null,this.plug=!1,this.total_scanned=0,this.active=!1,t?document.location=document.location:(spbcSendAJAXRequest({action:"spbc_scanner_tab__reload_accordion"},{notJson:!0,callback:function(t,e,s,a){jQuery(a).accordion("destroy").html(t).accordion({header:"h3",heightStyle:"content",collapsible:!0,active:!1}),spbcTblBulkActionsListen(),spbcTblRowActionsListen(),spbcTblPaginationListen(),spbcTblSortListen(),spbcStartShowHide(),spbcScannerReloadScanInfo()}},jQuery("#spbc_scan_accordion")),jQuery("#spbc_scanner_clear").length||jQuery('
').insertBefore("#spbcscan-scanner-caption"))}controller(t){if(console.log(this.state),void 0!==t&&t.end){if(this.state=this.getNextState(this.state),void 0===this.state)return void this.end();this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold")}if(!0!==this.paused){var e={action:"spbc_scanner_controller_front",method:this.state,offset:this.offset},t={type:"GET",success:this.success,callback:this.successCallback,error:this.error,errorOutput:this.errorOutput,complete:null,context:this,timeout:12e4};switch(this.state){case"get_modules_hashes":this.amount=2;break;case"clear_table":this.amount=1e4;break;case"file_system_analysis":this.amount=700;break;case"auto_cure":this.amount=5;break;case"outbound_links":this.amount=10;break;case"frontend_analysis":this.amount=spbcSettings.frontendAnalysisAmount;break;case"signature_analysis":this.amount=10,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"heuristic_analysis":this.amount=4,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"schedule_send_heuristic_suspicious_files":this.amount=1}e.amount=Math.round(this.amount*this.amount_coefficient),spbcSendAJAXRequest(e,t,jQuery("#spbc_scan_accordion"))}}setCoefficients(t){let e=this.amount_coefficient;"file_system_analysis"===t&&(e*=1.5),this.amount_coefficient=e}getNextState(t){return t=null===t?this.states[0]:this.states[this.states.indexOf(t)+1],t=void 0!==this.settings["scanner__"+t]&&0==+this.settings["scanner__"+t]?this.getNextState(t):t}setPercents(t){this.percent_completed=Math.floor(100*t)/100,this.progressbar.progressbar("option","value",this.percent_completed),this.progressbar_text.text(spbcScaner["progressbar_"+this.state]+" - "+this.percent_completed+"%")}success(t){t.error?this.error({status:200,responseText:t.error},t.error,t.msg):this.successCallback&&this.successCallback(t,this.data,this.obj)}successCallback(e){if(console.log(e),this.interactAccordion(e),void 0!==e.total&&(this.scan_percent=100/e.total),void 0!==e.processed_items&&("heuristic_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Heuristic Analysis

'),"signature_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Signature Analysis

'),this.logFileEntry(e.processed_items)),void 0!==e.stage_data_for_logging&&this.logStageEntry(e.stage_data_for_logging),void 0!==e.cured&&0{this.controller(e)},300)}interactAccordion(t){t.hasOwnProperty("interactivity_data")&&t.interactivity_data.hasOwnProperty("update_text")&&t.interactivity_data.update_text&&t.interactivity_data.hasOwnProperty("refresh_data")&&t.interactivity_data.refresh_data.hasOwnProperty("do_refresh")&&t.interactivity_data.refresh_data.do_refresh&&t.interactivity_data.refresh_data.hasOwnProperty("control_tab")&&t.interactivity_data.refresh_data.control_tab&&spbcReloadAccordion(t.interactivity_data.refresh_data.control_tab,t.interactivity_data.update_text)}error(t,e,s){var a=this.errorOutput;if(console.log("%c APBCT_AJAX_ERROR","color: red;"),console.log(e),console.log(s),console.log(t),"error"==e&&(""==s||"Not found"==s)&&(this.tryCount||(this.tryCount=0,this.retryLimit=30),this.tryCount++,console.log("Try #"+this.tryCount),this.setCoefficients(this.state),this.tryCount<=this.retryLimit))this.pause(),this.resume(),this.controller();else{if(200===t.status)if("parsererror"===e)a("Unexpected response from server. See console for details.",this.state),console.log("%c "+t.responseText,"color: pink;");else{let t=e;void 0!==s&&(t+=" Additional info: "+s),a(t,this.state)}else 500===t.status?a("Internal server error.",this.state):a("Unexpected response code: "+t.status+". Error: "+e,this.state);this.progressbar&&this.progressbar.fadeOut("slow"),this.end()}}errorOutput(t,e){spbcModal.open().putError(t+"
Stage: "+e)}logRaw(t){jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper .panel-body").prepend(t)}logFileEntry(t){for(var e in t)e&&this.logRaw('

'+this.getSiteUTCShiftedTimeString()+" - "+t[e].path+" - "+t[e].module+": "+t[e].status+"

")}logStageEntry(t){void 0!==jQuery(".panel-body .spbc_log-line span").first()&&void 0!==jQuery(".panel-body .spbc_log-line span").first()[0]&&jQuery(".panel-body .spbc_log-line span").first()[0].textContent===t.description||this.logRaw('

test '+this.getSiteUTCShiftedTimeString()+" - "+t.title+" "+t.description+"

")}showLinkForShuffleSalts(t){jQuery("#spbc_notice_about_shuffle_link").remove(),jQuery(jQuery(".spbc_tab--active .spbc_wrapper_field p")[1]).after('")}getSiteUTCShiftedTimeString(){let t=!1;var e=-1*(new Date).getTimezoneOffset()*1e3*60,e=(t="undefined"!=typeof spbcScaner&&void 0!==spbcScaner.timezone_shift&&!1!==spbcScaner.timezone_shift?Date.now()-e+1e3*spbcScaner.timezone_shift:t)?new Date(t):new Date,s=new Intl.DateTimeFormat("en-US",{month:"short"}).format,a=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return s(e)+" "+e.getDate()+" "+e.getFullYear()+" "+e.getHours()+":"+a+":"+i}} +class SpbcMalwareScanner{first_start=!0;active=!1;root="";settings=[];states=["get_cms_hashes","get_modules_hashes","clean_results","file_system_analysis","get_approved_hashes","get_denied_hashes","signature_analysis","heuristic_analysis","schedule_send_heuristic_suspicious_files","auto_cure_backup","auto_cure","os_cron_analysis","db_trigger_analysis","outbound_links","frontend_analysis","important_files_listing","send_results"];state=null;offset=0;amount=0;amount_coefficient=1;total_scanned=0;scan_percent=0;percent_completed=0;paused=!1;button=null;spinner=null;progress_overall=null;progressbar=null;progressbar_text=null;timeout=6e4;state_timer=0;constructor(t){for(var e in console.log("init"),jQuery("#spbcscan-results-log-module").length&&jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),void 0!==t.settings.auto_cure&&(t.settings.scanner__auto_cure_backup="1"),t)void 0!==this[e]&&(this[e]=t[e])}actionControl(){null===this.state?this.start():this.paused?(this.resume(),this.controller()):this.pause()}start(){this.active=!0,this.state_timer=Math.round((new Date).getTime()/1e3),this.state=this.getNextState(null),this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold"),this.progressbar.show(500),this.progress_overall.show(500),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),setTimeout(()=>{this.controller()},1e3)}pause(t,e,s){console.log("PAUSE"),this.button.html(spbcScaner.button_scan_resume),this.spinner.css({display:"none"}),this.paused=!0,this.active=!1}resume(t){console.log("RESUME"),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),this.paused=!1,this.active=!0}end(t){this.progressbar.hide(500),this.progress_overall.hide(500),this.button.html(spbcScaner.button_scan_perform),this.spinner.css({display:"none"}),this.state=null,this.plug=!1,this.total_scanned=0,this.active=!1,t?document.location=document.location:(spbcSendAJAXRequest({action:"spbc_scanner_tab__reload_accordion"},{notJson:!0,callback:function(t,e,s,a){jQuery(a).accordion("destroy").html(t).accordion({header:"h3",heightStyle:"content",collapsible:!0,active:!1}),spbcTblBulkActionsListen(),spbcTblRowActionsListen(),spbcTblPaginationListen(),spbcTblSortListen(),spbcStartShowHide(),spbcScannerReloadScanInfo()}},jQuery("#spbc_scan_accordion")),jQuery("#spbc_scanner_clear").length||jQuery('
').insertBefore("#spbcscan-scanner-caption"))}controller(t){if(console.log(this.state),void 0!==t&&t.end){if(this.state=this.getNextState(this.state),void 0===this.state)return void this.end();this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold")}if(!0!==this.paused){var e={action:"spbc_scanner_controller_front",method:this.state,offset:this.offset},t={type:"GET",success:this.success,callback:this.successCallback,error:this.error,errorOutput:this.errorOutput,complete:null,context:this,timeout:12e4};switch(this.state){case"get_modules_hashes":this.amount=2;break;case"clear_table":this.amount=1e4;break;case"file_system_analysis":this.amount=700;break;case"auto_cure":this.amount=5;break;case"outbound_links":this.amount=10;break;case"frontend_analysis":this.amount=spbcSettings.frontendAnalysisAmount;break;case"signature_analysis":this.amount=10,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"heuristic_analysis":this.amount=4,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"schedule_send_heuristic_suspicious_files":this.amount=1}e.amount=Math.round(this.amount*this.amount_coefficient),spbcSendAJAXRequest(e,t,jQuery("#spbc_scan_accordion"))}}setCoefficients(t){let e=this.amount_coefficient;"file_system_analysis"===t&&(e*=1.5),this.amount_coefficient=e}getNextState(t){return t=null===t?this.states[0]:this.states[this.states.indexOf(t)+1],t=void 0!==this.settings["scanner__"+t]&&0==+this.settings["scanner__"+t]?this.getNextState(t):t}setPercents(t){this.percent_completed=Math.floor(100*t)/100,this.progressbar.progressbar("option","value",this.percent_completed),this.progressbar_text.text(spbcScaner["progressbar_"+this.state]+" - "+this.percent_completed+"%")}success(t){t.error?this.error({status:200,responseText:t.error},t.error,t.msg):this.successCallback&&this.successCallback(t,this.data,this.obj)}successCallback(e){if(console.log(e),this.interactAccordion(e),void 0!==e.total&&(this.scan_percent=100/e.total),void 0!==e.processed_items&&("heuristic_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Heuristic Analysis

'),"signature_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Signature Analysis

'),this.logFileEntry(e.processed_items)),void 0!==e.stage_data_for_logging&&this.logStageEntry(e.stage_data_for_logging),void 0!==e.cured&&0{this.controller(e)},300)}interactAccordion(t){t.hasOwnProperty("interactivity_data")&&t.interactivity_data.hasOwnProperty("update_text")&&t.interactivity_data.update_text&&t.interactivity_data.hasOwnProperty("refresh_data")&&t.interactivity_data.refresh_data.hasOwnProperty("do_refresh")&&t.interactivity_data.refresh_data.do_refresh&&t.interactivity_data.refresh_data.hasOwnProperty("control_tab")&&t.interactivity_data.refresh_data.control_tab&&spbcReloadAccordion(t.interactivity_data.refresh_data.control_tab,t.interactivity_data.update_text)}error(t,e,s){var a=this.errorOutput;if(console.log("%c APBCT_AJAX_ERROR","color: red;"),console.log(e),console.log(s),console.log(t),"error"==e&&(""==s||"Not found"==s)&&(this.tryCount||(this.tryCount=0,this.retryLimit=30),this.tryCount++,console.log("Try #"+this.tryCount),this.setCoefficients(this.state),this.tryCount<=this.retryLimit))this.pause(),this.resume(),this.controller();else{if(200===t.status)if("parsererror"===e)a("Unexpected response from server. See console for details.",this.state),console.log("%c "+t.responseText,"color: pink;");else{let t=e;void 0!==s&&(t+=" Additional info: "+s),a(t,this.state)}else 500===t.status?a("Internal server error.",this.state):a("Unexpected response code: "+t.status+". Error: "+e,this.state);this.progressbar&&this.progressbar.fadeOut("slow"),this.end()}}errorOutput(t,e){spbcModal.open().putError(t+"
Stage: "+e)}logRaw(t){jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper .panel-body").prepend(t)}logFileEntry(t){for(var e in t)e&&this.logRaw('

'+this.getSiteUTCShiftedTimeString()+" - "+t[e].path+" - "+t[e].module+": "+t[e].status+"

")}logStageEntry(t){void 0!==jQuery(".panel-body .spbc_log-line span").first()&&void 0!==jQuery(".panel-body .spbc_log-line span").first()[0]&&jQuery(".panel-body .spbc_log-line span").first()[0].textContent===t.description||this.logRaw('

test '+this.getSiteUTCShiftedTimeString()+" - "+t.title+" "+t.description+"

")}showLinkForShuffleSalts(t){jQuery("#spbc_notice_about_shuffle_link").remove(),jQuery(jQuery(".spbc_tab--active .spbc_wrapper_field p")[1]).after('")}getSiteUTCShiftedTimeString(){let t=!1;var e=-1*(new Date).getTimezoneOffset()*1e3*60,e=(t="undefined"!=typeof spbcScaner&&void 0!==spbcScaner.timezone_shift&&!1!==spbcScaner.timezone_shift?Date.now()-e+1e3*spbcScaner.timezone_shift:t)?new Date(t):new Date,s=new Intl.DateTimeFormat("en-US",{month:"short"}).format,a=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return s(e)+" "+e.getDate()+" "+e.getFullYear()+" "+e.getHours()+":"+a+":"+i}} //# sourceMappingURL=spbc-scanner-plugin.min.js.map diff --git a/js/spbc-scanner-plugin.min.js.map b/js/spbc-scanner-plugin.min.js.map index 6ea56f56e..b0480d9a8 100644 --- a/js/spbc-scanner-plugin.min.js.map +++ b/js/spbc-scanner-plugin.min.js.map @@ -1 +1 @@ -{"version":3,"file":"spbc-scanner-plugin.min.js","sources":["spbc-scanner-plugin.js"],"sourcesContent":["'use strict';\n\n/**\n * class SpbcMalwareScanner\n */\nclass SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */\n first_start = true;\n\n active = false;\n\n root = '';\n settings = [];\n states = [\n 'get_cms_hashes',\n 'get_modules_hashes',\n 'clean_results',\n 'file_system_analysis',\n 'get_approved_hashes',\n 'get_denied_hashes',\n 'signature_analysis',\n 'heuristic_analysis',\n 'schedule_send_heuristic_suspicious_files',\n 'auto_cure_backup',\n 'auto_cure',\n 'os_cron_analysis',\n 'outbound_links',\n 'frontend_analysis',\n 'important_files_listing',\n 'send_results',\n ];\n state = null;\n offset = 0;\n amount = 0;\n amount_coefficient = 1;\n total_scanned = 0;\n scan_percent = 0;\n percent_completed = 0;\n\n paused = false;\n\n button = null;\n spinner = null;\n\n progress_overall = null;\n progressbar = null;\n progressbar_text = null;\n\n timeout = 60000;\n\n state_timer = 0;\n\n /**\n * constructor\n * @param {array} properties\n */\n constructor( properties ) {\n console.log('init');\n if (jQuery('#spbcscan-results-log-module').length) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n }\n\n // Crunch for cure backups\n if ( typeof properties['settings']['auto_cure'] !== 'undefined' ) {\n properties['settings']['scanner__auto_cure_backup'] = '1';\n }\n\n for ( let key in properties ) {\n if ( typeof this[key] !== 'undefined' ) {\n this[key] = properties[key];\n }\n }\n };\n\n /**\n * Function Action Control\n */\n actionControl() {\n if (this.state === null) {\n this.start();\n } else if (this.paused) {\n this.resume();\n this.controller();\n } else {\n this.pause();\n }\n };\n\n /**\n * Function Start\n */\n start() {\n this.active = true;\n this.state_timer = Math.round(new Date().getTime() /1000);\n\n this.state = this.getNextState( null );\n\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n\n this.progressbar.show(500);\n this.progress_overall.show(500);\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n\n setTimeout(() => {\n this.controller();\n }, 1000);\n };\n\n /**\n * Function Pause\n * @param {*} result\n * @param {*} data\n * @param {*} opt\n */\n pause( result, data, opt ) {\n console.log('PAUSE');\n this.button.html(spbcScaner.button_scan_resume);\n this.spinner.css({display: 'none'});\n this.paused = true;\n this.active = false;\n };\n\n /**\n * Function Resume\n * @param {*} opt\n */\n resume( opt ) {\n console.log('RESUME');\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n this.paused = false;\n this.active = true;\n };\n\n /**\n * Function End\n * @param {bool} reload\n */\n end( reload ) {\n this.progressbar.hide(500);\n this.progress_overall.hide(500);\n this.button.html(spbcScaner.button_scan_perform);\n this.spinner.css({display: 'none'});\n this.state = null;\n this.plug = false;\n this.total_scanned = 0;\n this.active = false;\n\n if (reload) {\n document.location = document.location;\n } else {\n spbcSendAJAXRequest(\n {action: 'spbc_scanner_tab__reload_accordion'},\n {\n notJson: true,\n callback: function(result, data, params, obj) {\n jQuery(obj).accordion('destroy')\n .html(result)\n .accordion({\n header: 'h3',\n heightStyle: 'content',\n collapsible: true,\n active: false,\n });\n spbcTblBulkActionsListen();\n spbcTblRowActionsListen();\n spbcTblPaginationListen();\n spbcTblSortListen();\n spbcStartShowHide();\n spbcScannerReloadScanInfo();\n },\n },\n jQuery('#spbc_scan_accordion'),\n );\n\n if (!jQuery('#spbc_scanner_clear').length) {\n let clearLink = '

Clear scanner logs


';\n jQuery(clearLink).insertBefore('#spbcscan-scanner-caption');\n }\n }\n };\n\n /**\n * Function Controller\n * @param {obj} result\n */\n controller( result ) {\n console.log(this.state);\n\n // The current stage is over. Switching to the new one\n if ( typeof result !== 'undefined' && result.end ) {\n this.state = this.getNextState( this.state );\n\n // End condition\n if (typeof this.state === 'undefined') {\n this.end();\n return;\n }\n\n // Set percent to 0\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n\n // Changing visualizing of the current stage\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n }\n\n // Break execution if paused\n if ( this.paused === true ) {\n return;\n }\n\n // // AJAX params\n let data = {\n action: 'spbc_scanner_controller_front',\n method: this.state,\n offset: this.offset,\n };\n\n let params = {\n type: 'GET',\n success: this.success,\n callback: this.successCallback,\n error: this.error,\n errorOutput: this.errorOutput,\n complete: null,\n context: this,\n timeout: 120000,\n };\n\n switch (this.state) {\n case 'get_modules_hashes': this.amount = 2; break;\n case 'clear_table': this.amount = 10000; break;\n case 'file_system_analysis': this.amount = 700; break;\n case 'auto_cure': this.amount = 5; break;\n case 'outbound_links': this.amount = 10; break;\n case 'frontend_analysis': this.amount = spbcSettings.frontendAnalysisAmount; break;\n case 'signature_analysis': this.amount = 10; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'heuristic_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'schedule_send_heuristic_suspicious_files': this.amount = 1; break;\n }\n\n data.amount = Math.round(this.amount * this.amount_coefficient);\n\n spbcSendAJAXRequest(\n data,\n params,\n jQuery('#spbc_scan_accordion'),\n );\n };\n\n /**\n * Set Coefficients\n * @param {string} state\n */\n setCoefficients( state ) {\n let coefficient = this.amount_coefficient;\n switch (state) {\n case 'file_system_analysis': coefficient *= 1.5; break;\n }\n this.amount_coefficient = coefficient;\n };\n\n /**\n * Get Next State\n * @param {string} state\n * @return {number}\n */\n getNextState( state ) {\n state = state === null ? this.states[0] : this.states[this.states.indexOf( state ) + 1];\n\n if (typeof this.settings['scanner__' + state] !== 'undefined' && +this.settings['scanner__' + state] === 0) {\n state = this.getNextState( state );\n }\n\n return state;\n };\n\n /**\n * Set Percents\n * @param {number} percents\n */\n setPercents( percents ) {\n this.percent_completed = Math.floor( percents * 100 ) / 100;\n this.progressbar.progressbar( 'option', 'value', this.percent_completed );\n this.progressbar_text.text( spbcScaner['progressbar_' + this.state] + ' - ' + this.percent_completed + '%' );\n };\n\n /**\n * Function Success\n * @param {obj} response\n */\n success( response ) {\n if ( !! response.error ) {\n this.error(\n {status: 200, responseText: response.error},\n response.error,\n response.msg,\n );\n } else {\n if ( this.successCallback ) {\n this.successCallback( response, this.data, this.obj );\n }\n }\n };\n\n // Processing response from backend\n /**\n * Success Callback\n * @param {obj} result\n */\n successCallback( result ) {\n console.log( result );\n\n this.interactAccordion(result);\n\n if ( typeof result.total !== 'undefined' ) {\n this.scan_percent = 100 / result.total;\n }\n\n if ( typeof result.processed_items !== 'undefined') {\n if ( this.state === 'heuristic_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Heuristic Analysis

');\n }\n if ( this.state === 'signature_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Signature Analysis

');\n }\n\n this.logFileEntry( result.processed_items );\n }\n\n if ( typeof result.stage_data_for_logging !== 'undefined') {\n this.logStageEntry( result.stage_data_for_logging );\n }\n\n // Add link on shuffle salt if cured\n if (result.cured !== undefined && Number(result.cured) > 0) {\n this.showLinkForShuffleSalts(result.message);\n }\n\n if ( result.end !== true && result.end !== 1 ) {\n let processedPercents = this.percent_completed + result.processed * this.scan_percent;\n if (result.stage_data_for_logging.title === 'File System Analysis' && processedPercents > 100) {\n processedPercents = 100;\n }\n this.setPercents(processedPercents);\n this.offset = this.offset + result.processed;\n this.controller( result );\n } else {\n console.log( this.state +\n ' stage took ' +\n ( Math.round(new Date().getTime() /1000) - this.state_timer ) +\n ' seconds to complete' );\n this.state_timer = Math.round(new Date().getTime()/1000);\n this.setPercents( 100 );\n this.scan_percent = 0;\n this.offset = 0;\n setTimeout(() => {\n this.controller( result );\n }, 300);\n }\n };\n\n /**\n * Run interactive refresh for accordion.\n * @param {obj|string[]} result\n */\n interactAccordion(result) {\n // validation control\n if (result.hasOwnProperty('interactivity_data') &&\n result.interactivity_data.hasOwnProperty('update_text') &&\n result.interactivity_data.update_text &&\n result.interactivity_data.hasOwnProperty('refresh_data') &&\n result.interactivity_data.refresh_data.hasOwnProperty('do_refresh') &&\n result.interactivity_data.refresh_data.do_refresh &&\n result.interactivity_data.refresh_data.hasOwnProperty('control_tab') &&\n result.interactivity_data.refresh_data.control_tab\n ) {\n spbcReloadAccordion(\n result.interactivity_data.refresh_data.control_tab,\n result.interactivity_data.update_text,\n );\n }\n }\n\n /**\n * Function Error\n * @param {object} xhr\n * @param {string} status\n * @param {string} error\n */\n error( xhr, status, error ) {\n let errorOutput = this.errorOutput;\n\n console.log( '%c APBCT_AJAX_ERROR', 'color: red;' );\n console.log( status );\n console.log( error );\n console.log( xhr );\n\n if (status == 'error' && (error == '' || error == 'Not found')) {\n if (!this.tryCount) {\n this.tryCount = 0;\n this.retryLimit = 30;\n }\n this.tryCount++;\n console.log('Try #' + this.tryCount);\n this.setCoefficients(this.state);\n if (this.tryCount <= this.retryLimit) {\n this.pause();\n this.resume();\n this.controller();\n return;\n }\n }\n\n if ( xhr.status === 200 ) {\n if ( status === 'parsererror' ) {\n errorOutput( 'Unexpected response from server. See console for details.', this.state );\n console.log( '%c ' + xhr.responseText, 'color: pink;' );\n } else {\n let errorString = status;\n if ( typeof error !== 'undefined' ) {\n errorString += ' Additional info: ' + error;\n }\n errorOutput( errorString, this.state );\n }\n } else if (xhr.status === 500) {\n errorOutput( 'Internal server error.', this.state);\n } else {\n errorOutput('Unexpected response code: ' + xhr.status + '. Error: ' + status, this.state);\n }\n\n if ( this.progressbar ) {\n this.progressbar.fadeOut('slow');\n }\n\n this.end();\n };\n\n /**\n * Error Output\n * @param {string} errorMsg\n * @param {string} stage\n */\n errorOutput( errorMsg, stage ) {\n spbcModal.open().putError( errorMsg + '
Stage: ' + stage);\n };\n\n /**\n * Log Raw\n * @param {htmlString|Element|Text|Array|jQuery} messageToLog\n */\n logRaw(messageToLog) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper .panel-body').prepend( messageToLog );\n };\n\n /**\n * Log File Entry\n * @param {array} items\n */\n logFileEntry(items) {\n for ( let key in items ) {\n if ( key ) {\n this.logRaw(\n '

' +\n this.getSiteUTCShiftedTimeString() + ' - ' +\n items[key].path + ' - ' + items[key].module +\n ': ' + items[key].status + '' +\n '

');\n }\n }\n };\n\n /**\n * Log Stage Entry\n * @param {obj} data\n */\n logStageEntry(data) {\n if (typeof jQuery('.panel-body .spbc_log-line span').first() !== 'undefined' &&\n typeof jQuery('.panel-body .spbc_log-line span').first()[0] !== 'undefined' &&\n jQuery('.panel-body .spbc_log-line span').first()[0].textContent === data.description\n ) {\n return;\n }\n this.logRaw( '

test ' +\n this.getSiteUTCShiftedTimeString() + ' - ' + '' +\n data.title + ' ' + '' + data.description + '

' );\n };\n\n /**\n * Show Link For Shuffle Salts\n * @param {string} message\n */\n showLinkForShuffleSalts(message) {\n jQuery('#spbc_notice_about_shuffle_link').remove();\n jQuery(jQuery('.spbc_tab--active .spbc_wrapper_field p')[1])\n .after(\n '
' +\n '' +\n message +\n '' +\n '
',\n );\n }\n\n /**\n * Get Site UTC Shifted Time String\n * @return {string}\n */\n getSiteUTCShiftedTimeString() {\n let utcShiftedTs = false;\n // gettings current system/browser offset\n let currentBrowserOffset = new Date().getTimezoneOffset();\n currentBrowserOffset = currentBrowserOffset * -1 * 1000 * 60;\n // chek if global ct object is defined\n if (typeof spbcScaner !== 'undefined' &&\n typeof spbcScaner.timezone_shift !== 'undefined' &&\n spbcScaner.timezone_shift !== false) {\n utcShiftedTs = Date.now() - currentBrowserOffset + (spbcScaner.timezone_shift * 1000);\n }\n let ctDate = utcShiftedTs ? new Date(utcShiftedTs) : new Date();\n // construct date string\n let shortMonthName = new Intl.DateTimeFormat('en-US', {month: 'short'}).format;\n let minutes = String(ctDate.getMinutes()).padStart(2, '0');\n let seconds = String(ctDate.getSeconds()).padStart(2, '0');\n return shortMonthName(ctDate) + ' ' +\n ctDate.getDate() + ' ' + ctDate.getFullYear() + ' ' +\n ctDate.getHours() + ':' + minutes + ':' + seconds;\n }\n}\n"],"names":["SpbcMalwareScanner","first_start","active","root","settings","states","state","offset","amount","amount_coefficient","total_scanned","scan_percent","percent_completed","paused","button","spinner","progress_overall","progressbar","progressbar_text","timeout","state_timer","constructor","properties","let","key","console","log","jQuery","length","removeClass","this","actionControl","start","resume","controller","pause","Math","round","Date","getTime","getNextState","setPercents","children","filter","addClass","show","html","spbcScaner","button_scan_pause","css","display","setTimeout","result","data","opt","button_scan_resume","end","reload","hide","button_scan_perform","plug","document","location","spbcSendAJAXRequest","action","notJson","callback","params","obj","accordion","header","heightStyle","collapsible","spbcTblBulkActionsListen","spbcTblRowActionsListen","spbcTblPaginationListen","spbcTblSortListen","spbcStartShowHide","spbcScannerReloadScanInfo","insertBefore","method","type","success","successCallback","error","errorOutput","complete","context","spbcSettings","frontendAnalysisAmount","status","setCoefficients","coefficient","indexOf","percents","floor","text","response","responseText","msg","interactAccordion","total","processed_items","logRaw","logFileEntry","stage_data_for_logging","logStageEntry","undefined","cured","Number","showLinkForShuffleSalts","message","processedPercents","processed","title","hasOwnProperty","interactivity_data","update_text","refresh_data","do_refresh","control_tab","spbcReloadAccordion","xhr","tryCount","retryLimit","errorString","fadeOut","errorMsg","stage","spbcModal","open","putError","messageToLog","prepend","items","getSiteUTCShiftedTimeString","path","module","first","textContent","description","remove","after","utcShiftedTs","currentBrowserOffset","getTimezoneOffset","ctDate","timezone_shift","now","shortMonthName","Intl","DateTimeFormat","month","format","minutes","String","getMinutes","padStart","seconds","getSeconds","getDate","getFullYear","getHours"],"mappings":"MAKMA,mBACFC,YAAc,CAAA,EAEdC,OAAS,CAAA,EAETC,KAAO,GACPC,SAAW,GACXC,OAAS,CACL,iBACA,qBACA,gBACA,uBACA,sBACA,oBACA,qBACA,qBACA,2CACA,mBACA,YACA,mBACA,iBACA,oBACA,0BACA,gBAEJC,MAAQ,KACRC,OAAS,EACTC,OAAS,EACTC,mBAAqB,EACrBC,cAAgB,EAChBC,aAAe,EACfC,kBAAoB,EAEpBC,OAAS,CAAA,EAETC,OAAS,KACTC,QAAU,KAEVC,iBAAmB,KACnBC,YAAc,KACdC,iBAAmB,KAEnBC,QAAU,IAEVC,YAAc,EAMdC,YAAaC,GAWT,IAAMC,IAAIC,KAVVC,QAAQC,IAAI,MAAM,EACdC,OAAO,8BAA8B,EAAEC,QACvCD,OAAO,sBAAsB,EAAEE,YAAY,eAAe,EAIV,KAAA,IAAxCP,EAAqB,SAAa,YAC1CA,EAAqB,SAA6B,0BAAI,KAGzCA,EACa,KAAA,IAAdQ,KAAKN,KACbM,KAAKN,GAAOF,EAAWE,GAGnC,CAKAO,gBACuB,OAAfD,KAAKxB,MACLwB,KAAKE,MAAM,EACJF,KAAKjB,QACZiB,KAAKG,OAAO,EACZH,KAAKI,WAAW,GAEhBJ,KAAKK,MAAM,CAEnB,CAKAH,QACIF,KAAK5B,OAAS,CAAA,EACd4B,KAAKV,YAAcgB,KAAKC,OAAM,IAAIC,MAAOC,QAAQ,EAAG,GAAI,EAExDT,KAAKxB,MAAQwB,KAAKU,aAAc,IAAK,EAErCV,KAAKW,YAAa,CAAE,EACpBX,KAAKnB,aAAe,EACpBmB,KAAKvB,OAAS,EACduB,KAAKd,iBAAiB0B,SAAS,MAAM,EAChCb,YAAY,WAAW,EACvBc,OAAO,6BAA+Bb,KAAKxB,KAAK,EAChDsC,SAAS,WAAW,EAEzBd,KAAKb,YAAY4B,KAAK,GAAG,EACzBf,KAAKd,iBAAiB6B,KAAK,GAAG,EAC9Bf,KAAKhB,OAAOgC,KAAKC,WAAWC,iBAAiB,EAC7ClB,KAAKf,QAAQkC,IAAI,CAACC,QAAS,QAAQ,CAAC,EAEpCC,WAAW,KACPrB,KAAKI,WAAW,CACpB,EAAG,GAAI,CACX,CAQAC,MAAOiB,EAAQC,EAAMC,GACjB7B,QAAQC,IAAI,OAAO,EACnBI,KAAKhB,OAAOgC,KAAKC,WAAWQ,kBAAkB,EAC9CzB,KAAKf,QAAQkC,IAAI,CAACC,QAAS,MAAM,CAAC,EAClCpB,KAAKjB,OAAS,CAAA,EACdiB,KAAK5B,OAAS,CAAA,CAClB,CAMA+B,OAAQqB,GACJ7B,QAAQC,IAAI,QAAQ,EACpBI,KAAKhB,OAAOgC,KAAKC,WAAWC,iBAAiB,EAC7ClB,KAAKf,QAAQkC,IAAI,CAACC,QAAS,QAAQ,CAAC,EACpCpB,KAAKjB,OAAS,CAAA,EACdiB,KAAK5B,OAAS,CAAA,CAClB,CAMAsD,IAAKC,GACD3B,KAAKb,YAAYyC,KAAK,GAAG,EACzB5B,KAAKd,iBAAiB0C,KAAK,GAAG,EAC9B5B,KAAKhB,OAAOgC,KAAKC,WAAWY,mBAAmB,EAC/C7B,KAAKf,QAAQkC,IAAI,CAACC,QAAS,MAAM,CAAC,EAClCpB,KAAKxB,MAAQ,KACbwB,KAAK8B,KAAO,CAAA,EACZ9B,KAAKpB,cAAgB,EACrBoB,KAAK5B,OAAS,CAAA,EAEVuD,EACAI,SAASC,SAAWD,SAASC,UAE7BC,oBACI,CAACC,OAAQ,oCAAoC,EAC7C,CACIC,QAAS,CAAA,EACTC,SAAU,SAASd,EAAQC,EAAMc,EAAQC,GACrCzC,OAAOyC,CAAG,EAAEC,UAAU,SAAS,EAC1BvB,KAAKM,CAAM,EACXiB,UAAU,CACPC,OAAQ,KACRC,YAAa,UACbC,YAAa,CAAA,EACbtE,OAAQ,CAAA,CACZ,CAAC,EACLuE,yBAAyB,EACzBC,wBAAwB,EACxBC,wBAAwB,EACxBC,kBAAkB,EAClBC,kBAAkB,EAClBC,0BAA0B,CAC9B,CACJ,EACAnD,OAAO,sBAAsB,CACjC,EAEKA,OAAO,qBAAqB,EAAEC,QAI/BD,OAHgB,wLAGA,EAAEoD,aAAa,2BAA2B,EAGtE,CAMA7C,WAAYkB,GAIR,GAHA3B,QAAQC,IAAII,KAAKxB,KAAK,EAGC,KAAA,IAAX8C,GAA0BA,EAAOI,IAAM,CAI/C,GAHA1B,KAAKxB,MAAQwB,KAAKU,aAAcV,KAAKxB,KAAM,EAGjB,KAAA,IAAfwB,KAAKxB,MAEZ,OADAwB,KAAAA,KAAK0B,IAAI,EAKb1B,KAAKW,YAAa,CAAE,EACpBX,KAAKnB,aAAe,EACpBmB,KAAKvB,OAAS,EAGduB,KAAKd,iBAAiB0B,SAAS,MAAM,EAChCb,YAAY,WAAW,EACvBc,OAAO,6BAA+Bb,KAAKxB,KAAK,EAChDsC,SAAS,WAAW,CAC7B,CAGA,GAAqB,CAAA,IAAhBd,KAAKjB,OAAV,CAKAU,IAAI8B,EAAO,CACPW,OAAQ,gCACRgB,OAAQlD,KAAKxB,MACbC,OAAQuB,KAAKvB,MACjB,EAEI4D,EAAS,CACTc,KAAM,MACNC,QAASpD,KAAKoD,QACdhB,SAAUpC,KAAKqD,gBACfC,MAAOtD,KAAKsD,MACZC,YAAavD,KAAKuD,YAClBC,SAAU,KACVC,QAASzD,KACTX,QAAS,IACb,EAEA,OAAQW,KAAKxB,OACb,IAAK,qBAAsBwB,KAAKtB,OAAS,EAAG,MAC5C,IAAK,cAAesB,KAAKtB,OAAS,IAAO,MACzC,IAAK,uBAAwBsB,KAAKtB,OAAS,IAAK,MAChD,IAAK,YAAasB,KAAKtB,OAAS,EAAG,MACnC,IAAK,iBAAkBsB,KAAKtB,OAAS,GAAI,MACzC,IAAK,oBAAqBsB,KAAKtB,OAASgF,aAAaC,uBAAwB,MAC7E,IAAK,qBAAsB3D,KAAKtB,OAAS,GAAI6C,EAAKqC,OAAS,qCAAsC,MACjG,IAAK,qBAAsB5D,KAAKtB,OAAS,EAAG6C,EAAKqC,OAAS,qCAAsC,MAChG,IAAK,2CAA4C5D,KAAKtB,OAAS,CAC/D,CAEA6C,EAAK7C,OAAS4B,KAAKC,MAAMP,KAAKtB,OAASsB,KAAKrB,kBAAkB,EAE9DsD,oBACIV,EACAc,EACAxC,OAAO,sBAAsB,CACjC,CAtCA,CAuCJ,CAMAgE,gBAAiBrF,GACbiB,IAAIqE,EAAc9D,KAAKrB,mBAElB,yBADGH,IACqBsF,GAAe,KAE5C9D,KAAKrB,mBAAqBmF,CAC9B,CAOApD,aAAclC,GAOV,OANAA,EAAkB,OAAVA,EAAiBwB,KAAKzB,OAAO,GAAKyB,KAAKzB,OAAOyB,KAAKzB,OAAOwF,QAASvF,CAAM,EAAI,GAGjFA,EAD8C,KAAA,IAAvCwB,KAAK1B,SAAS,YAAcE,IAAkE,GAAxC,CAACwB,KAAK1B,SAAS,YAAcE,GAClFwB,KAAKU,aAAclC,CAAM,EAG9BA,CACX,CAMAmC,YAAaqD,GACThE,KAAKlB,kBAAoBwB,KAAK2D,MAAkB,IAAXD,CAAe,EAAI,IACxDhE,KAAKb,YAAYA,YAAa,SAAU,QAASa,KAAKlB,iBAAkB,EACxEkB,KAAKZ,iBAAiB8E,KAAMjD,WAAW,eAAiBjB,KAAKxB,OAAS,MAAQwB,KAAKlB,kBAAoB,GAAI,CAC/G,CAMAsE,QAASe,GACGA,EAASb,MACbtD,KAAKsD,MACD,CAACM,OAAQ,IAAKQ,aAAcD,EAASb,KAAK,EAC1Ca,EAASb,MACTa,EAASE,GACb,EAEKrE,KAAKqD,iBACNrD,KAAKqD,gBAAiBc,EAAUnE,KAAKuB,KAAMvB,KAAKsC,GAAI,CAGhE,CAOAe,gBAAiB/B,GA6Bb,GA5BA3B,QAAQC,IAAK0B,CAAO,EAEpBtB,KAAKsE,kBAAkBhD,CAAM,EAEA,KAAA,IAAjBA,EAAOiD,QACfvE,KAAKnB,aAAe,IAAMyC,EAAOiD,OAGE,KAAA,IAA3BjD,EAAOkD,kBACK,uBAAfxE,KAAKxB,OAA0D,IAAxB,OAAO8C,EAAOiD,OACtDvE,KAAKyE,OAAO,2DAA2D,EAEvD,uBAAfzE,KAAKxB,OAA0D,IAAxB,OAAO8C,EAAOiD,OACtDvE,KAAKyE,OAAO,2DAA2D,EAG3EzE,KAAK0E,aAAcpD,EAAOkD,eAAgB,GAGA,KAAA,IAAlClD,EAAOqD,wBACf3E,KAAK4E,cAAetD,EAAOqD,sBAAuB,EAIjCE,KAAAA,IAAjBvD,EAAOwD,OAA8C,EAAvBC,OAAOzD,EAAOwD,KAAK,GACjD9E,KAAKgF,wBAAwB1D,EAAO2D,OAAO,EAG3B,CAAA,IAAf3D,EAAOI,KAA+B,IAAfJ,EAAOI,IAAY,CAC3CjC,IAAIyF,EAAoBlF,KAAKlB,kBAAoBwC,EAAO6D,UAAYnF,KAAKnB,aAC7B,yBAAxCyC,EAAOqD,uBAAuBS,OAAwD,IAApBF,IAClEA,EAAoB,KAExBlF,KAAKW,YAAYuE,CAAiB,EAClClF,KAAKvB,OAASuB,KAAKvB,OAAS6C,EAAO6D,UACnCnF,KAAKI,WAAYkB,CAAO,CAC5B,MACI3B,QAAQC,IAAKI,KAAKxB,MACd,gBACE8B,KAAKC,OAAM,IAAIC,MAAOC,QAAQ,EAAG,GAAI,EAAIT,KAAKV,aAChD,sBAAuB,EAC3BU,KAAKV,YAAcgB,KAAKC,OAAM,IAAIC,MAAOC,QAAQ,EAAE,GAAI,EACvDT,KAAKW,YAAa,GAAI,EACtBX,KAAKnB,aAAe,EACpBmB,KAAKvB,OAAS,EACd4C,WAAW,KACPrB,KAAKI,WAAYkB,CAAO,CAC5B,EAAG,GAAG,CAEd,CAMAgD,kBAAkBhD,GAEVA,EAAO+D,eAAe,oBAAoB,GAC1C/D,EAAOgE,mBAAmBD,eAAe,aAAa,GACtD/D,EAAOgE,mBAAmBC,aAC1BjE,EAAOgE,mBAAmBD,eAAe,cAAc,GACvD/D,EAAOgE,mBAAmBE,aAAaH,eAAe,YAAY,GAClE/D,EAAOgE,mBAAmBE,aAAaC,YACvCnE,EAAOgE,mBAAmBE,aAAaH,eAAe,aAAa,GACnE/D,EAAOgE,mBAAmBE,aAAaE,aAEvCC,oBACIrE,EAAOgE,mBAAmBE,aAAaE,YACvCpE,EAAOgE,mBAAmBC,WAC9B,CAER,CAQAjC,MAAOsC,EAAKhC,EAAQN,GAChB7D,IAAI8D,EAAcvD,KAAKuD,YAOvB,GALA5D,QAAQC,IAAK,sBAAuB,aAAc,EAClDD,QAAQC,IAAKgE,CAAO,EACpBjE,QAAQC,IAAK0D,CAAM,EACnB3D,QAAQC,IAAKgG,CAAI,EAEH,SAAVhC,IAA+B,IAATN,GAAwB,aAATA,KAChCtD,KAAK6F,WACN7F,KAAK6F,SAAW,EAChB7F,KAAK8F,WAAa,IAEtB9F,KAAK6F,QAAQ,GACblG,QAAQC,IAAI,QAAUI,KAAK6F,QAAQ,EACnC7F,KAAK6D,gBAAgB7D,KAAKxB,KAAK,EAC3BwB,KAAK6F,UAAY7F,KAAK8F,YACtB9F,KAAKK,MAAM,EACXL,KAAKG,OAAO,EACZH,KAAKI,WAAW,MAXxB,CAgBA,GAAoB,MAAfwF,EAAIhC,OACL,GAAgB,gBAAXA,EACDL,EAAa,4DAA6DvD,KAAKxB,KAAM,EACrFmB,QAAQC,IAAK,MAAQgG,EAAIxB,aAAc,cAAe,MACnD,CACH3E,IAAIsG,EAAcnC,EACI,KAAA,IAAVN,IACRyC,GAAe,qBAAuBzC,GAE1CC,EAAawC,EAAa/F,KAAKxB,KAAM,CACzC,MACsB,MAAfoH,EAAIhC,OACXL,EAAa,yBAA0BvD,KAAKxB,KAAK,EAEjD+E,EAAY,6BAA+BqC,EAAIhC,OAAS,YAAcA,EAAQ5D,KAAKxB,KAAK,EAGvFwB,KAAKb,aACNa,KAAKb,YAAY6G,QAAQ,MAAM,EAGnChG,KAAK0B,IAAI,CAvBT,CAwBJ,CAOA6B,YAAa0C,EAAUC,GACnBC,UAAUC,KAAK,EAAEC,SAAUJ,EAAW,cAAgBC,CAAK,CAC/D,CAMAzB,OAAO6B,GACHzG,OAAO,sBAAsB,EAAEE,YAAY,eAAe,EAC1DF,OAAO,mBAAmB,EAAEE,YAAY,eAAe,EACvDF,OAAO,+BAA+B,EAAE0G,QAASD,CAAa,CAClE,CAMA5B,aAAa8B,GACT,IAAM/G,IAAIC,KAAO8G,EACR9G,GACDM,KAAKyE,OACD,4BACAzE,KAAKyG,4BAA4B,EAAI,MACrCD,EAAM9G,GAAKgH,KAAO,MAAQF,EAAM9G,GAAKiH,OACrC,QAAUH,EAAM9G,GAAKkE,OACrB,UAAM,CAGtB,CAMAgB,cAAcrD,GACwD,KAAA,IAAvD1B,OAAO,kCAAkC,EAAE+G,MAAM,GACS,KAAA,IAA1D/G,OAAO,kCAAkC,EAAE+G,MAAM,EAAE,IAC1D/G,OAAO,kCAAkC,EAAE+G,MAAM,EAAE,GAAGC,cAAgBtF,EAAKuF,aAI/E9G,KAAKyE,OAAQ,iCACTzE,KAAKyG,4BAA4B,EAAY,SAC7ClF,EAAK6D,MAAkB,cAAW7D,EAAKuF,YAAc,aAAc,CAC3E,CAMA9B,wBAAwBC,GACpBpF,OAAO,iCAAiC,EAAEkH,OAAO,EACjDlH,OAAOA,OAAO,yCAAyC,EAAE,EAAE,EACtDmH,MACG,uKAEA/B,EAEA,YACJ,CACR,CAMAwB,8BACIhH,IAAIwH,EAAe,CAAA,EAEnBxH,IACAyH,EAA8C,CAAC,GADpB,IAAI1G,MAAO2G,kBAAkB,EACL,IAAO,GAOtDC,GAFAH,EAHsB,aAAtB,OAAOhG,YAC8B,KAAA,IAA9BA,WAAWoG,gBACY,CAAA,IAA9BpG,WAAWoG,eACI7G,KAAK8G,IAAI,EAAIJ,EAAoD,IAA5BjG,WAAWoG,eAEtDJ,GAAe,IAAIzG,KAAKyG,CAAY,EAAI,IAAIzG,KAErD+G,EAAiB,IAAIC,KAAKC,eAAe,QAAS,CAACC,MAAO,OAAO,CAAC,EAAEC,OACpEC,EAAUC,OAAOT,EAAOU,WAAW,CAAC,EAAEC,SAAS,EAAG,GAAG,EACrDC,EAAUH,OAAOT,EAAOa,WAAW,CAAC,EAAEF,SAAS,EAAG,GAAG,EACzD,OAAOR,EAAeH,CAAM,EAAI,IAC5BA,EAAOc,QAAQ,EAAI,IAAMd,EAAOe,YAAY,EAAI,IAChDf,EAAOgB,SAAS,EAAI,IAAMR,EAAU,IAAMI,CAClD,CACJ"} \ No newline at end of file +{"version":3,"file":"spbc-scanner-plugin.min.js","sources":["spbc-scanner-plugin.js"],"sourcesContent":["'use strict';\n\n/**\n * class SpbcMalwareScanner\n */\nclass SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */\n first_start = true;\n\n active = false;\n\n root = '';\n settings = [];\n states = [\n 'get_cms_hashes',\n 'get_modules_hashes',\n 'clean_results',\n 'file_system_analysis',\n 'get_approved_hashes',\n 'get_denied_hashes',\n 'signature_analysis',\n 'heuristic_analysis',\n 'schedule_send_heuristic_suspicious_files',\n 'auto_cure_backup',\n 'auto_cure',\n 'os_cron_analysis',\n 'db_trigger_analysis',\n 'outbound_links',\n 'frontend_analysis',\n 'important_files_listing',\n 'send_results',\n ];\n state = null;\n offset = 0;\n amount = 0;\n amount_coefficient = 1;\n total_scanned = 0;\n scan_percent = 0;\n percent_completed = 0;\n\n paused = false;\n\n button = null;\n spinner = null;\n\n progress_overall = null;\n progressbar = null;\n progressbar_text = null;\n\n timeout = 60000;\n\n state_timer = 0;\n\n /**\n * constructor\n * @param {array} properties\n */\n constructor( properties ) {\n console.log('init');\n if (jQuery('#spbcscan-results-log-module').length) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n }\n\n // Crunch for cure backups\n if ( typeof properties['settings']['auto_cure'] !== 'undefined' ) {\n properties['settings']['scanner__auto_cure_backup'] = '1';\n }\n\n for ( let key in properties ) {\n if ( typeof this[key] !== 'undefined' ) {\n this[key] = properties[key];\n }\n }\n };\n\n /**\n * Function Action Control\n */\n actionControl() {\n if (this.state === null) {\n this.start();\n } else if (this.paused) {\n this.resume();\n this.controller();\n } else {\n this.pause();\n }\n };\n\n /**\n * Function Start\n */\n start() {\n this.active = true;\n this.state_timer = Math.round(new Date().getTime() /1000);\n\n this.state = this.getNextState( null );\n\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n\n this.progressbar.show(500);\n this.progress_overall.show(500);\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n\n setTimeout(() => {\n this.controller();\n }, 1000);\n };\n\n /**\n * Function Pause\n * @param {*} result\n * @param {*} data\n * @param {*} opt\n */\n pause( result, data, opt ) {\n console.log('PAUSE');\n this.button.html(spbcScaner.button_scan_resume);\n this.spinner.css({display: 'none'});\n this.paused = true;\n this.active = false;\n };\n\n /**\n * Function Resume\n * @param {*} opt\n */\n resume( opt ) {\n console.log('RESUME');\n this.button.html(spbcScaner.button_scan_pause);\n this.spinner.css({display: 'inline'});\n this.paused = false;\n this.active = true;\n };\n\n /**\n * Function End\n * @param {bool} reload\n */\n end( reload ) {\n this.progressbar.hide(500);\n this.progress_overall.hide(500);\n this.button.html(spbcScaner.button_scan_perform);\n this.spinner.css({display: 'none'});\n this.state = null;\n this.plug = false;\n this.total_scanned = 0;\n this.active = false;\n\n if (reload) {\n document.location = document.location;\n } else {\n spbcSendAJAXRequest(\n {action: 'spbc_scanner_tab__reload_accordion'},\n {\n notJson: true,\n callback: function(result, data, params, obj) {\n jQuery(obj).accordion('destroy')\n .html(result)\n .accordion({\n header: 'h3',\n heightStyle: 'content',\n collapsible: true,\n active: false,\n });\n spbcTblBulkActionsListen();\n spbcTblRowActionsListen();\n spbcTblPaginationListen();\n spbcTblSortListen();\n spbcStartShowHide();\n spbcScannerReloadScanInfo();\n },\n },\n jQuery('#spbc_scan_accordion'),\n );\n\n if (!jQuery('#spbc_scanner_clear').length) {\n let clearLink = '

Clear scanner logs


';\n jQuery(clearLink).insertBefore('#spbcscan-scanner-caption');\n }\n }\n };\n\n /**\n * Function Controller\n * @param {obj} result\n */\n controller( result ) {\n console.log(this.state);\n\n // The current stage is over. Switching to the new one\n if ( typeof result !== 'undefined' && result.end ) {\n this.state = this.getNextState( this.state );\n\n // End condition\n if (typeof this.state === 'undefined') {\n this.end();\n return;\n }\n\n // Set percent to 0\n this.setPercents( 0 );\n this.scan_percent = 0;\n this.offset = 0;\n\n // Changing visualizing of the current stage\n this.progress_overall.children('span')\n .removeClass('spbc_bold')\n .filter('.spbc_overall_scan_status_' + this.state)\n .addClass('spbc_bold');\n }\n\n // Break execution if paused\n if ( this.paused === true ) {\n return;\n }\n\n // // AJAX params\n let data = {\n action: 'spbc_scanner_controller_front',\n method: this.state,\n offset: this.offset,\n };\n\n let params = {\n type: 'GET',\n success: this.success,\n callback: this.successCallback,\n error: this.error,\n errorOutput: this.errorOutput,\n complete: null,\n context: this,\n timeout: 120000,\n };\n\n switch (this.state) {\n case 'get_modules_hashes': this.amount = 2; break;\n case 'clear_table': this.amount = 10000; break;\n case 'file_system_analysis': this.amount = 700; break;\n case 'auto_cure': this.amount = 5; break;\n case 'outbound_links': this.amount = 10; break;\n case 'frontend_analysis': this.amount = spbcSettings.frontendAnalysisAmount; break;\n case 'signature_analysis': this.amount = 10; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'heuristic_analysis': this.amount = 4; data.status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR'; break;\n case 'schedule_send_heuristic_suspicious_files': this.amount = 1; break;\n }\n\n data.amount = Math.round(this.amount * this.amount_coefficient);\n\n spbcSendAJAXRequest(\n data,\n params,\n jQuery('#spbc_scan_accordion'),\n );\n };\n\n /**\n * Set Coefficients\n * @param {string} state\n */\n setCoefficients( state ) {\n let coefficient = this.amount_coefficient;\n switch (state) {\n case 'file_system_analysis': coefficient *= 1.5; break;\n }\n this.amount_coefficient = coefficient;\n };\n\n /**\n * Get Next State\n * @param {string} state\n * @return {number}\n */\n getNextState( state ) {\n state = state === null ? this.states[0] : this.states[this.states.indexOf( state ) + 1];\n\n if (typeof this.settings['scanner__' + state] !== 'undefined' && +this.settings['scanner__' + state] === 0) {\n state = this.getNextState( state );\n }\n\n return state;\n };\n\n /**\n * Set Percents\n * @param {number} percents\n */\n setPercents( percents ) {\n this.percent_completed = Math.floor( percents * 100 ) / 100;\n this.progressbar.progressbar( 'option', 'value', this.percent_completed );\n this.progressbar_text.text( spbcScaner['progressbar_' + this.state] + ' - ' + this.percent_completed + '%' );\n };\n\n /**\n * Function Success\n * @param {obj} response\n */\n success( response ) {\n if ( !! response.error ) {\n this.error(\n {status: 200, responseText: response.error},\n response.error,\n response.msg,\n );\n } else {\n if ( this.successCallback ) {\n this.successCallback( response, this.data, this.obj );\n }\n }\n };\n\n // Processing response from backend\n /**\n * Success Callback\n * @param {obj} result\n */\n successCallback( result ) {\n console.log( result );\n\n this.interactAccordion(result);\n\n if ( typeof result.total !== 'undefined' ) {\n this.scan_percent = 100 / result.total;\n }\n\n if ( typeof result.processed_items !== 'undefined') {\n if ( this.state === 'heuristic_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Heuristic Analysis

');\n }\n if ( this.state === 'signature_analysis' && typeof result.total !== 0 ) {\n this.logRaw('

Signature Analysis

');\n }\n\n this.logFileEntry( result.processed_items );\n }\n\n if ( typeof result.stage_data_for_logging !== 'undefined') {\n this.logStageEntry( result.stage_data_for_logging );\n }\n\n // Add link on shuffle salt if cured\n if (result.cured !== undefined && Number(result.cured) > 0) {\n this.showLinkForShuffleSalts(result.message);\n }\n\n if ( result.end !== true && result.end !== 1 ) {\n let processedPercents = this.percent_completed + result.processed * this.scan_percent;\n if (result.stage_data_for_logging.title === 'File System Analysis' && processedPercents > 100) {\n processedPercents = 100;\n }\n this.setPercents(processedPercents);\n this.offset = this.offset + result.processed;\n this.controller( result );\n } else {\n console.log( this.state +\n ' stage took ' +\n ( Math.round(new Date().getTime() /1000) - this.state_timer ) +\n ' seconds to complete' );\n this.state_timer = Math.round(new Date().getTime()/1000);\n this.setPercents( 100 );\n this.scan_percent = 0;\n this.offset = 0;\n setTimeout(() => {\n this.controller( result );\n }, 300);\n }\n };\n\n /**\n * Run interactive refresh for accordion.\n * @param {obj|string[]} result\n */\n interactAccordion(result) {\n // validation control\n if (result.hasOwnProperty('interactivity_data') &&\n result.interactivity_data.hasOwnProperty('update_text') &&\n result.interactivity_data.update_text &&\n result.interactivity_data.hasOwnProperty('refresh_data') &&\n result.interactivity_data.refresh_data.hasOwnProperty('do_refresh') &&\n result.interactivity_data.refresh_data.do_refresh &&\n result.interactivity_data.refresh_data.hasOwnProperty('control_tab') &&\n result.interactivity_data.refresh_data.control_tab\n ) {\n spbcReloadAccordion(\n result.interactivity_data.refresh_data.control_tab,\n result.interactivity_data.update_text,\n );\n }\n }\n\n /**\n * Function Error\n * @param {object} xhr\n * @param {string} status\n * @param {string} error\n */\n error( xhr, status, error ) {\n let errorOutput = this.errorOutput;\n\n console.log( '%c APBCT_AJAX_ERROR', 'color: red;' );\n console.log( status );\n console.log( error );\n console.log( xhr );\n\n if (status == 'error' && (error == '' || error == 'Not found')) {\n if (!this.tryCount) {\n this.tryCount = 0;\n this.retryLimit = 30;\n }\n this.tryCount++;\n console.log('Try #' + this.tryCount);\n this.setCoefficients(this.state);\n if (this.tryCount <= this.retryLimit) {\n this.pause();\n this.resume();\n this.controller();\n return;\n }\n }\n\n if ( xhr.status === 200 ) {\n if ( status === 'parsererror' ) {\n errorOutput( 'Unexpected response from server. See console for details.', this.state );\n console.log( '%c ' + xhr.responseText, 'color: pink;' );\n } else {\n let errorString = status;\n if ( typeof error !== 'undefined' ) {\n errorString += ' Additional info: ' + error;\n }\n errorOutput( errorString, this.state );\n }\n } else if (xhr.status === 500) {\n errorOutput( 'Internal server error.', this.state);\n } else {\n errorOutput('Unexpected response code: ' + xhr.status + '. Error: ' + status, this.state);\n }\n\n if ( this.progressbar ) {\n this.progressbar.fadeOut('slow');\n }\n\n this.end();\n };\n\n /**\n * Error Output\n * @param {string} errorMsg\n * @param {string} stage\n */\n errorOutput( errorMsg, stage ) {\n spbcModal.open().putError( errorMsg + '
Stage: ' + stage);\n };\n\n /**\n * Log Raw\n * @param {htmlString|Element|Text|Array|jQuery} messageToLog\n */\n logRaw(messageToLog) {\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper .panel-body').prepend( messageToLog );\n };\n\n /**\n * Log File Entry\n * @param {array} items\n */\n logFileEntry(items) {\n for ( let key in items ) {\n if ( key ) {\n this.logRaw(\n '

' +\n this.getSiteUTCShiftedTimeString() + ' - ' +\n items[key].path + ' - ' + items[key].module +\n ': ' + items[key].status + '' +\n '

');\n }\n }\n };\n\n /**\n * Log Stage Entry\n * @param {obj} data\n */\n logStageEntry(data) {\n if (typeof jQuery('.panel-body .spbc_log-line span').first() !== 'undefined' &&\n typeof jQuery('.panel-body .spbc_log-line span').first()[0] !== 'undefined' &&\n jQuery('.panel-body .spbc_log-line span').first()[0].textContent === data.description\n ) {\n return;\n }\n this.logRaw( '

test ' +\n this.getSiteUTCShiftedTimeString() + ' - ' + '' +\n data.title + ' ' + '' + data.description + '

' );\n };\n\n /**\n * Show Link For Shuffle Salts\n * @param {string} message\n */\n showLinkForShuffleSalts(message) {\n jQuery('#spbc_notice_about_shuffle_link').remove();\n jQuery(jQuery('.spbc_tab--active .spbc_wrapper_field p')[1])\n .after(\n '
' +\n '' +\n message +\n '' +\n '
',\n );\n }\n\n /**\n * Get Site UTC Shifted Time String\n * @return {string}\n */\n getSiteUTCShiftedTimeString() {\n let utcShiftedTs = false;\n // gettings current system/browser offset\n let currentBrowserOffset = new Date().getTimezoneOffset();\n currentBrowserOffset = currentBrowserOffset * -1 * 1000 * 60;\n // chek if global ct object is defined\n if (typeof spbcScaner !== 'undefined' &&\n typeof spbcScaner.timezone_shift !== 'undefined' &&\n spbcScaner.timezone_shift !== false) {\n utcShiftedTs = Date.now() - currentBrowserOffset + (spbcScaner.timezone_shift * 1000);\n }\n let ctDate = utcShiftedTs ? new Date(utcShiftedTs) : new Date();\n // construct date string\n let shortMonthName = new Intl.DateTimeFormat('en-US', {month: 'short'}).format;\n let minutes = String(ctDate.getMinutes()).padStart(2, '0');\n let seconds = String(ctDate.getSeconds()).padStart(2, '0');\n return shortMonthName(ctDate) + ' ' +\n ctDate.getDate() + ' ' + ctDate.getFullYear() + ' ' +\n ctDate.getHours() + ':' + minutes + ':' + seconds;\n }\n}\n"],"names":["SpbcMalwareScanner","first_start","active","root","settings","states","state","offset","amount","amount_coefficient","total_scanned","scan_percent","percent_completed","paused","button","spinner","progress_overall","progressbar","progressbar_text","timeout","state_timer","constructor","properties","let","key","console","log","jQuery","length","removeClass","this","actionControl","start","resume","controller","pause","Math","round","Date","getTime","getNextState","setPercents","children","filter","addClass","show","html","spbcScaner","button_scan_pause","css","display","setTimeout","result","data","opt","button_scan_resume","end","reload","hide","button_scan_perform","plug","document","location","spbcSendAJAXRequest","action","notJson","callback","params","obj","accordion","header","heightStyle","collapsible","spbcTblBulkActionsListen","spbcTblRowActionsListen","spbcTblPaginationListen","spbcTblSortListen","spbcStartShowHide","spbcScannerReloadScanInfo","insertBefore","method","type","success","successCallback","error","errorOutput","complete","context","spbcSettings","frontendAnalysisAmount","status","setCoefficients","coefficient","indexOf","percents","floor","text","response","responseText","msg","interactAccordion","total","processed_items","logRaw","logFileEntry","stage_data_for_logging","logStageEntry","undefined","cured","Number","showLinkForShuffleSalts","message","processedPercents","processed","title","hasOwnProperty","interactivity_data","update_text","refresh_data","do_refresh","control_tab","spbcReloadAccordion","xhr","tryCount","retryLimit","errorString","fadeOut","errorMsg","stage","spbcModal","open","putError","messageToLog","prepend","items","getSiteUTCShiftedTimeString","path","module","first","textContent","description","remove","after","utcShiftedTs","currentBrowserOffset","getTimezoneOffset","ctDate","timezone_shift","now","shortMonthName","Intl","DateTimeFormat","month","format","minutes","String","getMinutes","padStart","seconds","getSeconds","getDate","getFullYear","getHours"],"mappings":"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"} \ No newline at end of file diff --git a/js/src/spbc-scanner-plugin.js b/js/src/spbc-scanner-plugin.js index 4a3145e9b..f0ec25af6 100644 --- a/js/src/spbc-scanner-plugin.js +++ b/js/src/spbc-scanner-plugin.js @@ -23,6 +23,7 @@ class SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */ 'auto_cure_backup', 'auto_cure', 'os_cron_analysis', + 'db_trigger_analysis', 'outbound_links', 'frontend_analysis', 'important_files_listing', diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 56b2cde32..46eeac79a 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -59,6 +59,7 @@ class ScannerQueue 'frontend_analysis', 'important_files_listing', 'send_results', + // ATTENTION! Do not forget to localize this array in the frontend, use SpbcEnqueue. Do not forget to localize progress bar! ); /** diff --git a/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php b/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php index ec223eb7e..aed8b6e16 100644 --- a/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php +++ b/lib/CleantalkSP/SpbctWP/SpbcEnqueue.php @@ -293,6 +293,10 @@ function ($key) { //Cure 'progressbar_auto_cure_backup' => __('Creating a backup', 'security-malware-firewall'), 'progressbar_auto_cure' => __('Cure', 'security-malware-firewall'), + //OS Cron + 'progressbar_os_cron_analysis' => __('Cron tasks analysis', 'security-malware-firewall'), + //DB triggers + 'progressbar_db_trigger_analysis' => __('DB Trigger analysis', 'security-malware-firewall'), // Links 'progressbar_outbound_links' => __('Scanning links', 'security-malware-firewall'), // Frontend From 9666409488d6d3b9e0b097b8ebd087ed476ab46e Mon Sep 17 00:00:00 2001 From: alexandergull Date: Thu, 10 Apr 2025 19:12:31 +0500 Subject: [PATCH 05/18] Code. Scanner. DB triggers. Autotests. --- .../Scanner/DBTrigger/DBTriggerModel.php | 22 +++-- tests/Scanner/testDBTriggerModel.php | 86 +++++++++++++++++++ tests/Scanner/testDBTriggerService.php | 78 +++++++++++++++++ 3 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 tests/Scanner/testDBTriggerModel.php create mode 100644 tests/Scanner/testDBTriggerService.php diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index e970dccb3..9091743cc 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -6,15 +6,22 @@ class DBTriggerModel { /** * Runs the DBTrigger model to find malicious database triggers. - * + * @param array|null $mock_data Mocked triggers for testing purposes. * @return string|true The result of the operation or an error message. + * @psalm-suppress PossibleUnusedParameter */ - public static function run() + public static function run($mock_data = null) { try { - $db_triggers = DBTriggerService::getDataBaseTriggers(); + $db_triggers = $mock_data + ? $mock_data[0] + : DBTriggerService::getDataBaseTriggers(); + + $signatures = $mock_data + ? $mock_data[1] + : DBTriggerService::getSignaturesForTriggers(); - $bad_triggers = self::analyzeDBTriggers($db_triggers); + $bad_triggers = self::analyzeDBTriggers($db_triggers, $signatures); DBTriggerService::saveTriggersStorage($bad_triggers); } catch (\Exception $error) { @@ -30,13 +37,13 @@ public static function run() * @param array $db_triggers The list of triggers. * @return array The list of bad triggers. */ - private static function analyzeDBTriggers($db_triggers) + private static function analyzeDBTriggers($db_triggers, $signatures) { $bad_triggers = array(); foreach ($db_triggers as $trigger) { $trigger_code = $trigger->ACTION_STATEMENT; - $signature_name = self::isTriggerBad($trigger_code); + $signature_name = self::isTriggerBad($trigger_code, $signatures); if ($signature_name) { $bad_triggers[] = array( @@ -60,9 +67,8 @@ private static function analyzeDBTriggers($db_triggers) * @param string $trigger_code The code of the trigger. * @return string|bool The signature name if the trigger is bad, false otherwise. */ - private static function isTriggerBad($trigger_code) + private static function isTriggerBad($trigger_code, $signatures) { - $signatures = DBTriggerService::getSignaturesForTriggers(); foreach ($signatures as $signature_name => $signature) { if (preg_match('/' . $signature . '/is', $trigger_code)) { return $signature_name; diff --git a/tests/Scanner/testDBTriggerModel.php b/tests/Scanner/testDBTriggerModel.php new file mode 100644 index 000000000..d56250660 --- /dev/null +++ b/tests/Scanner/testDBTriggerModel.php @@ -0,0 +1,86 @@ + 'trigger1', + 'EVENT_MANIPULATION' => 'INSERT', + 'EVENT_OBJECT_TABLE' => 'table1', + 'ACTION_STATEMENT' => 'malicious_code', + 'ACTION_TIMING' => 'BEFORE', + ], + ]; + + $mock_signatures = ['malicious_signature' => 'malicious_code']; + + $mock_data = [$mock_triggers, $mock_signatures]; + + DBTriggerService::saveTriggersStorage([]); + + + $result = DBTriggerModel::run($mock_data); + + $found_triggers = DBTriggerService::loadTriggersStorage(); + + $expected_result = [ + [ + 'name' => 'trigger1', + 'table' => 'table1', + 'time' => 'BEFORE', + 'action' => 'INSERT', + 'code' => 'malicious_code', + 'signature' => 'malicious_signature', + 'status' => 'vulnerable trigger', + ], + ]; + + $this->assertTrue($result); + $this->assertNotEmpty($found_triggers); + $this->assertEquals($expected_result, $found_triggers); + } + + public function testRunFail() + { + $mock_triggers = [ + (object)[ + 'EVENT_MANIPULATION' => 'INSERT', + 'EVENT_OBJECT_TABLE' => 'table1', + 'ACTION_TIMING' => 'BEFORE', + ], + ]; + + $mock_signatures = ['malicious_signature' => 'malicious_code']; + + $mock_data = [$mock_triggers, $mock_signatures]; + + DBTriggerService::saveTriggersStorage([]); + + + $result = DBTriggerModel::run($mock_data); + + $found_triggers = DBTriggerService::loadTriggersStorage(); + + $expected_result = [ + [ + 'name' => 'trigger1', + 'table' => 'table1', + 'time' => 'BEFORE', + 'action' => 'INSERT', + 'code' => 'malicious_code', + 'signature' => 'malicious_signature', + 'status' => 'vulnerable trigger', + ], + ]; + + $this->assertIsString($result); + $this->assertEmpty($found_triggers); + $this->assertNotEquals($expected_result, $found_triggers); + } +} diff --git a/tests/Scanner/testDBTriggerService.php b/tests/Scanner/testDBTriggerService.php new file mode 100644 index 000000000..9237e2b83 --- /dev/null +++ b/tests/Scanner/testDBTriggerService.php @@ -0,0 +1,78 @@ + 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'vulnerable trigger'], + ]; + + DBTriggerService::saveTriggersStorage($bad_triggers); + + $this->assertEquals($bad_triggers, get_option('spbc_db_triggers')); + } + + public function testLoadTriggersStorage() + { + $expected_triggers = [ + ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'vulnerable trigger'], + ]; + + update_option('spbc_db_triggers', $expected_triggers); + + $this->assertEquals($expected_triggers, DBTriggerService::loadTriggersStorage()); + } + + public function testCountTriggersStorage() + { + $triggers = [ + ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'vulnerable trigger'], + ['name' => 'trigger2', 'table' => 'table2', 'time' => 'AFTER', 'action' => 'UPDATE', 'code' => 'code2', 'signature' => 'sig2', 'status' => 'vulnerable trigger'], + ]; + + update_option('spbc_db_triggers', $triggers); + + $this->assertEquals(2, DBTriggerService::countTriggersStorage()); + } + + public function testGetDataBaseTriggers() + { + global $wpdb; + + $wpdb = $this->createMock(\wpdb::class); + $wpdb->dbname = 'test_db'; + $wpdb->method('get_results')->willReturn([ + (object)[ + 'TRIGGER_NAME' => 'trigger1', + 'EVENT_MANIPULATION' => 'INSERT', + 'EVENT_OBJECT_TABLE' => 'table1', + 'ACTION_STATEMENT' => 'code1', + 'ACTION_TIMING' => 'BEFORE', + ], + ]); + + $triggers = DBTriggerService::getDataBaseTriggers(); + + $this->assertCount(1, $triggers); + $this->assertEquals('trigger1', $triggers[0]->TRIGGER_NAME); + } + + public function testGetSignaturesForTriggers() + { + global $wpdb; + + $wpdb = $this->createMock(\wpdb::class); + $wpdb->method('get_results')->willReturn([ + ['0' => 'sig1', '1' => 'code1'], + ]); + + $signatures = DBTriggerService::getSignaturesForTriggers(); + + $this->assertArrayHasKey('sig1', $signatures); + $this->assertEquals('code1', $signatures['sig1']); + } +} From 41c3e03bcfb38aad8ffdf021e83fe02f137e0d26 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Fri, 11 Apr 2025 13:24:56 +0500 Subject: [PATCH 06/18] Code. PHPUnit. Fixed testDBTriggerService. --- tests/Scanner/testDBTriggerService.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Scanner/testDBTriggerService.php b/tests/Scanner/testDBTriggerService.php index 9237e2b83..9946ebfa4 100644 --- a/tests/Scanner/testDBTriggerService.php +++ b/tests/Scanner/testDBTriggerService.php @@ -5,6 +5,17 @@ class testDBTriggerService extends TestCase { + private $origin_wpdb; + public function setUp() { + global $wpdb; + $this->origin_wpdb = $wpdb; + } + + public function tearDown() { + global $wpdb; + $wpdb = $this->origin_wpdb; + } + public function testSaveTriggersStorage() { $bad_triggers = [ From b20c7288ab96a2dc2a2d21bae7899d5b635737d9 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Fri, 11 Apr 2025 13:41:09 +0500 Subject: [PATCH 07/18] Fix. Disable OS Cron and DB trigger analysis if options disabled. --- lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 46eeac79a..5344e27e9 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -120,6 +120,14 @@ public function __construct($stage = '', $offset = null, $amount = null, $root_d if ( isset($spbc->settings['scanner__schedule_send_heuristic_suspicious_files']) && $spbc->settings['scanner__schedule_send_heuristic_suspicious_files'] == 0) { unset(self::$stages['schedule_send_heuristic_suspicious_files']); } + + if ( isset($spbc->settings['scanner__os_cron_analysis']) && $spbc->settings['scanner__os_cron_analysis'] == 0) { + unset(self::$stages['os_cron_analysis']); + } + + if ( isset($spbc->settings['scanner__db_trigger_analysis']) && $spbc->settings['scanner__db_trigger_analysis'] == 0) { + unset(self::$stages['db_trigger_analysis']); + } } /** From 6a45b40a666bf5d8c55f12d833c921f76255bb6f Mon Sep 17 00:00:00 2001 From: Glomberg Date: Tue, 15 Apr 2025 18:06:32 +0300 Subject: [PATCH 08/18] Fix. Scanner. SQL triggers: text fixed. --- .../SpbctWP/Scanner/DBTrigger/DBTriggerModel.php | 2 +- tests/Scanner/testDBTriggerModel.php | 4 ++-- tests/Scanner/testDBTriggerService.php | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 9091743cc..3a9b83180 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -53,7 +53,7 @@ private static function analyzeDBTriggers($db_triggers, $signatures) 'action' => $trigger->EVENT_MANIPULATION, 'code' => $trigger_code, 'signature' => $signature_name, - 'status' => 'vulnerable trigger', + 'status' => 'Vulnerable trigger', ); } } diff --git a/tests/Scanner/testDBTriggerModel.php b/tests/Scanner/testDBTriggerModel.php index d56250660..c2cbeedf2 100644 --- a/tests/Scanner/testDBTriggerModel.php +++ b/tests/Scanner/testDBTriggerModel.php @@ -37,7 +37,7 @@ public function testRunSuccess() 'action' => 'INSERT', 'code' => 'malicious_code', 'signature' => 'malicious_signature', - 'status' => 'vulnerable trigger', + 'status' => 'Vulnerable trigger', ], ]; @@ -75,7 +75,7 @@ public function testRunFail() 'action' => 'INSERT', 'code' => 'malicious_code', 'signature' => 'malicious_signature', - 'status' => 'vulnerable trigger', + 'status' => 'Vulnerable trigger', ], ]; diff --git a/tests/Scanner/testDBTriggerService.php b/tests/Scanner/testDBTriggerService.php index 9946ebfa4..7928ed81e 100644 --- a/tests/Scanner/testDBTriggerService.php +++ b/tests/Scanner/testDBTriggerService.php @@ -19,7 +19,7 @@ public function tearDown() { public function testSaveTriggersStorage() { $bad_triggers = [ - ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'vulnerable trigger'], + ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'Vulnerable trigger'], ]; DBTriggerService::saveTriggersStorage($bad_triggers); @@ -30,7 +30,7 @@ public function testSaveTriggersStorage() public function testLoadTriggersStorage() { $expected_triggers = [ - ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'vulnerable trigger'], + ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'Vulnerable trigger'], ]; update_option('spbc_db_triggers', $expected_triggers); @@ -41,8 +41,8 @@ public function testLoadTriggersStorage() public function testCountTriggersStorage() { $triggers = [ - ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'vulnerable trigger'], - ['name' => 'trigger2', 'table' => 'table2', 'time' => 'AFTER', 'action' => 'UPDATE', 'code' => 'code2', 'signature' => 'sig2', 'status' => 'vulnerable trigger'], + ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'Vulnerable trigger'], + ['name' => 'trigger2', 'table' => 'table2', 'time' => 'AFTER', 'action' => 'UPDATE', 'code' => 'code2', 'signature' => 'sig2', 'status' => 'Vulnerable trigger'], ]; update_option('spbc_db_triggers', $triggers); From b5686107a52c08d0f3d2b014bad188b380fc838e Mon Sep 17 00:00:00 2001 From: AntonV1211 Date: Thu, 20 Mar 2025 15:05:15 +0300 Subject: [PATCH 09/18] Fix. Scanner. SQL triggers: sending found signatures implemented. --- .../Scanner/DBTrigger/DBTriggerModel.php | 30 ++++++++++++++----- .../Scanner/DBTrigger/DBTriggerService.php | 4 +-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 3a9b83180..01b4e38c9 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -39,25 +39,41 @@ public static function run($mock_data = null) */ private static function analyzeDBTriggers($db_triggers, $signatures) { + global $spbc; + $bad_triggers = array(); + $triggered_signatures = []; foreach ($db_triggers as $trigger) { $trigger_code = $trigger->ACTION_STATEMENT; - $signature_name = self::isTriggerBad($trigger_code, $signatures); + $signature = self::isTriggerBad($trigger_code, $signatures); - if ($signature_name) { + if ($signature) { $bad_triggers[] = array( 'name' => $trigger->TRIGGER_NAME, 'table' => $trigger->EVENT_OBJECT_TABLE, 'time' => $trigger->ACTION_TIMING, 'action' => $trigger->EVENT_MANIPULATION, 'code' => $trigger_code, - 'signature' => $signature_name, + 'signature' => $signature[1], 'status' => 'Vulnerable trigger', ); + $triggered_signatures[key($signature)] = current($signature); } } + if ( count($triggered_signatures) > 0 ) { + $signature_idx = $spbc->data['scanner']['signatures_found']; + foreach ( $triggered_signatures as $signature_id => $_signature ) { + $signature_idx[$signature_id] = ! empty($signature_idx[$signature_id]) + ? (int)$signature_idx[$signature_id] + 1 + : 1; + } + + $spbc->data['scanner']['signatures_found'] = $signature_idx; + $spbc->save('data'); + } + return $bad_triggers; } @@ -65,13 +81,13 @@ private static function analyzeDBTriggers($db_triggers, $signatures) * Check if the trigger is bad. * * @param string $trigger_code The code of the trigger. - * @return string|bool The signature name if the trigger is bad, false otherwise. + * @return bool|array The signature name if the trigger is bad, false otherwise. */ private static function isTriggerBad($trigger_code, $signatures) { - foreach ($signatures as $signature_name => $signature) { - if (preg_match('/' . $signature . '/is', $trigger_code)) { - return $signature_name; + foreach ($signatures as $signature_id => $signature) { + if (preg_match('/' . $signature[2] . '/is', $trigger_code)) { + return [$signature_id => $signature]; } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php index 7d5e458bd..87e9472e0 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php @@ -67,7 +67,7 @@ final public static function getSignaturesForTriggers() global $wpdb; $cloud_signatures = $wpdb->get_results( $wpdb->prepare( - "SELECT name, body FROM " . SPBC_TBL_SCAN_SIGNATURES . " WHERE type = %s", + "SELECT id, name, body FROM " . SPBC_TBL_SCAN_SIGNATURES . " WHERE type = %s", 'TRIGGER' ), ARRAY_N @@ -76,7 +76,7 @@ final public static function getSignaturesForTriggers() $cloud_signatures = array(); } foreach ($cloud_signatures as $key => $signature) { - $cloud_signatures[$signature[0]] = $signature[1]; + $cloud_signatures[$signature[0]] = [$signature[1], $signature[2]]; unset($cloud_signatures[$key]); } return $cloud_signatures; From 53c79db5b6711ddfbade984591dcdd47e90fafbf Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 17 Apr 2025 16:57:37 +0300 Subject: [PATCH 10/18] Fix. Scanner. Clear `signatures_found` on scanner start. --- lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 5344e27e9..b511c2457 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -763,6 +763,9 @@ public function clean_results($offset = null, $amount = 50000) // phpcs:ignore P // Deleting newly added exclusions $this->deleteFilesOfExclusionDirs($spbc->settings['scanner__dir_exclusions']); + $spbc->data['scanner']['signatures_found'] = []; // Clearing ids of the signatures found + $spbc->save('data'); + $out = array( 'total' => (int)$deleted, 'processed' => (int)$deleted, @@ -1328,7 +1331,6 @@ public function signature_analysis($status = 'UNKNOWN,MODIFIED,OK,INFECTED,ERROR ); } $spbc->data['scanner']['scanned_total'] = 0; - $spbc->data['scanner']['signatures_found'] = []; // Clearing ids of the signatures found $total = $this->countFilesByStatusAndChecked($status_raw, 'SIGNATURE_ANALYSIS'); if ( ! isset($total['total']) ) { error_log('countFilesByStatusAndChecked: ' . $total['error'] . ' ' . $total['comment']); From 7622a6f48ba6033191fec2cf6cc853a7bbf7e0b6 Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 17 Apr 2025 16:58:39 +0300 Subject: [PATCH 11/18] Fix. Triggers scanner. PHP Unit testing - using mock data. --- .../Scanner/DBTrigger/DBTriggerModel.php | 26 +++++++++++-------- .../SpbctWP/Scanner/ScannerQueue.php | 3 ++- tests/Scanner/testDBTriggerModel.php | 10 ++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 01b4e38c9..e0370bdb8 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -6,22 +6,16 @@ class DBTriggerModel { /** * Runs the DBTrigger model to find malicious database triggers. - * @param array|null $mock_data Mocked triggers for testing purposes. * @return string|true The result of the operation or an error message. - * @psalm-suppress PossibleUnusedParameter */ - public static function run($mock_data = null) + public function run() { try { - $db_triggers = $mock_data - ? $mock_data[0] - : DBTriggerService::getDataBaseTriggers(); + $db_triggers = $this->getDataBaseTriggers(); - $signatures = $mock_data - ? $mock_data[1] - : DBTriggerService::getSignaturesForTriggers(); + $signatures = $this->getSignaturesForTriggers(); - $bad_triggers = self::analyzeDBTriggers($db_triggers, $signatures); + $bad_triggers = $this->analyzeDBTriggers($db_triggers, $signatures); DBTriggerService::saveTriggersStorage($bad_triggers); } catch (\Exception $error) { @@ -37,7 +31,7 @@ public static function run($mock_data = null) * @param array $db_triggers The list of triggers. * @return array The list of bad triggers. */ - private static function analyzeDBTriggers($db_triggers, $signatures) + private function analyzeDBTriggers($db_triggers, $signatures) { global $spbc; @@ -93,4 +87,14 @@ private static function isTriggerBad($trigger_code, $signatures) return false; } + + protected function getDataBaseTriggers() + { + return DBTriggerService::getDataBaseTriggers(); + } + + protected function getSignaturesForTriggers() + { + return DBTriggerService::getSignaturesForTriggers(); + } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index b511c2457..a9259b737 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -1086,7 +1086,8 @@ public function db_trigger_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMeth $scanning_stages_storage->converter->loadCollection(); $stage_data_obj = $scanning_stages_storage->getStage(DBTriggerAnalysis::class); - $result = DBTriggerModel::run(); + $trigger_scanner = new DBTriggerModel(); + $result = $trigger_scanner->run(); if (true !== $result) { ScanningLogFacade::writeToLog( diff --git a/tests/Scanner/testDBTriggerModel.php b/tests/Scanner/testDBTriggerModel.php index c2cbeedf2..d0ba9d1c0 100644 --- a/tests/Scanner/testDBTriggerModel.php +++ b/tests/Scanner/testDBTriggerModel.php @@ -20,12 +20,16 @@ public function testRunSuccess() $mock_signatures = ['malicious_signature' => 'malicious_code']; - $mock_data = [$mock_triggers, $mock_signatures]; + $stub = $this->createMock(DBTriggerModel::class); - DBTriggerService::saveTriggersStorage([]); + $stub->method('getDataBaseTriggers') + ->willReturn($mock_triggers) + ->method('getSignaturesForTriggers') + ->willReturn($mock_signatures); + DBTriggerService::saveTriggersStorage([]); - $result = DBTriggerModel::run($mock_data); + $result = $stub->run(); $found_triggers = DBTriggerService::loadTriggersStorage(); From 7cbbca42e49e67b143c83f87de2e05c73475b724 Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 17 Apr 2025 17:52:01 +0300 Subject: [PATCH 12/18] Fix. Code. DB Triggers unit tests fixed. --- .../Scanner/DBTrigger/DBTriggerModel.php | 8 ++-- tests/Scanner/testDBTriggerModel.php | 38 +++++++++++++------ tests/Scanner/testDBTriggerService.php | 6 +-- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index e0370bdb8..14a16316e 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -49,7 +49,7 @@ private function analyzeDBTriggers($db_triggers, $signatures) 'time' => $trigger->ACTION_TIMING, 'action' => $trigger->EVENT_MANIPULATION, 'code' => $trigger_code, - 'signature' => $signature[1], + 'signature' => current($signature)[0], 'status' => 'Vulnerable trigger', ); $triggered_signatures[key($signature)] = current($signature); @@ -80,7 +80,7 @@ private function analyzeDBTriggers($db_triggers, $signatures) private static function isTriggerBad($trigger_code, $signatures) { foreach ($signatures as $signature_id => $signature) { - if (preg_match('/' . $signature[2] . '/is', $trigger_code)) { + if (preg_match('/' . $signature[1] . '/is', $trigger_code)) { return [$signature_id => $signature]; } } @@ -88,12 +88,12 @@ private static function isTriggerBad($trigger_code, $signatures) return false; } - protected function getDataBaseTriggers() + public function getDataBaseTriggers() { return DBTriggerService::getDataBaseTriggers(); } - protected function getSignaturesForTriggers() + public function getSignaturesForTriggers() { return DBTriggerService::getSignaturesForTriggers(); } diff --git a/tests/Scanner/testDBTriggerModel.php b/tests/Scanner/testDBTriggerModel.php index d0ba9d1c0..14403d269 100644 --- a/tests/Scanner/testDBTriggerModel.php +++ b/tests/Scanner/testDBTriggerModel.php @@ -18,18 +18,24 @@ public function testRunSuccess() ], ]; - $mock_signatures = ['malicious_signature' => 'malicious_code']; + $mock_signatures = [666 => ['malicious_signature', 'malicious_code']]; - $stub = $this->createMock(DBTriggerModel::class); + // Create a partial mock of DBTriggerModel + $dbTriggerModelMock = $this->getMockBuilder(DBTriggerModel::class) + ->setMethods(['getDataBaseTriggers', 'getSignaturesForTriggers']) // Mock specific methods + ->getMock(); - $stub->method('getDataBaseTriggers') - ->willReturn($mock_triggers) - ->method('getSignaturesForTriggers') - ->willReturn($mock_signatures); + // Mock the getDataBaseTriggers method + $dbTriggerModelMock->method('getDataBaseTriggers') + ->willReturn($mock_triggers); + + // Mock the getSignaturesForTriggers method + $dbTriggerModelMock->method('getSignaturesForTriggers') + ->willReturn($mock_signatures); DBTriggerService::saveTriggersStorage([]); - $result = $stub->run(); + $result = $dbTriggerModelMock->run(); $found_triggers = DBTriggerService::loadTriggersStorage(); @@ -60,14 +66,24 @@ public function testRunFail() ], ]; - $mock_signatures = ['malicious_signature' => 'malicious_code']; + $mock_signatures = [666 => ['malicious_signature', 'malicious_code']]; - $mock_data = [$mock_triggers, $mock_signatures]; + // Create a partial mock of DBTriggerModel + $dbTriggerModelMock = $this->getMockBuilder(DBTriggerModel::class) + ->setMethods(['getDataBaseTriggers', 'getSignaturesForTriggers']) // Mock specific methods + ->getMock(); - DBTriggerService::saveTriggersStorage([]); + // Mock the getDataBaseTriggers method + $dbTriggerModelMock->method('getDataBaseTriggers') + ->willReturn($mock_triggers); + // Mock the getSignaturesForTriggers method + $dbTriggerModelMock->method('getSignaturesForTriggers') + ->willReturn($mock_signatures); + + DBTriggerService::saveTriggersStorage([]); - $result = DBTriggerModel::run($mock_data); + $result = $dbTriggerModelMock->run(); $found_triggers = DBTriggerService::loadTriggersStorage(); diff --git a/tests/Scanner/testDBTriggerService.php b/tests/Scanner/testDBTriggerService.php index 7928ed81e..c4014a1e4 100644 --- a/tests/Scanner/testDBTriggerService.php +++ b/tests/Scanner/testDBTriggerService.php @@ -78,12 +78,12 @@ public function testGetSignaturesForTriggers() $wpdb = $this->createMock(\wpdb::class); $wpdb->method('get_results')->willReturn([ - ['0' => 'sig1', '1' => 'code1'], + [666, 'sig1', 'code1'], ]); $signatures = DBTriggerService::getSignaturesForTriggers(); - $this->assertArrayHasKey('sig1', $signatures); - $this->assertEquals('code1', $signatures['sig1']); + $this->assertArrayHasKey(666, $signatures); + $this->assertEquals(['sig1', 'code1'], $signatures[666]); } } From 6b8d5773548a4385e46843aae6f9ec13403370eb Mon Sep 17 00:00:00 2001 From: svfcode Date: Thu, 8 May 2025 13:26:57 +0300 Subject: [PATCH 13/18] New. Scan. Add delete action. --- inc/spbc-settings.php | 9 ++++++- lib/CleantalkSP/SpbctWP/ListTable.php | 25 ++++++++++++++++++- .../Scanner/DBTrigger/DBTriggerModel.php | 23 +++++++++++++++++ .../Scanner/DBTrigger/DBTriggerService.php | 22 +++++++++++++++- .../Scanner/DBTrigger/DBTriggerView.php | 13 +++++++++- 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index 749160c65..7624bd14b 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -4295,11 +4295,18 @@ function spbc_list_table__get_args_by_type($table_type) . __('DB Trigger not found in the server environment or is unavailable to read/write.', 'security-malware-firewall') . '', 'columns' => array( + 'cb' => array('heading' => '', 'class' => 'check-column', 'width_percent' => 2), 'about_trigger' => array( 'heading' => 'About trigger', 'width_percent' => 30 ), // name, table, time, action - 'code' => array( 'heading' => 'Code', 'width_percent' => 45 ), + 'code' => array( 'heading' => 'Code', 'width_percent' => 43 ), 'signature' => array( 'heading' => 'Signature', 'width_percent' => 10 ), 'analysis_status' => array( 'heading' => 'Verdict', 'width_percent' => 15 ), ), + 'actions' => array( + 'delete' => array('name' => 'Delete',), + ), + 'bulk_actions' => array( + 'delete' => array('name' => 'Delete',), + ), ); break; diff --git a/lib/CleantalkSP/SpbctWP/ListTable.php b/lib/CleantalkSP/SpbctWP/ListTable.php index 9053f8654..25fea152d 100644 --- a/lib/CleantalkSP/SpbctWP/ListTable.php +++ b/lib/CleantalkSP/SpbctWP/ListTable.php @@ -3,6 +3,7 @@ namespace CleantalkSP\SpbctWP; use CleantalkSP\SpbctWP\Scanner\Cure; +use CleantalkSP\SpbctWP\Scanner\DBTrigger\DBTriggerService; use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronController; use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale; use CleantalkSP\Variables\Post; @@ -652,7 +653,10 @@ public static function ajaxRowActionHandler() $action = Post::getString('add_action'); $is_frontend_malware_action = $action === 'disapprove_page' || $action === 'approve_page'; - if ( $action !== 'restore' && ! $is_frontend_malware_action && strpos($action, 'oscron_task') === false ) { + if ( $action !== 'restore' && ! $is_frontend_malware_action && + strpos($action, 'oscron_task') === false && + strpos($action, 'delete-trigger') === false + ) { $check_file_exist_result = self::spbcCheckFileExist(); if (isset($check_file_exist_result['error'])) { @@ -713,6 +717,9 @@ public static function ajaxRowActionHandler() case 'enable_oscron_task': self::ajaxRowActionHandlerApproveOSCronTask(); break; + case 'delete-trigger': + self::ajaxRowActionHandlerDeleteTrigger(); + break; default: wp_send_json(array('temp_html' => '')); } @@ -740,6 +747,22 @@ public static function ajaxRowActionHandlerApproveOSCronTask() } } + public static function ajaxRowActionHandlerDeleteTrigger() + { + global $spbc; + $result = DBTriggerService::deleteTrigger(Post::get('id', null, 'word')); + if ($result) { + $out = array( + 'html' => '', + ); + wp_send_json($out); + } else { + wp_send_json_error(esc_html($result)); + } + } + public static function ajaxRowActionHandlerDisableOSCronTask() { global $spbc; diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 14a16316e..d00987afe 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -12,6 +12,7 @@ public function run() { try { $db_triggers = $this->getDataBaseTriggers(); + $db_triggers = $this->removeSentTriggers($db_triggers); $signatures = $this->getSignaturesForTriggers(); @@ -25,6 +26,27 @@ public function run() return true; } + /** + * Remove the triggers that have already been sent. + * + * @param array $db_triggers The list of triggers. + * @return array The list of triggers without the sent ones. + */ + private function removeSentTriggers($db_triggers) + { + $scanned_triggers = DBTriggerService::loadTriggersStorage(); + $scanned_triggers = array_map(function($trigger) { + return $trigger['name']; + }, $scanned_triggers); + $db_triggers = array_filter($db_triggers, function($trigger) use ($scanned_triggers) { + if (in_array($trigger->TRIGGER_NAME, $scanned_triggers)) { + return false; + } + return true; + }); + return $db_triggers; + } + /** * Analyze the triggers and return the bad ones. * @@ -51,6 +73,7 @@ private function analyzeDBTriggers($db_triggers, $signatures) 'code' => $trigger_code, 'signature' => current($signature)[0], 'status' => 'Vulnerable trigger', + 'sent' => true, ); $triggered_signatures[key($signature)] = current($signature); } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php index 87e9472e0..d85482464 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php @@ -15,7 +15,9 @@ final class DBTriggerService final public static function saveTriggersStorage($bad_triggers) { if (is_array($bad_triggers)) { - update_option(self::$option_name, $bad_triggers); + $scanned_triggers = self::loadTriggersStorage(); + $scanned_triggers = array_merge($scanned_triggers, $bad_triggers); + update_option(self::$option_name, $scanned_triggers); } } @@ -81,4 +83,22 @@ final public static function getSignaturesForTriggers() } return $cloud_signatures; } + + /** + * Delete trigger from storage. + * @param string $trigger_name + * @return bool + */ + final public static function deleteTrigger($trigger_name) + { + global $wpdb; + + $wpdb->query('DROP TRIGGER IF EXISTS ' . $trigger_name); + + $triggers = self::loadTriggersStorage(); + $triggers = array_filter($triggers, function($trigger) use ($trigger_name) { + return $trigger['name'] !== $trigger_name; + }); + return update_option(self::$option_name, $triggers); + } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php index 2a49f3f2c..2e72f4c2e 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerView.php @@ -16,11 +16,15 @@ final public static function prepareTableData($table) $table->items = DBTriggerService::loadTriggersStorage(); $table->items_count = DBTriggerService::countTriggersStorage(); $table->columns = [ + 'cb' => array('heading' => '', 'class' => 'check-column', 'width_percent' => 2), 'about_trigger' => array( 'heading' => 'About trigger', 'width_percent' => 30 ), // name, table, time, action - 'code' => array( 'heading' => 'Code', 'width_percent' => 45 ), + 'code' => array( 'heading' => 'Code', 'width_percent' => 43 ), 'signature' => array( 'heading' => 'Signature', 'width_percent' => 10 ), 'analysis_status' => array( 'heading' => 'Verdict', 'width_percent' => 15 ), ]; + $table->actions = [ + 'delete' => array('name' => 'Delete',), + ]; foreach ($table->items as $key => $item) { $table->items[$key]['about_trigger'] = sprintf( '%s
%s
%s
%s', @@ -29,9 +33,16 @@ final public static function prepareTableData($table) __('Time', 'security-malware-firewall') . ': ' . $item['time'], __('Action', 'security-malware-firewall') . ': ' . $item['action'] ); + $table->items[$key]['about_trigger'] .= sprintf( + '
%s
', + $item['name'], + $item['name'], + __('Delete', 'security-malware-firewall') + ); $table->items[$key]['code'] = $item['code']; $table->items[$key]['signature'] = $item['signature']; $table->items[$key]['analysis_status'] = $item['status']; + $table->items[$key]['cb'] = 1; } return $table; } From 63d4f0fdda265b4b17ba5c2799eb7319a435167c Mon Sep 17 00:00:00 2001 From: svfcode Date: Thu, 8 May 2025 13:45:13 +0300 Subject: [PATCH 14/18] fix phpcs --- lib/CleantalkSP/SpbctWP/ListTable.php | 2 +- .../SpbctWP/Scanner/DBTrigger/DBTriggerModel.php | 12 ++++++------ .../SpbctWP/Scanner/DBTrigger/DBTriggerService.php | 3 ++- tests/Scanner/testDBTriggerModel.php | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/ListTable.php b/lib/CleantalkSP/SpbctWP/ListTable.php index 25fea152d..7e7e79975 100644 --- a/lib/CleantalkSP/SpbctWP/ListTable.php +++ b/lib/CleantalkSP/SpbctWP/ListTable.php @@ -759,7 +759,7 @@ public static function ajaxRowActionHandlerDeleteTrigger() ); wp_send_json($out); } else { - wp_send_json_error(esc_html($result)); + wp_send_json_error(esc_html((string)$result)); } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index d00987afe..4db93f0c5 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -35,14 +35,14 @@ public function run() private function removeSentTriggers($db_triggers) { $scanned_triggers = DBTriggerService::loadTriggersStorage(); - $scanned_triggers = array_map(function($trigger) { + $scanned_triggers = array_map(function ($trigger) { return $trigger['name']; }, $scanned_triggers); - $db_triggers = array_filter($db_triggers, function($trigger) use ($scanned_triggers) { - if (in_array($trigger->TRIGGER_NAME, $scanned_triggers)) { - return false; - } - return true; + $db_triggers = array_filter($db_triggers, function ($trigger) use ($scanned_triggers) { + if (in_array($trigger->TRIGGER_NAME, $scanned_triggers)) { + return false; + } + return true; }); return $db_triggers; } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php index d85482464..ccc913066 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php @@ -93,10 +93,11 @@ final public static function deleteTrigger($trigger_name) { global $wpdb; + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TRIGGER IF EXISTS ' . $trigger_name); $triggers = self::loadTriggersStorage(); - $triggers = array_filter($triggers, function($trigger) use ($trigger_name) { + $triggers = array_filter($triggers, function ($trigger) use ($trigger_name) { return $trigger['name'] !== $trigger_name; }); return update_option(self::$option_name, $triggers); diff --git a/tests/Scanner/testDBTriggerModel.php b/tests/Scanner/testDBTriggerModel.php index 14403d269..de1ef73f0 100644 --- a/tests/Scanner/testDBTriggerModel.php +++ b/tests/Scanner/testDBTriggerModel.php @@ -48,6 +48,7 @@ public function testRunSuccess() 'code' => 'malicious_code', 'signature' => 'malicious_signature', 'status' => 'Vulnerable trigger', + 'sent' => true, ], ]; From 27f909a47669f3a1127f096e8bcfb9c97b7af7ab Mon Sep 17 00:00:00 2001 From: svfcode Date: Tue, 13 May 2025 12:48:23 +0300 Subject: [PATCH 15/18] fix test --- tests/Scanner/testDBTriggerModel.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Scanner/testDBTriggerModel.php b/tests/Scanner/testDBTriggerModel.php index de1ef73f0..dd7c16b53 100644 --- a/tests/Scanner/testDBTriggerModel.php +++ b/tests/Scanner/testDBTriggerModel.php @@ -101,7 +101,6 @@ public function testRunFail() ]; $this->assertIsString($result); - $this->assertEmpty($found_triggers); $this->assertNotEquals($expected_result, $found_triggers); } } From 65974e3c5b511441ecbb8637029936b850fc124c Mon Sep 17 00:00:00 2001 From: svfcode Date: Tue, 13 May 2025 12:55:17 +0300 Subject: [PATCH 16/18] fix test --- tests/Scanner/testDBTriggerService.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Scanner/testDBTriggerService.php b/tests/Scanner/testDBTriggerService.php index c4014a1e4..e820f84d5 100644 --- a/tests/Scanner/testDBTriggerService.php +++ b/tests/Scanner/testDBTriggerService.php @@ -18,6 +18,8 @@ public function tearDown() { public function testSaveTriggersStorage() { + update_option('spbc_db_triggers', []); + $bad_triggers = [ ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'Vulnerable trigger'], ]; @@ -29,6 +31,8 @@ public function testSaveTriggersStorage() public function testLoadTriggersStorage() { + update_option('spbc_db_triggers', []); + $expected_triggers = [ ['name' => 'trigger1', 'table' => 'table1', 'time' => 'BEFORE', 'action' => 'INSERT', 'code' => 'code1', 'signature' => 'sig1', 'status' => 'Vulnerable trigger'], ]; From dbc35f03d4d6a58ceebcab9194c2bb58e4562f18 Mon Sep 17 00:00:00 2001 From: svfcode Date: Thu, 22 May 2025 08:40:00 +0300 Subject: [PATCH 17/18] Upd. TriggersScanner. Improved triggers gathering. --- .../Scanner/DBTrigger/DBTriggerModel.php | 2 ++ .../Scanner/DBTrigger/DBTriggerService.php | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php index 4db93f0c5..b1945191c 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerModel.php @@ -11,6 +11,8 @@ class DBTriggerModel public function run() { try { + DBTriggerService::removeNotExistsTriggers(); + $db_triggers = $this->getDataBaseTriggers(); $db_triggers = $this->removeSentTriggers($db_triggers); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php index ccc913066..36610836b 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php @@ -102,4 +102,29 @@ final public static function deleteTrigger($trigger_name) }); return update_option(self::$option_name, $triggers); } + + /** + * Remove triggers that are not in the database schema, but are in the storage. + * @return void + */ + final public static function removeNotExistsTriggers() + { + $db_triggers = self::getDataBaseTriggers(); + $scanned_triggers = self::loadTriggersStorage(); + + $scanned_triggers_names = array_column($scanned_triggers, 'name'); + $db_triggers_names = array_column($db_triggers, 'TRIGGER_NAME'); + $not_exists_triggers = array_diff($scanned_triggers_names, $db_triggers_names); + // error_log(print_r($db_triggers_names, true)); + // error_log(print_r($scanned_triggers_names, true)); + // error_log(print_r($not_exists_triggers, true)); + + // Update storage with remaining triggers + if (!empty($not_exists_triggers)) { + $updated_triggers = array_filter($scanned_triggers, function ($trigger) use ($not_exists_triggers) { + return !in_array($trigger['name'], $not_exists_triggers); + }); + update_option(self::$option_name, $updated_triggers); + } + } } From 4300de0dd41ddae3812105e5b28abbb79ecdab14 Mon Sep 17 00:00:00 2001 From: svfcode Date: Thu, 22 May 2025 08:43:25 +0300 Subject: [PATCH 18/18] remove debug --- lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php index 36610836b..419a5d7ea 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/DBTrigger/DBTriggerService.php @@ -115,9 +115,6 @@ final public static function removeNotExistsTriggers() $scanned_triggers_names = array_column($scanned_triggers, 'name'); $db_triggers_names = array_column($db_triggers, 'TRIGGER_NAME'); $not_exists_triggers = array_diff($scanned_triggers_names, $db_triggers_names); - // error_log(print_r($db_triggers_names, true)); - // error_log(print_r($scanned_triggers_names, true)); - // error_log(print_r($not_exists_triggers, true)); // Update storage with remaining triggers if (!empty($not_exists_triggers)) {