From 1d2aaf79343190d070689d0cd17ac92272f8ead6 Mon Sep 17 00:00:00 2001 From: svfcode Date: Sat, 16 Nov 2024 12:58:16 +0300 Subject: [PATCH 01/10] Upd. Code. Upd security_log_clear function. --- composer.json | 3 +- psalm.xml | 7 ++ security-malware-firewall.php | 26 ++--- .../Common/Functions/SecurityLogClearTest.php | 94 +++++++++++++++++++ 4 files changed, 111 insertions(+), 19 deletions(-) create mode 100644 tests/Common/Functions/SecurityLogClearTest.php diff --git a/composer.json b/composer.json index 485633e90..b39305840 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "phpunit/phpunit": "^7.5", "squizlabs/php_codesniffer": "3.*", "phpcompatibility/php-compatibility": "@dev", - "yoast/phpunit-polyfills": "^1.0" + "yoast/phpunit-polyfills": "^1.0", + "glomberg/wpdb-unsafe-methods": "^1.0" }, "scripts": { "test": [ diff --git a/psalm.xml b/psalm.xml index 812bc59bd..3065a223c 100644 --- a/psalm.xml +++ b/psalm.xml @@ -64,4 +64,11 @@ + + + + query + get_results + + \ No newline at end of file diff --git a/security-malware-firewall.php b/security-malware-firewall.php index e6cf5f6e6..b27914134 100644 --- a/security-malware-firewall.php +++ b/security-malware-firewall.php @@ -2011,26 +2011,16 @@ function spbc_security_log_clear() return false; } - if ( SPBC_WPMS ) { - $wpdb->query( - "DELETE FROM " . SPBC_TBL_SECURITY_LOG - . " WHERE sent = 1 - AND id NOT IN (" - . implode(',', $remain_ids) . - ")" - . ( $spbc->ms__work_mode == 2 ? '' : ' AND blog_id = ' . get_current_blog_id() ) - . ";" - ); - } else { - $wpdb->query( - "DELETE FROM " . SPBC_TBL_SECURITY_LOG - . " WHERE sent = 1 - AND id NOT IN (" - . implode(',', $remain_ids) . - ");" - ); + $wpms_query_part = ''; + if ( SPBC_WPMS && $spbc->ms__work_mode == 2 ) { + $wpms_query_part = ' AND blog_id = ' . get_current_blog_id(); } + $placeholders = rtrim(str_repeat('%s,', count($remain_ids)), ','); + $query = "DELETE FROM " . SPBC_TBL_SECURITY_LOG . " WHERE sent = 1 AND id NOT IN ($placeholders) " . $wpms_query_part . ";"; + + $wpdb->query($wpdb->prepare($query, $remain_ids)); + return true; } diff --git a/tests/Common/Functions/SecurityLogClearTest.php b/tests/Common/Functions/SecurityLogClearTest.php new file mode 100644 index 000000000..a9822adb5 --- /dev/null +++ b/tests/Common/Functions/SecurityLogClearTest.php @@ -0,0 +1,94 @@ +createTable(SPBC_TBL_SECURITY_LOG); + $this->wpdb = $wpdb; + $this->clearMockData(); + } + + protected function tearDown(): void + { + // Tear down the test environment + $this->wpdb->query("DROP TABLE IF EXISTS " . SPBC_TBL_SECURITY_LOG); + unset($this->wpdb); + } + + protected function insertMockData($count = 60) + { + // Insert mock data into SPBC_TBL_SECURITY_LOG + for ($i = 1; $i <= $count; $i++) { + $this->wpdb->insert( + SPBC_TBL_SECURITY_LOG, + array( + 'datetime' => current_time('mysql'), + 'event' => 'mock_event_' . $i, + 'sent' => 1 + ) + ); + } + } + + protected function clearMockData() + { + $this->wpdb->query("DELETE FROM " . SPBC_TBL_SECURITY_LOG); + } + + public function testSecurityLogClearRemovesOldEntries() + { + $this->insertMockData(); + + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SECURITY_LOG; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(60, $initial_entries); + + // Act: Call the function + spbc_security_log_clear(); + + // Assert: Only 50 entries should remain + $remaining_entries = $this->wpdb->get_var($query); + $this->assertEquals(50, $remaining_entries); + + $this->clearMockData(); + } + + public function testSecurityLogClearHandlesEmptyTable() + { + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SECURITY_LOG; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(0, $initial_entries); + + // Act: Call the function + spbc_security_log_clear(); + + // Assert: Table should still be empty + $remaining_entries = $this->wpdb->get_var("SELECT COUNT(*) FROM " . SPBC_TBL_SECURITY_LOG); + $this->assertEquals(0, $remaining_entries); + } + + public function testSecurityLogClearHandlesLessThan50Entries() + { + $this->insertMockData(30); + + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SECURITY_LOG; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(30, $initial_entries); + + // Act: Call the function + spbc_security_log_clear(); + + // Assert: All 30 entries should remain + $remaining_entries = $this->wpdb->get_var("SELECT COUNT(*) FROM " . SPBC_TBL_SECURITY_LOG); + $this->assertEquals(30, $remaining_entries); + } +} From 140db7d2e813033fb406d74d950da209ef83a5e1 Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 03:48:19 +0300 Subject: [PATCH 02/10] Upd. Code. Updated send_file function. --- inc/spbc-scanner.php | 190 +----------------- .../SpbctWP/Scanner/ScanRepository.php | 56 ++++++ .../SpbctWP/Scanner/ScanResultsRepository.php | 36 ---- .../SpbctWP/Scanner/ScanStorage.php | 80 ++++++++ .../SpbctWP/Scanner/ScannerQueue.php | 3 +- .../Services/SendFileToCloudService.php | 190 ++++++++++++++++++ security-malware-firewall.php | 27 +-- .../Common/Functions/ScannerFileSendTest.php | 70 +++++++ .../Storage/SetFileAsNotPendingQueueTest.php | 79 ++++++++ .../Services/SendFileToCloudServiceMock.php | 24 +++ 10 files changed, 516 insertions(+), 239 deletions(-) create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/ScanRepository.php delete mode 100644 lib/CleantalkSP/SpbctWP/Scanner/ScanResultsRepository.php create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/ScanStorage.php create mode 100644 lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php create mode 100644 tests/Common/Functions/ScannerFileSendTest.php create mode 100644 tests/Common/Scanner/Storage/SetFileAsNotPendingQueueTest.php create mode 100644 tests/Mock/Services/SendFileToCloudServiceMock.php diff --git a/inc/spbc-scanner.php b/inc/spbc-scanner.php index c0720c0e5..66686bc73 100644 --- a/inc/spbc-scanner.php +++ b/inc/spbc-scanner.php @@ -15,6 +15,7 @@ use CleantalkSP\Common\Helpers\Arr; use CleantalkSP\SpbctWP\DTO; use CleantalkSP\Fpdf\Pdf; +use CleantalkSP\SpbctWP\Scanner\Services\SendFileToCloudService; /** * Cron wrapper function for launchBackground @@ -230,198 +231,21 @@ function spbc_scanner_file_remove_from_log($file_id) /** * Send file to Cleantalk Cloud - * @param int $file_id + * @param bool $direct_call + * @param int|string $file_id * @param bool $do_rescan + * @param string $handler * @return array + * @todo: check if $do_rescan is needed */ -function spbc_scanner_file_send_handler($file_id = null, $do_rescan = true) -{ - global $spbc, $wpdb; - - $root_path = spbc_get_root_path(); - - if (!$file_id) { - return array('error' => 'WRONG_FILE_ID'); - } - - // Getting file info. - $sql = 'SELECT fast_hash, path, source_type, source, source_status, version, mtime, weak_spots, full_hash, real_full_hash, status, checked_signatures, checked_heuristic - FROM ' . SPBC_TBL_SCAN_FILES . ' - WHERE fast_hash = "' . $file_id . '" - LIMIT 1'; - $sql_result = $wpdb->get_results($sql, ARRAY_A); - $file_info = $sql_result[0]; - - if (empty($file_info)) { - return array('error' => 'FILE_NOT_FOUND'); - } - - if (!file_exists($root_path . $file_info['path'])) { - $res = spbc_scanner_file_remove_from_log($file_id); - if ($res === false) { - return array( - 'error' => __('File not exists and must be removed from log, but something went wrong.', 'security-malware-firewall'), - 'error_type' => 'FILE_NOT_EXISTS_DB_ERROR' - ); - } - - return array( - 'error' => __('File not exists and will be removed from log.', 'security-malware-firewall'), - 'error_type' => 'FILE_NOT_EXISTS' - ); - } - - if (!is_readable($root_path . $file_info['path'])) { - return array('error' => 'FILE_NOT_READABLE'); - } - - if (filesize($root_path . $file_info['path']) < 1) { - return array('error' => 'FILE_SIZE_ZERO'); - } - - if (filesize($root_path . $file_info['path']) > 1048570) { - return array('error' => 'FILE_SIZE_TOO_LARGE'); - } - - if ($file_info['status'] === 'APPROVED_BY_CT' || $file_info['status'] === 'APPROVED_BY_CLOUD') { - return array('error' => 'IT_IS_IMPOSIBLE_RESEND_APPROVED_FILE'); - } - - if ( $do_rescan ) { - // Scan file before send it - $rescan_results = spbc_scanner_rescan_single_file($file_info['path'], $file_info['full_hash'], $root_path); - if (isset($rescan_results['error'])) { - return array('error' => $rescan_results['error']); - } - - $merged_result = $rescan_results['merged_result']; - - //prepare weakspots for DTO - $file_info['weak_spots'] = $merged_result['weak_spots']; - - //update file in the table - $wpdb->update( - SPBC_TBL_SCAN_FILES, - array( - 'checked_signatures' => $file_info['checked_signatures'], - 'checked_heuristic' => $file_info['checked_heuristic'], - 'status' => $file_info['status'] === 'MODIFIED' ? 'MODIFIED' : $merged_result['status'], - 'severity' => $merged_result['severity'], - 'weak_spots' => json_encode($merged_result['weak_spots']), - 'full_hash' => md5_file($root_path . $file_info['path']), - ), - array('fast_hash' => $file_info['fast_hash']), - array('%s', '%s', '%s', '%s', '%s', '%s'), - array('%s') - ); - } - - // Updating file_info if file source is unknown - if ( ! isset($file_info['version'], $file_info['source'], $file_info['source_type'])) { - $file_info_updated = spbc_get_source_info_of($file_info['path']); - if ($file_info_updated) { - $file_info = array_merge($file_info, $file_info_updated); - } - } - - // prepare file hash - $file_info['full_hash'] = md5_file($root_path . $file_info['path']); - - // Getting file && API call - $file_content = file_get_contents($root_path . $file_info['path']); - try { - $dto = new DTO\MScanFilesDTO( - array( - 'path_to_sfile' => $file_info['path'], - 'attached_sfile' => $file_content, - 'md5sum_sfile' => $file_info['full_hash'], - 'dangerous_code' => $file_info['weak_spots'], - 'version' => $file_info['version'], - 'source' => $file_info['source'], - 'source_type' => $file_info['source_type'], - 'source_status' => $file_info['source_status'], - 'real_hash' => $file_info['real_full_hash'], - 'client_php_version' => phpversion(), - 'auto_send_type' => 'Suspicious', - 'current_scanner_settings' => json_encode($spbc->settings), - 'plugin_heuristic_checked' => $file_info['checked_heuristic'], - 'plugin_signatures_checked' => $file_info['checked_signatures'], - ) - ); - } catch ( \InvalidArgumentException $e ) { - return array('error' => "File can not be send. Error: \n" . substr($e->getMessage(), 0, 100)); - } - - $api_response = SpbcAPI::method__security_pscan_files_send($spbc->settings['spbc_key'], $dto); - - if (!empty($api_response['error'])) { - if ($api_response['error'] === 'QUEUE_FULL') { - //do something with not queued files - $sql_result = $wpdb->query( - 'UPDATE ' . SPBC_TBL_SCAN_FILES - . ' SET' - . ' last_sent = ' . current_time('timestamp') . ',' - . ' pscan_pending_queue = 1' - . ' WHERE fast_hash = "' . $file_id . '"' - ); - - if ($sql_result === false) { - return array('error' => 'DB_COULD_NOT_UPDATE pscan_pending_queue'); - } - - //set new cron to resend unqueued files - \CleantalkSP\SpbctWP\Cron::updateTask( - 'scanner_resend_pscan_files', - 'spbc_scanner_resend_pscan_files', - SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD, - time() + SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD - ); - - return array('success' => true, 'result' => $api_response); - } else { - //out API error if error is not queue_full - return $api_response; - } - } - - if (!isset($api_response['file_id'])) { - return array('error' => 'API_RESPONSE: file_id is NULL'); - } - - // Updating "last_sent" - $sql_result = $wpdb->query( - 'UPDATE ' . SPBC_TBL_SCAN_FILES - . ' SET' - . ' last_sent = ' . current_time('timestamp') . ',' - . ' pscan_processing_status = "NEW",' - . ' pscan_pending_queue = 0,' - . ' pscan_file_id = "' . $api_response["file_id"] . '"' - . ' WHERE fast_hash = "' . $file_id . '"' - ); - - if ($sql_result === false) { - return array('error' => 'DB_COULDNT_UPDATE pscan_processing_status'); - } - - //set new cron to update statuses - \CleantalkSP\SpbctWP\Cron::updateTask( - 'scanner_update_pscan_files_status', - 'spbc_scanner_update_pscan_files_status', - SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD, - time() + SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD - ); - - return array('success' => true, 'result' => $api_response); -} - -function spbc_scanner_file_send($direct_call = false, $file_id = null, $do_rescan = true) +function spbc_scanner_file_send($direct_call = false, $file_id = null, $do_rescan = true, $handler = SendFileToCloudService::class) { if ( ! $direct_call) { spbc_check_ajax_referer('spbc_secret_nonce', 'security'); $file_id = preg_match('@[a-zA-Z0-9]{32}@', Post::get('file_id')) ? Post::get('file_id') : null; } - $output = spbc_scanner_file_send_handler($file_id, $do_rescan); + $output = $handler::sendFile($file_id, $do_rescan); if ( !$direct_call ) { wp_send_json($output); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanRepository.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanRepository.php new file mode 100644 index 000000000..17f16cc91 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScanRepository.php @@ -0,0 +1,56 @@ +get_results( + 'SELECT real_full_hash FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE status="APPROVED_BY_USER"', + ARRAY_A + ); + } + + /** + * Get queued to send to Cleantalk Cloud files + */ + public static function getPendingQueueFiles() + { + global $wpdb; + + return $wpdb->get_results( + 'SELECT fast_hash, status FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE pscan_pending_queue = 1', + ARRAY_A + ); + } + + /** + * Get file info by fast_hash + */ + public static function getFileInfoByFastHash($fast_hash) + { + global $wpdb; + + $sql = $wpdb->prepare( + 'SELECT fast_hash, path, source_type, source, source_status, version, mtime, weak_spots, + full_hash, real_full_hash, status, checked_signatures, checked_heuristic + FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE fast_hash = %s LIMIT 1', + $fast_hash + ); + + $sql_result = $wpdb->get_results($sql, ARRAY_A); + + return $sql_result[0]; + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanResultsRepository.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanResultsRepository.php deleted file mode 100644 index 577de4dc4..000000000 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScanResultsRepository.php +++ /dev/null @@ -1,36 +0,0 @@ -db = DB::getInstance(); - } - - /** - * Get approved hashes - */ - public function getApprovedRealFullHashes() - { - $rows = $this->db->fetchAll( - 'SELECT real_full_hash - FROM ' . SPBC_TBL_SCAN_FILES . ' - WHERE status="APPROVED_BY_USER"' - ); - - return $rows; - } -} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanStorage.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanStorage.php new file mode 100644 index 000000000..ebd88dd12 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScanStorage.php @@ -0,0 +1,80 @@ +prepare( + 'UPDATE ' . SPBC_TBL_SCAN_FILES + . ' SET' + . ' last_sent = ' . current_time('timestamp') . ',' + . ' pscan_pending_queue = 1' + . ' WHERE fast_hash = %s', + $fast_hash + ); + + return $wpdb->query($sql); + } + + /** + * Update file as sent successfully + * + * @param string $fast_hash + * @param string $file_id + * @return bool + */ + public static function updateSendFileStatusAsSuccess($fast_hash, $file_id) + { + global $wpdb; + + $sql = $wpdb->prepare( + 'UPDATE ' . SPBC_TBL_SCAN_FILES + . ' SET' + . ' last_sent = ' . current_time('timestamp') . ',' + . ' pscan_processing_status = "NEW",' + . ' pscan_pending_queue = 0,' + . ' pscan_file_id = %s' + . ' WHERE fast_hash = %s', + $file_id, + $fast_hash + ); + + return $wpdb->query($sql); + } + + /** + * Set file as not pending queue + * + * @param string $fast_hash + * @return void + */ + public static function setFileAsNotPendingQueue($fast_hash) + { + global $wpdb; + + $update_sql = $wpdb->prepare( + 'UPDATE ' . SPBC_TBL_SCAN_FILES + . ' SET ' + . 'pscan_pending_queue = 0 ' + . 'WHERE fast_hash = %s', + $fast_hash + ); + + $wpdb->query($update_sql); + } +} diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 963f508c4..f14d2fcda 100755 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -623,8 +623,7 @@ public function get_modules_hashes($amount = null, $offset = null) // phpcs:igno ); // Remove approved files - $scan_results_repository = new ScanResultsRepository(); - $approved_real_full_hashes = $scan_results_repository->getApprovedRealFullHashes(); + $approved_real_full_hashes = ScanRepository::getApprovedRealFullHashes(); if ($approved_real_full_hashes) { foreach ($result_hashes as $key => $data) { diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php b/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php new file mode 100644 index 000000000..239e83f37 --- /dev/null +++ b/lib/CleantalkSP/SpbctWP/Scanner/Services/SendFileToCloudService.php @@ -0,0 +1,190 @@ + 'WRONG_FILE_ID'); + } + + $file_info = ScanRepository::getFileInfoByFastHash($file_id); + if (empty($file_info)) { + return array('error' => 'FILE_NOT_FOUND'); + } + + $root_path = spbc_get_root_path(); + + // if file not exists, remove it from the database + if (!file_exists($root_path . $file_info['path'])) { + $res = spbc_scanner_file_remove_from_log($file_id); + if ($res === false) { + return array( + 'error' => __('File not exists and must be removed from log, but something went wrong.', 'security-malware-firewall'), + 'error_type' => 'FILE_NOT_EXISTS_DB_ERROR' + ); + } + + return array( + 'error' => __('File not exists and will be removed from log.', 'security-malware-firewall'), + 'error_type' => 'FILE_NOT_EXISTS' + ); + } + + if (!is_readable($root_path . $file_info['path'])) { + return array('error' => 'FILE_NOT_READABLE'); + } + + if (filesize($root_path . $file_info['path']) < 1) { + return array('error' => 'FILE_SIZE_ZERO'); + } + + if (filesize($root_path . $file_info['path']) > 1048570) { + return array('error' => 'FILE_SIZE_TOO_LARGE'); + } + + if ($file_info['status'] === 'APPROVED_BY_CT' || $file_info['status'] === 'APPROVED_BY_CLOUD') { + return array('error' => 'IT_IS_IMPOSIBLE_RESEND_APPROVED_FILE'); + } + + // @todo: check if $do_rescan is needed, because it's used for every file now. + if ( $do_rescan ) { + // Scan file before send it + $rescan_results = spbc_scanner_rescan_single_file($file_info['path'], $file_info['full_hash'], $root_path); + if (isset($rescan_results['error'])) { + return array('error' => $rescan_results['error']); + } + + $merged_result = $rescan_results['merged_result']; + + // prepare weakspots for DTO + $file_info['weak_spots'] = $merged_result['weak_spots']; + + // update file in the table + $wpdb->update( + SPBC_TBL_SCAN_FILES, + array( + 'checked_signatures' => $file_info['checked_signatures'], + 'checked_heuristic' => $file_info['checked_heuristic'], + 'status' => $file_info['status'] === 'MODIFIED' ? 'MODIFIED' : $merged_result['status'], + 'severity' => $merged_result['severity'], + 'weak_spots' => json_encode($merged_result['weak_spots']), + 'full_hash' => md5_file($root_path . $file_info['path']), + ), + array('fast_hash' => $file_info['fast_hash']), + array('%s', '%s', '%s', '%s', '%s', '%s'), + array('%s') + ); + } + + // Updating file_info if file source is unknown + if ( ! isset($file_info['version'], $file_info['source'], $file_info['source_type'])) { + $file_info_updated = spbc_get_source_info_of($file_info['path']); + if ($file_info_updated) { + $file_info = array_merge($file_info, $file_info_updated); + } + } + + // prepare file hash + $file_info['full_hash'] = md5_file($root_path . $file_info['path']); + + // Getting file && API call + $file_content = file_get_contents($root_path . $file_info['path']); + try { + $dto = new MScanFilesDTO( + array( + 'path_to_sfile' => $file_info['path'], + 'attached_sfile' => $file_content, + 'md5sum_sfile' => $file_info['full_hash'], + 'dangerous_code' => $file_info['weak_spots'], + 'version' => $file_info['version'], + 'source' => $file_info['source'], + 'source_type' => $file_info['source_type'], + 'source_status' => $file_info['source_status'], + 'real_hash' => $file_info['real_full_hash'], + 'client_php_version' => phpversion(), + 'auto_send_type' => 'Suspicious', + 'current_scanner_settings' => json_encode($spbc->settings), + 'plugin_heuristic_checked' => $file_info['checked_heuristic'], + 'plugin_signatures_checked' => $file_info['checked_signatures'], + ) + ); + } catch ( \InvalidArgumentException $e ) { + return array('error' => "File can not be send. Error: \n" . substr($e->getMessage(), 0, 100)); + } + + $api_response = SpbcAPI::method__security_pscan_files_send($spbc->settings['spbc_key'], $dto); + + if (!empty($api_response['error'])) { + return self::handleApiError($api_response, $file_id); + } + + if (!isset($api_response['file_id'])) { + return array('error' => 'API_RESPONSE: file_id is NULL'); + } + + $sql_result = ScanStorage::updateSendFileStatusAsSuccess($file_id, $api_response['file_id']); + + if ($sql_result === false) { + return array('error' => 'DB_COULDNT_UPDATE pscan_processing_status'); + } + + self::addCronToUpdateStatuses(); + + return array('success' => true, 'result' => $api_response); + } + + protected static function handleApiError($api_response, $file_id) + { + if ($api_response['error'] === 'QUEUE_FULL') { + $sql_result = ScanStorage::setFileAsPendingQueue($file_id); + + if ($sql_result === false) { + return array('error' => 'DB_COULD_NOT_UPDATE pscan_pending_queue'); + } + + self::addCronToResendUnqueuedFiles(); + + return array('success' => true, 'result' => $api_response); + } else { + // out API error if error is not queue_full + return $api_response; + } + } + + private static function addCronToUpdateStatuses() + { + \CleantalkSP\SpbctWP\Cron::updateTask( + 'scanner_update_pscan_files_status', + 'spbc_scanner_update_pscan_files_status', + SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD, + time() + SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD + ); + } + + private static function addCronToResendUnqueuedFiles() + { + \CleantalkSP\SpbctWP\Cron::updateTask( + 'scanner_resend_pscan_files', + 'spbc_scanner_resend_pscan_files', + SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD, + time() + SPBC_PSCAN_RESEND_FILES_STATUS_PERIOD + ); + } +} diff --git a/security-malware-firewall.php b/security-malware-firewall.php index b27914134..f45ef0fee 100644 --- a/security-malware-firewall.php +++ b/security-malware-firewall.php @@ -37,6 +37,8 @@ use CleantalkSP\SpbctWP\Helpers\IP; use CleantalkSP\SpbctWP\Helpers\HTTP; use CleantalkSP\SpbctWP\API as SpbcAPI; +use CleantalkSP\SpbctWP\Scanner\ScanRepository; +use CleantalkSP\SpbctWP\Scanner\ScanStorage; use CleantalkSP\SpbctWP\VulnerabilityAlarm\VulnerabilityAlarm; // Prevent direct call @@ -1837,27 +1839,16 @@ function spbc_scanner_update_pscan_files_status() */ function spbc_scanner_resend_pscan_files($do_rescan = true) { - global $wpdb; - // Reading DB for unqueued files - $unqueued_files_list = $wpdb->get_results( - 'SELECT fast_hash, status' - . ' FROM ' . SPBC_TBL_SCAN_FILES - . ' WHERE pscan_pending_queue = 1', - ARRAY_A - ); + $pending_queue_files = ScanRepository::getPendingQueueFiles(); - if ( !empty($unqueued_files_list) ) { - foreach ( $unqueued_files_list as $file ) { - //fix for files sent to manual analysis - if ( !empty($file['status']) && $file['status'] === 'APPROVED_BY_CT') { - $update_sql = - 'UPDATE ' . SPBC_TBL_SCAN_FILES - . ' SET ' - . 'pscan_pending_queue = 0 ' - . 'WHERE fast_hash = "' . $file['fast_hash'] . '"'; - $wpdb->query($update_sql); + if (!empty($pending_queue_files)) { + foreach ($pending_queue_files as $file) { + // fix for files sent to manual analysis + if (!empty($file['status']) && $file['status'] === 'APPROVED_BY_CT') { + ScanStorage::setFileAsNotPendingQueue($file['fast_hash']); continue; } + spbc_scanner_file_send(true, $file['fast_hash'], $do_rescan); } } else { diff --git a/tests/Common/Functions/ScannerFileSendTest.php b/tests/Common/Functions/ScannerFileSendTest.php new file mode 100644 index 000000000..9b02b207d --- /dev/null +++ b/tests/Common/Functions/ScannerFileSendTest.php @@ -0,0 +1,70 @@ +createTable(SPBC_TBL_SCAN_FILES); + $this->wpdb = $wpdb; + $this->clearMockData(); + } + + protected function tearDown(): void + { + // Tear down the test environment + $this->wpdb->query("DROP TABLE IF EXISTS " . SPBC_TBL_SCAN_FILES); + unset($this->wpdb); + } + + protected function insertMockData($case = 'queue_full', $count = 60) + { + // Insert mock data into SPBC_TBL_SCAN_FILES + switch ($case) { + case 'queue_full': + for ($i = 1; $i <= $count; $i++) { + $this->wpdb->insert( + SPBC_TBL_SCAN_FILES, + array( + 'fast_hash' => 'mock_fast_hash_' . $i, + 'pscan_pending_queue' => 1 + ) + ); + } + } + } + + protected function clearMockData() + { + $this->wpdb->query("DELETE FROM " . SPBC_TBL_SCAN_FILES); + } + + public function testFileSendToCloudHandleApiErrorQueueFull() + { + $this->insertMockData('queue_full'); + + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SCAN_FILES; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(60, $initial_entries); + + $file_hash = "SELECT fast_hash FROM " . SPBC_TBL_SCAN_FILES . " LIMIT 1"; + $file_hash = $this->wpdb->get_var($file_hash); + + // Act: Call the function + spbc_scanner_file_send(true, $file_hash, true, SendFileToCloudServiceMock::class); + + // Assert: Only 50 entries should remain + $remaining_entries = $this->wpdb->get_var($query); + $this->assertEquals(60, $remaining_entries); + + $this->clearMockData(); + } +} diff --git a/tests/Common/Scanner/Storage/SetFileAsNotPendingQueueTest.php b/tests/Common/Scanner/Storage/SetFileAsNotPendingQueueTest.php new file mode 100644 index 000000000..1798ceeed --- /dev/null +++ b/tests/Common/Scanner/Storage/SetFileAsNotPendingQueueTest.php @@ -0,0 +1,79 @@ +createTable(SPBC_TBL_SCAN_FILES); + $this->wpdb = $wpdb; + $this->clearMockData(); + } + + protected function tearDown(): void + { + // Tear down the test environment + $this->wpdb->query("DROP TABLE IF EXISTS " . SPBC_TBL_SCAN_FILES); + unset($this->wpdb); + } + + protected function insertMockData($count = 60) + { + // Insert mock data into SPBC_TBL_SCAN_FILES + for ($i = 1; $i <= $count; $i++) { + $this->wpdb->insert( + SPBC_TBL_SCAN_FILES, + array( + 'fast_hash' => 'mock_fast_hash_' . $i, + 'pscan_pending_queue' => 1 + ) + ); + } + } + + protected function clearMockData() + { + $this->wpdb->query("DELETE FROM " . SPBC_TBL_SCAN_FILES); + } + + public function testSetFileAsNotPendingQueue() + { + $this->insertMockData(); + + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SCAN_FILES; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(60, $initial_entries); + + // Act: Call the function + for ($i = 1; $i <= 60; $i++) { + ScanStorage::setFileAsNotPendingQueue('mock_fast_hash_' . $i); + } + + $remaining_entries = $this->wpdb->get_var($query); + $this->assertEquals(60, $remaining_entries); + + $this->clearMockData(); + } + + public function testSetFileAsNotPendingQueueEmptyTable() + { + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SCAN_FILES; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(0, $initial_entries); + + // Act: Call the function + ScanStorage::setFileAsNotPendingQueue('mock_fast_hash_1'); + + // Assert: Table should still be empty + $remaining_entries = $this->wpdb->get_var("SELECT COUNT(*) FROM " . SPBC_TBL_SCAN_FILES); + $this->assertEquals(0, $remaining_entries); + } +} diff --git a/tests/Mock/Services/SendFileToCloudServiceMock.php b/tests/Mock/Services/SendFileToCloudServiceMock.php new file mode 100644 index 000000000..51f9eca9c --- /dev/null +++ b/tests/Mock/Services/SendFileToCloudServiceMock.php @@ -0,0 +1,24 @@ + 'QUEUE_FULL'), $file_id); + } + } +} From 087e3523f146875bbd96a96e60e232ee5f2f0c85 Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 14:43:09 +0300 Subject: [PATCH 03/10] Upd. Code. Added suppress for simple cases. --- lib/CleantalkSP/SpbctWP/Deactivator.php | 26 +++++++++++++++++++ lib/CleantalkSP/SpbctWP/Scanner/Frontend.php | 1 + lib/CleantalkSP/SpbctWP/Scanner/Links.php | 1 + .../SpbctWP/Variables/AltSessions.php | 5 ++-- lib/CleantalkSP/Updater/UpdaterScripts.php | 15 +++++++++++ security-malware-firewall.php | 1 + 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Deactivator.php b/lib/CleantalkSP/SpbctWP/Deactivator.php index 56c0d9ec6..e4d91c580 100644 --- a/lib/CleantalkSP/SpbctWP/Deactivator.php +++ b/lib/CleantalkSP/SpbctWP/Deactivator.php @@ -263,18 +263,31 @@ private static function deleteFrontendMeta() public static function deleteBlogTables() //ok { global $wpdb; + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_auth_logs'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_monitoring_users'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips_v4'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips_v6'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_countries'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips_v4_temp'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips_v6_temp'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_countries_temp'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall_logs'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_traffic_control_logs'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_traffic_control_logs'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_bfp_blocked'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_sessions'); } @@ -285,18 +298,31 @@ public static function deleteBlogTables() //ok public static function deleteCommonTables() //ok { global $wpdb; + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_results'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_firewall_data_v4'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_firewall_data_v6'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_firewall_data_v4_temp'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_firewall_data_v6_temp'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_links_logs'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_signatures'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_frontend'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_backups'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_backuped_files'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_results_log'); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_cure_log'); + // @psalm-suppress WpdbUnsafeMethodsIssue } /** diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Frontend.php b/lib/CleantalkSP/SpbctWP/Scanner/Frontend.php index f4ca4720a..d6ba31767 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Frontend.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Frontend.php @@ -390,6 +390,7 @@ public static function resetCheckResult() global $wpdb; $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_spbc_frontend__last_checked' OR meta_key = 'spbc_frontend__last_checked';"); + // @psalm-suppress WpdbUnsafeMethodsIssue return $wpdb->query('DELETE FROM ' . SPBC_TBL_SCAN_FRONTEND . ';'); } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Links.php b/lib/CleantalkSP/SpbctWP/Scanner/Links.php index 39365ec2c..3e66460b4 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Links.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Links.php @@ -280,6 +280,7 @@ public static function resetCheckResult() global $wpdb; $wpdb->query("DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_spbc_links_checked';"); + // @psalm-suppress WpdbUnsafeMethodsIssue return $wpdb->query('DELETE FROM ' . SPBC_TBL_SCAN_LINKS . ';'); } } diff --git a/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php b/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php index 5b31b3b8a..a29cfaf82 100644 --- a/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php +++ b/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php @@ -159,8 +159,7 @@ public static function wipe() { global $wpdb; - return $wpdb->query( - 'TRUNCATE TABLE ' . SPBC_TBL_SESSIONS . ';' - ); + // @psalm-suppress WpdbUnsafeMethodsIssue + return $wpdb->query('TRUNCATE TABLE ' . SPBC_TBL_SESSIONS . ';'); } } diff --git a/lib/CleantalkSP/Updater/UpdaterScripts.php b/lib/CleantalkSP/Updater/UpdaterScripts.php index e7cfb7fb7..e171accb7 100644 --- a/lib/CleantalkSP/Updater/UpdaterScripts.php +++ b/lib/CleantalkSP/Updater/UpdaterScripts.php @@ -180,12 +180,14 @@ public static function updateTo_2_22_0() //phpcs:ignore PSR1.Methods.CamelCapsMe global $wpdb, $spbc, $wp_version; // Set source_type = null for custom files + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query( "UPDATE `" . SPBC_TBL_SCAN_FILES . "` SET source_type = NULL WHERE source_type = 'CORE' && real_full_hash IS NULL;" ); // Set source = wordpress and version for core files + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query( "UPDATE `" . SPBC_TBL_SCAN_FILES . "` SET source = 'wordpress', @@ -199,6 +201,7 @@ public static function updateTo_2_22_0() //phpcs:ignore PSR1.Methods.CamelCapsMe } foreach ( $spbc->plugins as $name => $version ) { + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query( "UPDATE `" . SPBC_TBL_SCAN_FILES . "` SET source = '$name', @@ -213,6 +216,7 @@ public static function updateTo_2_22_0() //phpcs:ignore PSR1.Methods.CamelCapsMe } foreach ( $spbc->themes as $name => $version ) { + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query( "UPDATE `" . SPBC_TBL_SCAN_FILES . "` SET source_type = 'THEME', @@ -221,6 +225,8 @@ public static function updateTo_2_22_0() //phpcs:ignore PSR1.Methods.CamelCapsMe WHERE path LIKE '%$name%' && real_full_hash IS NOT NULL;" ); } + + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query( "UPDATE `" . SPBC_TBL_SCAN_FILES . "` SET checked = 'YES_HEURISTIC' @@ -238,6 +244,7 @@ public static function updateTo_2_24_0() //phpcs:ignore PSR1.Methods.CamelCapsMe { global $wpdb; + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query( "UPDATE `" . SPBC_TBL_SCAN_FILES . "` SET weak_spots = NULL, @@ -1248,12 +1255,15 @@ public static function updateTo_2_126_0() //phpcs:ignore PSR1.Methods.CamelCapsM // Deleting data from each blog foreach ( $blogs as $blog ) { switch_to_blog($blog); + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips'); } switch_to_blog($initial_blog); } else { + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips'); } + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_firewall_data'); } @@ -1307,8 +1317,10 @@ public static function migrateDbData_2_128_1() //phpcs:ignore PSR1.Methods.Camel foreach ( $blogs as $blog ) { switch_to_blog($blog); $table_name = $wpdb->prefix . 'spbc_scan_results'; + // @psalm-suppress WpdbUnsafeMethodsIssue $exist = $wpdb->query("SHOW TABLES LIKE '" . $table_name . "';"); if (!empty($exist)) { + // @psalm-suppress WpdbUnsafeMethodsIssue $field_exist = $wpdb->query("SHOW COLUMNS FROM " . $table_name . " LIKE 'analysis_status';"); if (!empty($field_exist)) { $wpdb->query(sprintf($sql, $table_name)); @@ -1318,8 +1330,11 @@ public static function migrateDbData_2_128_1() //phpcs:ignore PSR1.Methods.Camel switch_to_blog($initial_blog); } else { $table_name = $wpdb->prefix . 'spbc_scan_results'; + // @psalm-suppress WpdbUnsafeMethodsIssue $exist = $wpdb->query("SHOW TABLES LIKE '" . $table_name . "';"); if (!empty($exist)) { + + // @psalm-suppress WpdbUnsafeMethodsIssue $field_exist = $wpdb->query("SHOW COLUMNS FROM " . $table_name . " LIKE 'analysis_status';"); if (!empty($field_exist)) { $wpdb->query(sprintf($sql, $table_name)); diff --git a/security-malware-firewall.php b/security-malware-firewall.php index f45ef0fee..0d7540aa7 100644 --- a/security-malware-firewall.php +++ b/security-malware-firewall.php @@ -774,6 +774,7 @@ function spbc_security_firewall_drop() { global $wpdb; + // @psalm-suppress WpdbUnsafeMethodsIssue $result = $wpdb->query('DELETE FROM `' . SPBC_TBL_FIREWALL_DATA . '`;'); if ( $result !== false ) { From ed99b24962a9ada1a97ff3f12a035a66a4f9350a Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 18:45:14 +0300 Subject: [PATCH 04/10] Upd. Code. Updated signatures repository. --- lib/CleantalkSP/SpbctWP/Deactivator.php | 14 +- .../Stages/SignatureAnalysis/Repository.php | 54 +++---- .../SpbctWP/Variables/AltSessions.php | 10 +- lib/CleantalkSP/Updater/UpdaterScripts.php | 1 - tests/Common/DeactivatorTest.php | 67 +++++++++ .../Signatures/SignaturesUpdateTest.php | 134 ++++++++++++++++++ tests/Common/Variables/AltSessionsTest.php | 84 +++++++++++ 7 files changed, 318 insertions(+), 46 deletions(-) create mode 100644 tests/Common/DeactivatorTest.php create mode 100644 tests/Common/Scanner/Signatures/SignaturesUpdateTest.php create mode 100644 tests/Common/Variables/AltSessionsTest.php diff --git a/lib/CleantalkSP/SpbctWP/Deactivator.php b/lib/CleantalkSP/SpbctWP/Deactivator.php index e4d91c580..ad5d3cdce 100644 --- a/lib/CleantalkSP/SpbctWP/Deactivator.php +++ b/lib/CleantalkSP/SpbctWP/Deactivator.php @@ -341,15 +341,17 @@ public static function muPluginUninstall() /** * Deletes current individual blog options. */ - public static function deleteBlogOptions() //APBCT + public static function deleteBlogOptions() { global $wpdb; // Deleting all data from wp_options $wpdb->query( - 'DELETE FROM ' . $wpdb->options - . ' WHERE' - . ' option_name LIKE "spbc_%" AND' - . ' option_name <> "spbc_deactivation_in_process"' + $wpdb->prepare( + 'DELETE FROM ' . $wpdb->options + . ' WHERE option_name LIKE %s AND option_name <> %s', + '%spbc_%', + 'spbc_deactivation_in_process' + ) ); } @@ -357,7 +359,7 @@ public static function deleteBlogOptions() //APBCT * Deletes update folders * @return void */ - private static function deleteSecFWUpdateFolder() //APBCT + private static function deleteSecFWUpdateFolder() { $current_blog_id = get_current_blog_id(); $wp_upload_dir = wp_upload_dir(); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Stages/SignatureAnalysis/Repository.php b/lib/CleantalkSP/SpbctWP/Scanner/Stages/SignatureAnalysis/Repository.php index b37446c85..fc16bf0c2 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Stages/SignatureAnalysis/Repository.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Stages/SignatureAnalysis/Repository.php @@ -81,38 +81,31 @@ public static function getSignaturesFromCloud($latest_signature_local) public static function clearSignaturesTable() { global $wpdb; - $wpdb->query('DELETE FROM ' . SPBC_TBL_SCAN_SIGNATURES . ' WHERE 1;'); + + // @psalm-suppress WpdbUnsafeMethodsIssue + $wpdb->query('DELETE FROM ' . SPBC_TBL_SCAN_SIGNATURES . ';'); } public static function addSignaturesToDb($map, $signatures) { global $wpdb; - $sql_head = 'INSERT INTO ' . SPBC_TBL_SCAN_SIGNATURES - . ' (' . implode(',', $map) . ')' - . ' VALUES '; + $sql_data = array(); - $sql_tail = ' ON DUPLICATE KEY UPDATE ' - . 'submitted = submitted;'; foreach ( $signatures as $signature ) { /** @psalm-suppress InvalidArgument */ - $tmp = implode( - ',', - array_map( - function ($elem) { - return Helper::prepareParamForSQLQuery(stripslashes($elem ?: 'null')); - }, - $signature - ) - ); + $tmp = implode(',', array_map(function ($elem) { + return Helper::prepareParamForSQLQuery(stripslashes($elem ?: 'null')); + }, $signature)); $sql_data[] = "($tmp)"; } - $query = - $sql_head + $query = 'INSERT INTO ' . SPBC_TBL_SCAN_SIGNATURES . ' (' . implode(',', $map) . ')' . ' VALUES ' . implode(',', $sql_data) - . $sql_tail; + . ' ON DUPLICATE KEY UPDATE submitted = submitted;'; + // suppress because data is already prepared in Helper::prepareParamForSQLQuery method + // @psalm-suppress WpdbUnsafeMethodsIssue return $wpdb->query($query); } @@ -130,31 +123,22 @@ public static function thereAreSignaturesInDb() public static function addSignaturesToDbOneByOne($map, $signatures) { global $wpdb; - $sql_head = 'INSERT INTO ' . SPBC_TBL_SCAN_SIGNATURES - . ' (' . implode(',', $map) . ')' - . ' VALUES '; - $sql_tail = ' ON DUPLICATE KEY UPDATE ' - . 'submitted = submitted;'; + $bad_signatures = array(); foreach ( $signatures as $signature ) { /** @psalm-suppress InvalidArgument */ - $tmp = implode( - ',', - array_map( - function ($elem) { - return Helper::prepareParamForSQLQuery(stripslashes($elem ?: 'null')); - }, - $signature - ) - ); + $tmp = implode(',', array_map(function ($elem) { + return Helper::prepareParamForSQLQuery(stripslashes($elem ?: 'null')); + }, $signature)); $sql_data = "($tmp)"; - $query = - $sql_head + $query = 'INSERT INTO ' . SPBC_TBL_SCAN_SIGNATURES . ' (' . implode(',', $map) . ')' . ' VALUES ' . $sql_data - . $sql_tail; + . ' ON DUPLICATE KEY UPDATE submitted = submitted;'; + // suppress because data is already prepared in Helper::prepareParamForSQLQuery method + // @psalm-suppress WpdbUnsafeMethodsIssue $signature_added = $wpdb->query($query); if (!$signature_added) { diff --git a/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php b/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php index a29cfaf82..ddc0e1387 100644 --- a/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php +++ b/lib/CleantalkSP/SpbctWP/Variables/AltSessions.php @@ -141,10 +141,12 @@ public static function cleanFromOld() self::$sessions_already_cleaned = true; $wpdb->query( - 'DELETE - FROM `' . SPBC_TBL_SESSIONS . '` - WHERE last_update < NOW() - INTERVAL ' . self::SESSION__LIVE_TIME . ' SECOND - LIMIT 100000;' + $wpdb->prepare( + 'DELETE FROM ' . SPBC_TBL_SESSIONS . ' + WHERE last_update < NOW() - INTERVAL %d SECOND + LIMIT 100000;', + self::SESSION__LIVE_TIME + ) ); } } diff --git a/lib/CleantalkSP/Updater/UpdaterScripts.php b/lib/CleantalkSP/Updater/UpdaterScripts.php index e171accb7..c501dcf62 100644 --- a/lib/CleantalkSP/Updater/UpdaterScripts.php +++ b/lib/CleantalkSP/Updater/UpdaterScripts.php @@ -1333,7 +1333,6 @@ public static function migrateDbData_2_128_1() //phpcs:ignore PSR1.Methods.Camel // @psalm-suppress WpdbUnsafeMethodsIssue $exist = $wpdb->query("SHOW TABLES LIKE '" . $table_name . "';"); if (!empty($exist)) { - // @psalm-suppress WpdbUnsafeMethodsIssue $field_exist = $wpdb->query("SHOW COLUMNS FROM " . $table_name . " LIKE 'analysis_status';"); if (!empty($field_exist)) { diff --git a/tests/Common/DeactivatorTest.php b/tests/Common/DeactivatorTest.php new file mode 100644 index 000000000..e9e4fec1b --- /dev/null +++ b/tests/Common/DeactivatorTest.php @@ -0,0 +1,67 @@ +wpdb = $wpdb; + $this->clearMockData(); + } + + protected function tearDown(): void + { + // Tear down the test environment + unset($this->wpdb); + } + + protected function insertMockData($count = 60) + { + for ($i = 1; $i <= $count; $i++) { + $this->wpdb->insert( + $this->wpdb->options, + ['option_name' => 'spbc_test_option_name_' . $i] + ); + } + + $this->wpdb->insert( + $this->wpdb->options, + ['option_name' => 'spbc_deactivation_in_process'] + ); + } + + protected function clearMockData() + { + $this->wpdb->query( + $this->wpdb->prepare( + "DELETE FROM " . $this->wpdb->options . " WHERE option_name LIKE %s", + '%spbc_%' + ) + ); + } + + public function testDeleteBlogOptions() + { + $this->insertMockData(); + + $query = "SELECT COUNT(*) FROM " . $this->wpdb->options . " WHERE option_name LIKE '%spbc_%'"; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(61, $initial_entries); + + // Act: Call the function + Deactivator::deleteBlogOptions(); + + // Assert: Only 1 entry should remain + $entries = $this->wpdb->get_var($query); + $this->assertEquals(1, $entries); + + $this->clearMockData(); + } +} diff --git a/tests/Common/Scanner/Signatures/SignaturesUpdateTest.php b/tests/Common/Scanner/Signatures/SignaturesUpdateTest.php new file mode 100644 index 000000000..8951aab3c --- /dev/null +++ b/tests/Common/Scanner/Signatures/SignaturesUpdateTest.php @@ -0,0 +1,134 @@ +createTable(SPBC_TBL_SCAN_SIGNATURES); + $this->wpdb = $wpdb; + $this->clearMockData(); + + $this->default_map = [ + 'id', + 'name', + 'body', + 'type', + 'attack_type', + 'waf_action', + 'submitted', + 'cci', + 'waf_headers', + 'waf_url', + ]; + + $this->test_signatures = [ + [ + 'id' => 30, + 'name' => 'dk', + 'body' => '52063944af6710d83d977b1ea817920c', + 'type' => 'FILE', + 'attack_type' => 'MALWARE', + 'waf_action' => 'DENY', + 'submitted' => '2018-11-02 17:05:42', + 'cci' => '{"cci":[{"objects":[{"file":"self"}],"action":{"delete":"true"}}]}', + 'waf_headers' => '', + 'waf_url' => '', + ], + [ + 'id' => 32, + 'name' => 'Pass', + 'body' => '9a470b89e3fb537c410d7a4488fd69f9', + 'type' => 'FILE', + 'attack_type' => 'MALWARE', + 'waf_action' => 'DENY', + 'submitted' => '2018-11-02 17:06:17', + 'cci' => '{"cci":[{"objects":[{"file":"self"}],"action":{"delete":"true"}}]}', + 'waf_headers' => '', + 'waf_url' => '', + ], + [ + 'id' => 38, + 'name' => 'FTP', + 'body' => 'cb92b1b0c32b1d209394ea9328e1c8ed', + 'type' => 'FILE', + 'attack_type' => 'MALWARE', + 'waf_action' => 'DENY', + 'submitted' => '2018-11-02 17:07:37', + 'cci' => '{"cci":[{"objects":[{"file":"self"}],"action":{"delete":"true"}}]}', + 'waf_headers' => '', + 'waf_url' => '', + ], + ]; + } + + protected function tearDown(): void + { + // Tear down the test environment + $this->wpdb->query("DROP TABLE IF EXISTS " . SPBC_TBL_SCAN_SIGNATURES); + unset($this->wpdb); + } + + protected function insertMockData($count = 60) + { + // Insert mock data into SPBC_TBL_SCAN_SIGNATURES + for ($i = 1; $i <= $count; $i++) { + $this->wpdb->insert( + SPBC_TBL_SCAN_SIGNATURES, + [ + 'id' => $i, + 'name' => 'test_name_' . $i, + 'body' => 'test_body_' . $i, + 'type' => 'CODE_PHP', + 'attack_type' => 'XSS', + 'submitted' => time() + ] + ); + } + } + + protected function clearMockData() + { + $this->wpdb->query("DELETE FROM " . SPBC_TBL_SCAN_SIGNATURES); + } + + public function testClearSignaturesTable() + { + $this->insertMockData(); + + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SCAN_SIGNATURES; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(60, $initial_entries); + + // Act: Call the function + \CleantalkSP\SpbctWP\Scanner\Stages\SignatureAnalysis\Repository::clearSignaturesTable(); + + // Assert: Only 0 entries should remain + $entries = $this->wpdb->get_var($query); + $this->assertEquals(0, $entries); + + $this->clearMockData(); + } + + public function testAddSignaturesToDb() + { + // Act: Call the function + \CleantalkSP\SpbctWP\Scanner\Stages\SignatureAnalysis\Repository::addSignaturesToDb($this->default_map, $this->test_signatures); + + // Assert: Only 3 entries should remain + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SCAN_SIGNATURES; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(3, $initial_entries); + + $this->clearMockData(); + } +} diff --git a/tests/Common/Variables/AltSessionsTest.php b/tests/Common/Variables/AltSessionsTest.php new file mode 100644 index 000000000..048e91672 --- /dev/null +++ b/tests/Common/Variables/AltSessionsTest.php @@ -0,0 +1,84 @@ +createTable(SPBC_TBL_SESSIONS); + $this->wpdb = $wpdb; + $this->clearMockData(); + } + + protected function tearDown(): void + { + // Tear down the test environment + $this->wpdb->query("DROP TABLE IF EXISTS " . SPBC_TBL_SESSIONS); + unset($this->wpdb); + } + + protected function insertMockData($count = 60) + { + // Insert mock data into SPBC_TBL_SESSIONS (old data) + for ($i = 1; $i <= $count; $i++) { + $this->wpdb->insert( + SPBC_TBL_SESSIONS, + [ + 'id' => $i, + 'name' => 'test_name_' . $i, + 'last_update' => date('Y-m-d H:i:s', time() - $i * 10 - AltSessions::SESSION__LIVE_TIME) + ] + ); + } + + // Insert mock data into SPBC_TBL_SESSIONS (fresh data) + for ($i = 1; $i <= 10; $i++) { + $this->wpdb->insert( + SPBC_TBL_SESSIONS, + [ + 'id' => 'test_id_' . $i, + 'name' => 'test_name_' . $i, + 'last_update' => date('Y-m-d H:i:s', time() - $i * 10) + ] + ); + } + } + + protected function clearMockData() + { + $this->wpdb->query("DELETE FROM " . SPBC_TBL_SESSIONS); + } + + public function testCleanFromOld() + { + $this->insertMockData(); + + $query = "SELECT COUNT(*) FROM " . SPBC_TBL_SESSIONS; + $initial_entries = $this->wpdb->get_var($query); + $this->assertEquals(70, $initial_entries); + + // Act: Call the function + $counter_of_cleaned_tries = 0; + while (!AltSessions::$sessions_already_cleaned) { + AltSessions::cleanFromOld(); + $counter_of_cleaned_tries++; + } + + // Assert: Only 10 entries should remain + $entries = $this->wpdb->get_var($query); + $this->assertEquals(10, $entries); + + // Assert: The function was called 100 times or less + $this->assertLessThanOrEqual(100, $counter_of_cleaned_tries); + + $this->clearMockData(); + } +} From 3de107cc39bc1e7cbfcdfe6c186b28a72b054dae Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 21:18:09 +0300 Subject: [PATCH 05/10] Upd. Code. Updated backup handler. --- inc/spbc-backups.php | 170 +++++++++++++++++++++++++------------------ 1 file changed, 101 insertions(+), 69 deletions(-) diff --git a/inc/spbc-backups.php b/inc/spbc-backups.php index 177eca714..459b6693f 100644 --- a/inc/spbc-backups.php +++ b/inc/spbc-backups.php @@ -7,6 +7,8 @@ function spbc_backup__rotate($type = 'signatures', $out = array('success' => tru global $wpdb; $result = $wpdb->get_row('SELECT COUNT(*) as cnt FROM ' . SPBC_TBL_BACKUPS . ' WHERE type = ' . Helper::prepareParamForSQLQuery(strtoupper($type)), OBJECT); if ($result->cnt > 10) { + // suppress because data is already prepared in Helper::prepareParamForSQLQuery method + // @psalm-suppress WpdbUnsafeMethodsIssue $result = $wpdb->get_results( 'SELECT backup_id' . ' FROM ' . SPBC_TBL_BACKUPS @@ -79,96 +81,126 @@ function spbc_backup__delete($direct_call = false, $backup_id = null) return $output; } -function spbc_backup__files_with_signatures($direct_call = false) +/** + * Make backup of files with signatures handler + * @return array + */ +function spbc_backup__files_with_signatures_handler() { - if ( ! $direct_call) { - spbc_check_ajax_referer('spbc_secret_nonce', 'security'); - } - global $wpdb, $spbc; + $output = array('success' => true); + $files_to_backup = $wpdb->get_results('SELECT path, weak_spots FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE weak_spots LIKE "%\"SIGNATURES\":%";', ARRAY_A); - if (is_array($files_to_backup) && count($files_to_backup)) { - $sql_query = 'INSERT INTO ' . SPBC_TBL_BACKUPED_FILES . ' (backup_id, real_path, back_path) VALUES'; - $sql_data = array(); + if (!is_array($files_to_backup) || !count($files_to_backup)) { + $output = array('success' => true); + return $output; + } - foreach ($files_to_backup as $file) { - $weak_spots = json_decode($file['weak_spots'], true); + $sql_data = array(); + foreach ($files_to_backup as $file) { + $weak_spots = json_decode($file['weak_spots'], true); - if (!empty($weak_spots['SIGNATURES'])) { - $signtures_in_file = array(); - foreach ($weak_spots['SIGNATURES'] as $signatures_in_string) { - $signtures_in_file = array_merge($signtures_in_file, array_diff($signatures_in_string, $signtures_in_file)); - } - $signtures_in_file = implode(',', $signtures_in_file); + $signtures_in_file = array(); + if (!empty($weak_spots['SIGNATURES'])) { + foreach ($weak_spots['SIGNATURES'] as $signatures_in_string) { + $signtures_in_file = array_merge($signtures_in_file, array_diff($signatures_in_string, $signtures_in_file)); } + } - $signatures_with_cci = ! empty($signtures_in_file) - ? $wpdb->get_results('SELECT * FROM ' . SPBC_TBL_SCAN_SIGNATURES . ' WHERE id IN (' . $signtures_in_file . ') AND cci IS NOT NULL') - : null; - - // Backup only files which will be cured - if ($signatures_with_cci) { - if ( ! isset($backup_id)) { - // Adding new backup - $wpdb->insert(SPBC_TBL_BACKUPS, array('type' => 'SIGNATURES', 'datetime' => date('Y-m-d H:i:s'))); - $backup_id = $wpdb->insert_id; - $spbc->data['scanner']['last_backup'] = $backup_id; - $spbc->save('data'); - $dir_name = SPBC_PLUGIN_DIR . 'backups/'; - if ( ! is_dir($dir_name)) { - mkdir($dir_name); - file_put_contents($dir_name . 'index.php', 'get_results($wpdb->prepare($sql_signatures, $signtures_in_file), ARRAY_A); - $result = spbc_backup__file($file['path'], $backup_id); + // Backup only files which will be cured + if (!$signatures_with_cci) { + continue; + } - if (empty($result['error'])) { - $sql_data[] = '(' . $backup_id . ',' . Helper::prepareParamForSQLQuery($file['path']) . ',' . Helper::prepareParamForSQLQuery($result) . ')'; - } else { - // Mark the backup STOPPED while errors occurred - $wpdb->update(SPBC_TBL_BACKUPS, array('status' => 'STOPPED'), array('backup_id' => $backup_id)); - $output = $result; - break; - } + // Adding new backup batch + if ( ! isset($backup_id)) { + $wpdb->insert(SPBC_TBL_BACKUPS, array('type' => 'SIGNATURES', 'datetime' => date('Y-m-d H:i:s'))); + $backup_id = $wpdb->insert_id; + $spbc->data['scanner']['last_backup'] = $backup_id; + $spbc->save('data'); + $dir_name = SPBC_PLUGIN_DIR . 'backups/'; + if ( ! is_dir($dir_name)) { + mkdir($dir_name); + file_put_contents($dir_name . 'index.php', 'data['scanner']['last_backup']; + $result = spbc_backup__file($file['path'], $backup_id); - // Writing backuped files to DB - if ( ! empty($sql_data) && ! isset($output['error'])) { - if ($wpdb->query($sql_query . implode(',', $sql_data) . ';') !== false) { - // Updating current backup status - if ($wpdb->update(SPBC_TBL_BACKUPS, array('status' => 'BACKUPED'), array('backup_id' => $backup_id)) !== false) { - $result = spbc_backup__rotate('signatures'); - if (empty($result['error'])) { - $output = array('success' => true); - } else { - $output = array('error' => 'BACKUP_ROTATE: ' . substr($result['error'], 0, 1024)); - } - } else { - $output = array('error' => 'DB_WRITE_ERROR: ' . substr($wpdb->last_error, 0, 1024)); - } - } else { - $wpdb->update(SPBC_TBL_BACKUPS, array('status' => 'STOPPED'), array('backup_id' => $backup_id)); - $output = array('error' => 'DB_WRITE_ERROR: ' . substr($wpdb->last_error, 0, 1024)); - } + if (empty($result['error'])) { + $sql_data[] = '(' . $backup_id . ',' . Helper::prepareParamForSQLQuery($file['path']) . ',' . Helper::prepareParamForSQLQuery($result) . ')'; } else { - $output = array('success' => true); + // Mark the backup STOPPED while errors occurred + $wpdb->update(SPBC_TBL_BACKUPS, array('status' => 'STOPPED'), array('backup_id' => $backup_id)); + $output = $result; + break; } - } else { + } + + if (empty($sql_data) || isset($output['error'])) { $output = array('success' => true); + return $output; + } + + $backup_id = isset($backup_id) ? $backup_id : $spbc->data['scanner']['last_backup']; + + // Writing backuped files to DB + $sql_query = 'INSERT INTO ' . SPBC_TBL_BACKUPED_FILES . ' (backup_id, real_path, back_path) VALUES'; + // suppress because data is already prepared in Helper::prepareParamForSQLQuery method + // @psalm-suppress WpdbUnsafeMethodsIssue + $result = $wpdb->query($sql_query . implode(',', $sql_data) . ';'); + if ($result === false) { + $wpdb->update(SPBC_TBL_BACKUPS, array('status' => 'STOPPED'), array('backup_id' => $backup_id)); + $output = array('error' => 'DB_WRITE_ERROR: ' . substr($wpdb->last_error, 0, 1024)); + return $output; + } + + // Updating current backup status + $result = $wpdb->update(SPBC_TBL_BACKUPS, array('status' => 'BACKUPED'), array('backup_id' => $backup_id)); + if ($result === false) { + $output = array('error' => 'DB_WRITE_ERROR: ' . substr($wpdb->last_error, 0, 1024)); + return $output; + } + + $result = spbc_backup__rotate('signatures'); + if (!empty($result['error'])) { + $output = array('error' => 'BACKUP_ROTATE: ' . substr($result['error'], 0, 1024)); + return $output; } + $output = array('success' => true); + + return $output; +} + +/** + * Make backup of files with signatures + * @return array + */ +function spbc_backup__files_with_signatures($direct_call = false) +{ + if ( ! $direct_call) { + spbc_check_ajax_referer('spbc_secret_nonce', 'security'); + } + + $output = spbc_backup__files_with_signatures_handler(); + $output['end'] = 1; if (!$direct_call) { From c7b864457fb622f65f8e4beb12819fa17cca8051 Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 21:26:25 +0300 Subject: [PATCH 06/10] Upd. Code. Updated surface handler. --- lib/CleantalkSP/SpbctWP/Scanner/Surface.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Surface.php b/lib/CleantalkSP/SpbctWP/Scanner/Surface.php index fd3c0631b..307b9e99e 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Surface.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Surface.php @@ -337,6 +337,8 @@ private function saveIteratorCompletedDirs($iterator_result) } $insert_query = 'INSERT INTO ' . self::$completed_dirs_table_name . ' (dir_path, running_due_stage) VALUES ' . $completed_dirs_string ; + // suppress because data is already prepared in foreach loop + // @psalm-suppress WpdbUnsafeMethodsIssue $insert_result = $wpdb->query($insert_query); return (bool)$insert_result; @@ -372,8 +374,8 @@ private function clearIteratorCompletedDirs($reset_increment = false) global $wpdb; // run query - $delete_query = 'DELETE FROM ' . self::$completed_dirs_table_name . ' WHERE running_due_stage = ' . (int)$this->running_due_stage . ';'; - $delete_result = $wpdb->query($delete_query); + $delete_query = 'DELETE FROM ' . self::$completed_dirs_table_name . ' WHERE running_due_stage = %d;'; + $delete_result = $wpdb->query($wpdb->prepare($delete_query, (int)$this->running_due_stage)); if ($delete_result === false) { return false; } @@ -381,6 +383,7 @@ private function clearIteratorCompletedDirs($reset_increment = false) if ($reset_increment) { $count = $wpdb->get_var('SELECT COUNT(*) FROM ' . self::$completed_dirs_table_name); if ($count === '0') { + // @psalm-suppress WpdbUnsafeMethodsIssue $wpdb->query('ALTER TABLE ' . self::$completed_dirs_table_name . ' AUTO_INCREMENT = 1;'); } } From 5749e679d6903650b618d0e255c4fed523dbf0cb Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 21:40:29 +0300 Subject: [PATCH 07/10] Upd. Code. Updated settings handler. --- inc/spbc-settings.php | 30 ++++++++++++++++++++---------- security-malware-firewall.php | 25 +++++++++---------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index 619c1607e..95d106068 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -3006,12 +3006,17 @@ function spbc_field_scanner__prepare_data__frontend(&$table) function spbc_field_scanner__get_data__frontend_malware($offset = 1, $limit = 20, $order_direction = "DESC", $order = "page_id") { global $wpdb; - return $wpdb->get_results( + + return $wpdb->get_results($wpdb->prepare( 'SELECT * FROM ' . SPBC_TBL_SCAN_FRONTEND . ' WHERE approved IS NULL OR approved <> 1 - ORDER BY ' . $order . ' ' . $order_direction . ' - LIMIT ' . $offset . ',' . $limit . ';' - ); + ORDER BY %s %s + LIMIT %d, %d;', + $order, + $order_direction, + $offset, + $limit + )); } /** @@ -3023,12 +3028,15 @@ function spbc_field_scanner__get_data__frontend_malware($offset = 1, $limit = 20 function spbc_field_scanner__get_data__frontend_approved($offset = 0, $limit = 20) { global $wpdb; - return $wpdb->get_results( + + return $wpdb->get_results($wpdb->prepare( 'SELECT * FROM ' . SPBC_TBL_SCAN_FRONTEND . ' WHERE approved = 1 ORDER BY page_id DESC - LIMIT ' . $offset . ',' . $limit . ';' - ); + LIMIT %d, %d;', + $offset, + $limit + )); } /** @@ -4358,13 +4366,15 @@ function spbc_field_backups__get_data($offset = 0, $limit = 20) { global $wpdb; - return $wpdb->get_results( + return $wpdb->get_results($wpdb->prepare( 'SELECT ' . SPBC_TBL_BACKUPS . '.backup_id, ' . SPBC_TBL_BACKUPS . '.datetime, ' . SPBC_TBL_BACKUPS . '.type, ' . SPBC_TBL_BACKUPED_FILES . '.real_path FROM ' . SPBC_TBL_BACKUPS . ' RIGHT JOIN ' . SPBC_TBL_BACKUPED_FILES . ' ON ' . SPBC_TBL_BACKUPS . '.backup_id = ' . SPBC_TBL_BACKUPED_FILES . '.backup_id ORDER BY DATETIME DESC - LIMIT ' . $offset . ',' . $limit . ';' - ); + LIMIT %d, %d;', + $offset, + $limit + )); } function spbc_field_backups() diff --git a/security-malware-firewall.php b/security-malware-firewall.php index 0d7540aa7..f3d2048ae 100644 --- a/security-malware-firewall.php +++ b/security-malware-firewall.php @@ -985,6 +985,7 @@ function spbc_send_logs($api_key = null) ? (" WHERE blog_id = " . get_current_blog_id() . ' AND ') : " WHERE "; + // @psalm-suppress WpdbUnsafeMethodsIssue $rows = $wpdb->get_results( "SELECT id, datetime, timestamp_gmt, user_login, page, page_time, event, auth_ip, role, user_agent, browser_sign FROM " . SPBC_TBL_SECURITY_LOG @@ -1043,31 +1044,23 @@ function spbc_send_logs($api_key = null) $result = SpbcAPI::method__security_logs($security_logs_method_dto); if ( empty($result['error']) ) { - //Clear local table if it's ok. + // Clear local table if it's ok. if ( $result['rows'] == $rows_count ) { $updated_ids = array(); foreach ($data as $item) { $updated_ids[] = $item['log_id']; } + + $placeholders = rtrim(str_repeat('%s,', count($updated_ids)), ','); if ( SPBC_WPMS ) { - $wpdb->query( - "UPDATE " . SPBC_TBL_SECURITY_LOG - . " SET sent = 1 - WHERE id IN (" - . implode(',', $updated_ids) . - ")" + $sql = "UPDATE " . SPBC_TBL_SECURITY_LOG . " SET sent = 1 WHERE id IN ($placeholders)" . ( $spbc->ms__work_mode == 2 ? '' : ' AND blog_id = ' . get_current_blog_id() ) - . ";" - ); + . ";"; } else { - $wpdb->query( - "UPDATE " . SPBC_TBL_SECURITY_LOG - . " SET sent = 1 - WHERE id IN (" - . implode(',', $updated_ids) . - ");" - ); + $sql = "UPDATE " . SPBC_TBL_SECURITY_LOG . " SET sent = 1 WHERE id IN ($placeholders);"; } + $wpdb->query($wpdb->prepare($sql, $updated_ids)); + $result = $rows_count; } else { $result = array( From 09ccf37101fed4caf4da20ee6efcf970e31b58db Mon Sep 17 00:00:00 2001 From: svfcode Date: Sun, 17 Nov 2024 22:10:51 +0300 Subject: [PATCH 08/10] Upd. Code. Updated scanner handlers. --- inc/spbc-scanner.php | 214 +++++++++++++++++++++++-------------------- 1 file changed, 115 insertions(+), 99 deletions(-) diff --git a/inc/spbc-scanner.php b/inc/spbc-scanner.php index 66686bc73..576454eee 100644 --- a/inc/spbc-scanner.php +++ b/inc/spbc-scanner.php @@ -333,109 +333,123 @@ function spbc_scanner_rescan_single_file($file_path, $full_hash, $root_path) return $out; } -function spbc_scanner_file_delete($direct_call = false, $file_id = null) +/** + * Delete file from the database handler. + * @param int|string $file_id + * @return array + */ +function spbc_scanner_file_delete_handler($file_id) { - global $spbc; + global $wpdb; - if ( ! $direct_call) { - spbc_check_ajax_referer('spbc_secret_nonce', 'security'); + if (!$file_id) { + return array('error' => 'WRONG_FILE_ID'); } - if ( $spbc->data['license_trial'] == 1 ) { - wp_send_json(['error' => spbc_get_trial_restriction_notice(), 'hide_support_link' => '1']); + $root_path = spbc_get_root_path(); + + // Getting file info. + $sql = 'SELECT * FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE fast_hash = %s LIMIT 1'; + $sql_result = $wpdb->get_results($wpdb->prepare($sql, $file_id), ARRAY_A); + $file_info = $sql_result[0]; + + if (empty($file_info)) { + return array('error' => 'FILE_NOT_FOUND'); } - $time_start = microtime(true); + $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path . $file_info['path']; - global $wpdb; + if (!file_exists($file_path)) { + return array('error' => 'FILE_NOT_EXISTS'); + } - $root_path = spbc_get_root_path(); - $file_id = $direct_call - ? $file_id - : Post::get('file_id', 'hash'); + if (!is_writable($file_path)) { + return array('error' => 'FILE_NOT_WRITABLE'); + } - if ($file_id) { - // Getting file info. - $sql = 'SELECT * - FROM ' . SPBC_TBL_SCAN_FILES . ' - WHERE fast_hash = "' . $file_id . '" - LIMIT 1'; - $sql_result = $wpdb->get_results($sql, ARRAY_A); - $file_info = $sql_result[0]; + $is_file_required_result = spbc_is_file_required_in_php_ini($file_path); + if ($is_file_required_result !== false) { + return $is_file_required_result === null + ? array('error' => 'PHP_INI_REQUIREMENTS_CHECK_FAIL') + : array('error' => 'FILE_IS_REQUIRED_IN_PHP_INI'); + } - if ( ! empty($file_info)) { - $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path . $file_info['path']; + // Getting file && API call + $remembered_file_content = file_get_contents($file_path); + $response_content_before_actions = HTTP::getContentFromURL(get_option('home')); + $response_content_admin_before_actions = HTTP::getContentFromURL(get_option('home') . '/wp-admin'); + $result = unlink($file_path); - if (file_exists($file_path)) { - if (is_writable($file_path)) { - $is_file_required_result = spbc_is_file_required_in_php_ini($file_path); - if ( $is_file_required_result === false ) { - // Getting file && API call - $remembered_file_content = file_get_contents($file_path); - $response_content_before_actions = HTTP::getContentFromURL(get_option('home')); - $response_content_admin_before_actions = HTTP::getContentFromURL(get_option('home') . '/wp-admin'); - $result = unlink($file_path); - - if ($result) { - $response_content = HTTP::getContentFromURL(get_option('home')); - if ( $response_content === $response_content_before_actions ) { - $response_content_ok = true; - } else { - if (is_string($response_content) && !spbc_search_page_errors($response_content)) { - $response_content_ok = true; - } else { - $response_content_ok = false; - } - } + if (!$result) { + return array('error' => 'FILE_COULDNT_DELETE'); + } - $response_content_admin = HTTP::getContentFromURL(get_option('home') . '/wp-admin'); - if ( $response_content_admin === $response_content_admin_before_actions ) { - $response_content_admin_ok = true; - } else { - if (is_string($response_content_admin) && !spbc_search_page_errors($response_content_admin)) { - $response_content_admin_ok = true; - } else { - $response_content_admin_ok = false; - } - } + $response_content = HTTP::getContentFromURL(get_option('home')); + if ($response_content === $response_content_before_actions) { + $response_content_ok = true; + } else { + if (is_string($response_content) && !spbc_search_page_errors($response_content)) { + $response_content_ok = true; + } else { + $response_content_ok = false; + } + } - if ( - !$response_content_admin_ok || - !$response_content_ok - ) { - $output = array('error' => 'WEBSITE_RESPONSE_BAD'); - $result = file_put_contents($file_path, $remembered_file_content); - $output['error'] .= $result === false ? ' REVERT_FAILED' : ' REVERT_OK'; - } else { - // Deleting row from DB - if ($wpdb->query('DELETE FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE fast_hash = "' . $file_id . '"') !== false) { - $output = array('success' => true); - } else { - $output = array('error' => 'DB_COULDNT_DELETE_ROW'); - } - } - } else { - $output = array('error' => 'FILE_COULDNT_DELETE'); - } - unset($remembered_file_content); - } else { - $output = $is_file_required_result === null - ? array('error' => 'PHP_INI_REQUIREMENTS_CHECK_FAIL') - : array('error' => 'FILE_IS_REQUIRED_IN_PHP_INI'); - } - } else { - $output = array('error' => 'FILE_NOT_WRITABLE'); - } - } else { - $output = array('error' => 'FILE_NOT_EXISTS'); - } + $response_content_admin = HTTP::getContentFromURL(get_option('home') . '/wp-admin'); + if ($response_content_admin === $response_content_admin_before_actions) { + $response_content_admin_ok = true; + } else { + if (is_string($response_content_admin) && !spbc_search_page_errors($response_content_admin)) { + $response_content_admin_ok = true; } else { - $output = array('error' => 'FILE_NOT_FOUND'); + $response_content_admin_ok = false; } + } + + if (!$response_content_admin_ok || !$response_content_ok) { + $result = file_put_contents($file_path, $remembered_file_content); + $output = array('error' => 'WEBSITE_RESPONSE_BAD'); + $output['error'] .= $result === false ? ' REVERT_FAILED' : ' REVERT_OK'; + return $output; + } + + // Deleting row from DB + $sql = 'DELETE FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE fast_hash = %s'; + $result = $wpdb->query($wpdb->prepare($sql, $file_id)); + if ($result !== false) { + $output = array('success' => true); } else { - $output = array('error' => 'WRONG_FILE_ID'); + $output = array('error' => 'DB_COULDNT_DELETE_ROW'); + } + + unset($remembered_file_content); + + return $output; +} + +/** + * Delete file from the database. + * @param bool $direct_call + * @param int|string $file_id + * @return non-empty-array|true + */ +function spbc_scanner_file_delete($direct_call = false, $file_id = null) +{ + global $spbc; + + if ( ! $direct_call) { + spbc_check_ajax_referer('spbc_secret_nonce', 'security'); } + if ( $spbc->data['license_trial'] == 1 ) { + wp_send_json(['error' => spbc_get_trial_restriction_notice(), 'hide_support_link' => '1']); + } + + $time_start = microtime(true); + + $file_id = $direct_call ? $file_id : Post::get('file_id', 'hash'); + $output = spbc_scanner_file_delete_handler($file_id); + $exec_time = round(microtime(true) - $time_start); $output['exec_time'] = $exec_time; @@ -659,12 +673,14 @@ function spbc_scanner_pscan_check_analysis_status($direct_call = false, $file_id * If file process is not finished, update data */ $update_result = $wpdb->query( - 'UPDATE ' . SPBC_TBL_SCAN_FILES - . ' SET ' - . ' pscan_pending_queue = 0, ' - . ' pscan_processing_status = "' . $api_response['processing_status'] . '",' - . ' pscan_estimated_execution_time = "' . $api_response['estimated_execution_time'] . '"' - . ' WHERE pscan_file_id = "' . $file_info['pscan_file_id'] . '"' + $wpdb->prepare( + 'UPDATE ' . SPBC_TBL_SCAN_FILES + . ' SET pscan_pending_queue = 0, pscan_processing_status = %s, pscan_estimated_execution_time = %s' + . ' WHERE pscan_file_id = %s', + $api_response['processing_status'], + $api_response['estimated_execution_time'], + $file_info['pscan_file_id'] + ) ); } else { if ( $api_response['file_status'] === 'SAFE' ) { @@ -1099,7 +1115,8 @@ function spbc_scanner_get_files_by_category($category) $ids = array(); $query = 'SELECT fast_hash from ' . SPBC_TBL_SCAN_FILES . spbc_get_sql_where_addiction_for_table_of_category($category); - + // suppress because data is static + // @psalm-suppress WpdbUnsafeMethodsIssue $res = $wpdb->get_results($query); foreach ($res as $tmp) { @@ -1526,11 +1543,11 @@ function spbc_scanner_file_replace($direct_call = false, $file_id = null, $_plat if ($file_id) { // Getting file info. - $sql = 'SELECT path, source_type, source, version, status, severity, source_type + $sql = 'SELECT path, source_type, source, version, status, severity, source_type FROM ' . SPBC_TBL_SCAN_FILES . ' - WHERE fast_hash = "' . $file_id . '" + WHERE fast_hash = %s LIMIT 1'; - $sql_result = $wpdb->get_results($sql, ARRAY_A); + $sql_result = $wpdb->get_results($wpdb->prepare($sql, $file_id), ARRAY_A); $file_info = $sql_result[0]; if ( ! empty($file_info)) { @@ -1546,10 +1563,7 @@ function spbc_scanner_file_replace($direct_call = false, $file_id = null, $_plat if ($res_fwrite) { fclose($file_desc); - $db_result = $wpdb->query( - 'DELETE FROM ' . SPBC_TBL_SCAN_FILES - . ' WHERE fast_hash = "' . $file_id . '";' - ); + $db_result = $wpdb->query($wpdb->prepare('DELETE FROM ' . SPBC_TBL_SCAN_FILES . ' WHERE fast_hash = %s', $file_id)); if ($db_result) { $output = array('success' => true,); @@ -1852,6 +1866,8 @@ function spbc_scanner_analysis_log_delete_from_log($direct_call = false) pscan_balls = null, pscan_file_id = null WHERE fast_hash IN (" . trim($file_ids_string, ',') . ")"; + // suppress because data is already prepared in Validate::isHash method + // @psalm-suppress WpdbUnsafeMethodsIssue $updated_rows = $wpdb->query($query); if ( ! $updated_rows) { From d17703b424b6362b432e98fb2edf194dcaf60367 Mon Sep 17 00:00:00 2001 From: svfcode Date: Tue, 19 Nov 2024 08:56:25 +0300 Subject: [PATCH 09/10] Upd. Code. Updated file view function. --- inc/spbc-scanner.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/inc/spbc-scanner.php b/inc/spbc-scanner.php index 576454eee..a71a61b8f 100644 --- a/inc/spbc-scanner.php +++ b/inc/spbc-scanner.php @@ -1375,12 +1375,15 @@ function spbc_scanner_file_view($direct_call = false, $file_id = null) if ($file_id) { // Getting file info. - $sql = 'SELECT * + $sql = $wpdb->prepare( + 'SELECT * FROM ' . SPBC_TBL_SCAN_FILES . ' - WHERE fast_hash = "' . $file_id . '" - LIMIT 1'; + WHERE fast_hash = %s + LIMIT 1', + $file_id + ); $sql_result = $wpdb->get_results($sql, ARRAY_A); - $file_info = $sql_result[0]; + $file_info = isset($sql_result[0]) ? $sql_result[0] : null; if ( ! empty($file_info)) { $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path . $file_info['path']; From 4141fd483e129c3bfbde91abac35920a982bcda7 Mon Sep 17 00:00:00 2001 From: Denis Shagimuratov Date: Wed, 20 Nov 2024 09:47:08 -0600 Subject: [PATCH 10/10] Tagline, description have been updated. --- readme.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/readme.txt b/readme.txt index 64b33ba88..d86de61f2 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ -=== Security & Malware scan by CleanTalk === +=== Security & Malware scan by CleanTalk === Contributors: glomberg, alexandergull, sergefcleantalk Tags: security, firewall, malware, wordpress security, brute force Requires at least: 5.0 @@ -8,7 +8,7 @@ Stable tag: 2.146 License: GPLv2 License URI: https://www.gnu.org/licenses/gpl-2.0.html -Enhance your WordPress site's security with malware scanning, firewall, brute force protection, and 2FA. +Enhance security with Malware & Vulnerabilities scanner, FireWall, Brute protection and Two Factor Authentication (2FA). Security plugin. == Description == @@ -17,8 +17,8 @@ Enhance your WordPress site's security with malware scanning, firewall, brute fo * **Web Application Security Firewall** * **Security Malware scanner with AntiVirus functions** * **Daily auto malware scan** -* **Stops brute force attacks to hack passwords(Like Fail2ban)** -* **Stops brute force attacks to find WordPress accounts(Like Fail2ban)** +* **Stops brute force attacks to hack passwords (Brute force protection like Fail2ban)** +* **Stops brute force attacks to find WordPress accounts (like Fail2ban)** * **Limit Login Attempts** * **Security Protection for WordPress login form** * **Security Protection for WordPress backend** @@ -26,12 +26,13 @@ Enhance your WordPress site's security with malware scanning, firewall, brute fo * **Security audit log** * **Security Real-time traffic monitor** * **Checking Outbound Links** -* **Two Factor Authentication** +* **Two Factor Authentication (2FA) ** * **No Malware - No Google Penalties. Give your SEO boost.** * **Custom wp-login URL** * **Notifications of administrator users authorizations to your website** * **Backend PHP logs** * **Hide Login Default Login Page** +* **Known vulnerabilities scanner among installed plugins and themes.** CleanTalk is a Cloud security service that protects your website from online threats and provides you great security instruments to control your website security. We provide detailed security stats for all of our security features to have a full control of security. All security logs are stored in the cloud for 45 days. @@ -89,15 +90,17 @@ This option allows you to check files of plugins and themes with heuristic analy = Security Malware scanner to find SQL Injections = The CleanTalk Security Malware Scanner allows you to find code that allows performing SQL injection. It is this problem that the scanner solves. -= CleanTalk Web Application FireWall for WordPress Security Plugin = += Scanner of known vulnerabilities = +Plugin checks installed plugins and themes for known (published) vulnerabilities. If finds vulnerable plugin/theme, it sends an Email notification and shows data in the WordPress Dashboard. -The main purpose of Security Web Application FireWall is to protect the Web application from unauthorized access, even if there are critical vulnerabilities. += Web Application FireWall (WAF) for WordPress Security Plugin = +The main purpose of Web Application FireWall (WAF) is real-time protection from unauthorized access, even if there are critical known/unknown vulnerabilities. Security Web Application FireWall catches all requests to your website and checks HTTP parameters that include: SQL Injection, Cross Site Scripting (XSS), uploading files from non-authorised users, PHP constructions/code, the presence of malicious code in the downloaded files. In addition to effective information security and information security applications are required to know what is quality of protection and CleanTalk Security has logged all blocked requests that allow you to know and analyze accurate information. You can see your Cleantalk Security Logs in your Control panel. https://cleantalk.org/my/logs_firewall -Security CleanTalk Web Application FireWall for WordPress is the proactive defense against known and unknown vulnerabilities to prevent hacks in real-time. +CleanTalk's research team updates WAF database each time as we find a vulnerability, it means plugin's users get protection even against unpublished vulnurebilites. Learn more how to set up and test [About Security Web Application Firewall](https://cleantalk.org/help/security-waf "About Web Application Firewall")