Current File : /home/n742ef5/royalanteam.com/wp-content/plugins/security-malware-firewall/inc/spbc-scanner.php |
<?php
use CleantalkSP\Common\Scanner\SignaturesAnalyser\Structures\Verdict;
use CleantalkSP\SpbctWP\DB;
use CleantalkSP\SpbctWP\API as SpbcAPI;
use CleantalkSP\SpbctWP\Helpers\CSV;
use CleantalkSP\SpbctWP\Scanner\CureLog\CureLog;
use CleantalkSP\SpbctWP\Scanner\FrontendScan;
use CleantalkSP\SpbctWP\Scanner\Stages\CureStage;
use CleantalkSP\SpbctWP\Scanner\Surface;
use CleantalkSP\Variables\Post;
use CleantalkSP\SpbctWP\Scanner\Links;
use CleantalkSP\SpbctWP\Scanner;
use CleantalkSP\SpbctWP\Helpers\HTTP;
use CleantalkSP\Common\Helpers\Arr;
use CleantalkSP\SpbctWP\DTO;
use CleantalkSP\Fpdf\Pdf;
use CleantalkSP\SpbctWP\Scanner\ScanningLog\ScanningLogFacade;
use CleantalkSP\SpbctWP\Scanner\Services\SendFileToCloudService;
/**
* Cron wrapper function for launchBackground
*
* @return bool|string|string[]|void
*/
function spbc_scanner__launch()
{
$result = \CleantalkSP\SpbctWP\Scanner\ScannerQueue::launchBackground();
if (\CleantalkSP\SpbctWP\RemoteCalls::check()) {
$result = empty($result['error'])
? 'OK'
: 'FAIL ' . die(json_encode($result));
}
return $result;
}
/**
* Cron wrapper function for controllerBackground
*
* @param null $transaction_id
* @param null $stage
* @param null $offset
* @param null $amount
*
* @return bool|string|string[]
*/
function spbc_scanner__controller($transaction_id = null, $stage = null, $offset = null, $amount = null)
{
//cron task provide a single parameter
if (isset($transaction_id) && is_array($transaction_id)) {
$stage = isset($transaction_id['stage']) ? $transaction_id['stage'] : null;
$offset = isset($transaction_id['offset']) ? $transaction_id['offset'] : null;
$amount = isset($transaction_id['amount']) ? $transaction_id['amount'] : null;
$transaction_id = isset($transaction_id['transaction_id']) ? $transaction_id['transaction_id'] : null;
}
$result = \CleantalkSP\SpbctWP\Scanner\ScannerQueue::controllerBackground($transaction_id, $stage, $offset, $amount);
if (\CleantalkSP\SpbctWP\RemoteCalls::check()) {
$result = empty($result['error'])
? 'OK'
: 'FAIL ' . die(json_encode($result));
}
return $result;
}
/**
* /**
* For debug purpose
* Clear table from results
*
* @param $direct_call
*
* @return array|void
*/
function spbc_scanner_clear($direct_call = false)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
global $spbc;
$spbc->plugins = array();
$spbc->save('plugins');
$spbc->themes = array();
$spbc->save('themes');
$spbc->data['scanner'] = array(
'last_wp_version' => null,
'cron' => array(
'state' => 'get_hashes',
'total_scanned' => 0,
'offset' => 0,
),
);
$spbc->save('data');
$out = [
'deleted_files_entries' => Scanner\Controller::resetCheckResult(),
'deleted_frontend_entries' => Scanner\Frontend::resetCheckResult(),
'deleted_links' => Scanner\Links::resetCheckResult(),
];
if ($direct_call) {
return $out;
}
wp_send_json($out);
}
/**
* Get count of domains, use in domains accordion
* @return int
*/
function spbc_scanner_links_count_found__domains()
{
global $wpdb;
$count = $wpdb->get_results(
'SELECT COUNT(DISTINCT domain)
FROM ' . SPBC_TBL_SCAN_LINKS . ';',
OBJECT_K
);
return $count ? key($count) : 0;
}
/**
* Get scanned domains, use in domains accordion
* @param int $offset
* @param int $amount
* @param string $order
* @param string $by
* @param bool $get_array
* @return array|object|null
*/
function spbc_scanner_links_get_scanned__domains($offset = 0, $amount = 20, $order = null, $by = null, $get_array = false)
{
global $wpdb;
$offset = intval($offset);
$amount = intval($amount);
$data = $wpdb->get_results(
'SELECT domain, page_url, COUNT(domain) as link_count
FROM ' . SPBC_TBL_SCAN_LINKS . '
GROUP BY domain
' . ($order && $by ? "ORDER BY $by $order" : '') . '
LIMIT ' . $offset . ',' . $amount . ';',
$get_array === true ? ARRAY_A : OBJECT
);
return $data;
}
/**
* Remove file from the database
* @param int|string $file_id
* @return bool
*/
function spbc_scanner_file_remove_from_log($file_id)
{
global $wpdb;
return $wpdb->delete(SPBC_TBL_SCAN_FILES, array('fast_hash' => $file_id));
}
/**
* Send file to Cleantalk Cloud
* @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($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::getString('file_id')) ? Post::getString('file_id') : null;
}
$output = $handler::sendFile($file_id, $do_rescan);
if ( !$direct_call ) {
wp_send_json($output);
}
return $output;
}
/**
* Do rescan a single file with heuristic and signatures.
* @param $file_path
* @param $full_hash
* @param $root_path
* @return array[]|string[]
* <ul>
* <li>array('heuristic_result' => Verdict,
* <br>'signatures_result' => Verdict,
* <br>'merged_result' => array(
* <br>     'status' => '',
* <br>     'severity' => '',
* <br>     'weak_spots' => '')) on success
* </li>
* <li>array('error' => 'error_text') on failure.</li>
* </ul>
*/
function spbc_scanner_rescan_single_file($file_path, $full_hash, $root_path)
{
global $wpdb;
$out = array(
'heuristic_result' => array(),
'signatures_result' => array(),
'merged_result' => array(),
);
//Heuristic
$heuristic_scanner = new \CleantalkSP\Common\Scanner\HeuristicAnalyser\Controller();
$file_to_check = new Scanner\FileInfoExtended(array('path' => $file_path));
$result_heur = $heuristic_scanner->scanFile($file_to_check, $root_path);
if ($result_heur->status === 'ERROR') {
return array('error' => $result_heur->error_msg);
}
// Signature
$signatures = $wpdb->get_results('SELECT * FROM ' . SPBC_TBL_SCAN_SIGNATURES, ARRAY_A);
$signatures_scanner = new \CleantalkSP\Common\Scanner\SignaturesAnalyser\Controller();
$file_to_check = new \CleantalkSP\Common\Scanner\SignaturesAnalyser\Structures\FileInfo(
$file_path,
$full_hash
);
$result_sign = $signatures_scanner->scanFile($file_to_check, $root_path, $signatures);
if ($result_sign->status === 'ERROR') {
return array('error' => $result_sign->error_msg);
}
$out['heuristic_result'] = $result_heur;
$out['signature_result'] = $result_sign;
$merged_result = Arr::mergeWithSavingNumericKeysRecursive((array)$result_heur, (array)$result_sign);
//merge weak-spots
if ( isset($merged_result['weak_spots']) &&
is_array($merged_result['weak_spots']) &&
isset($merged_result['weak_spots'][0]) &&
count($merged_result['weak_spots']) > 1 ) {
unset($merged_result['weak_spots'][0]);
}
//merge status and severities
if ($result_sign->status !== 'OK') {
//signatures verdict is prior
$merged_result['status'] = $result_sign->status;
$merged_result['severity'] = $result_sign->severity;
} elseif ($result_heur->status !== 'OK') {
//if no signatures found - check heuristic verict
$merged_result['status'] = $result_heur->status;
$merged_result['severity'] = $result_heur->severity;
}
$out['merged_result'] = $merged_result;
return $out;
}
/**
* Delete file from the database handler.
* @param int|string $file_id
* @return array
*/
function spbc_scanner_file_delete_handler($file_id)
{
global $wpdb;
if (!$file_id) {
return array('error' => 'WRONG_FILE_ID');
}
$root_path = spbc_get_root_path();
$response_content_ok = false;
$response_content_admin_ok = false;
// 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');
}
$file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path . $file_info['path'];
if (!file_exists($file_path)) {
return array('error' => 'FILE_NOT_EXISTS');
}
if (!is_writable($file_path)) {
return array('error' => 'FILE_NOT_WRITABLE');
}
$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');
}
// Getting file and prechecking website
$remembered_file_content = file_get_contents($file_path);
$precheck_public = wp_remote_get(get_option('home'));
$precheck_admin = wp_remote_get(get_option('home') . '/wp-admin');
$result = unlink($file_path);
if (!$result) {
return array('error' => 'FILE_COULDNT_DELETE');
}
// if not work before and after unlink - it's ok, if both work - it's ok too and much better
$postcheck_public = wp_remote_get(get_option('home'));
if ((is_wp_error($postcheck_public) && is_wp_error($precheck_public)) ||
(!is_wp_error($postcheck_public) && !is_wp_error($precheck_public) &&
wp_remote_retrieve_response_code($postcheck_public) === wp_remote_retrieve_response_code($precheck_public))
) {
$response_content_ok = true;
}
// if not work before and after unlink - it's ok, if both work - it's ok too and much better
$postcheck_admin = wp_remote_get(get_option('home') . '/wp-admin');
if ((is_wp_error($postcheck_admin) && is_wp_error($precheck_admin)) ||
(!is_wp_error($postcheck_admin) && !is_wp_error($precheck_admin) &&
wp_remote_retrieve_response_code($postcheck_admin) === wp_remote_retrieve_response_code($precheck_admin))
) {
$response_content_admin_ok = true;
}
// general check to revert if something went wrong
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' => '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<array-key, mixed>|true
*/
function spbc_scanner_file_delete($direct_call = false, $file_id = null)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$time_start = microtime(true);
$file_id = $direct_call ? $file_id : Post::getString('file_id');
$output = spbc_scanner_file_delete_handler($file_id);
$exec_time = round(microtime(true) - $time_start);
$output['exec_time'] = $exec_time;
if ($direct_call) {
return $output;
} else {
wp_send_json($output);
return true;
}
}
/**
* Cheсk if the filepath is required in php.ini settings. It could be set in "auto_prepend_file" or in "auto_append_file" keys strings.
* @param $file_path
* @return bool|null True if files is required, false otherwise. Null on any code fail.
*/
function spbc_is_file_required_in_php_ini($file_path)
{
try {
$ini_auto_prepend_file_req[] = @ini_get('auto_prepend_file');
$ini_auto_prepend_file_req[] = @ini_get('auto_append_file');
if (!empty($file_path) && is_string($file_path)) {
foreach ($ini_auto_prepend_file_req as $required_string) {
if (is_string($required_string)) {
if (strpos($required_string, $file_path) !== false ||
(!empty(basename($file_path)) && strpos($required_string, basename($file_path)) !== false)) {
return true;
}
}
}
}
} catch (\Exception $_e) {
return null;
}
return false;
}
/**
* Approve file
* @param bool $direct_call
* @param int|string $file_id
* @return void|array
*/
function spbc_scanner_file_approve($direct_call = false, $file_id = null)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
global $wpdb;
$time_start = microtime(true);
$root_path = spbc_get_root_path();
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
if ($file_id) {
// Getting file info.
$sql = $wpdb->prepare('SELECT path, status, severity, pscan_status, pscan_processing_status, pscan_balls, pscan_file_id
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1', $file_id);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
$file_info = $sql_result[0];
if ( ! empty($file_info)) {
if (file_exists($root_path . $file_info['path'])) {
if (is_readable($root_path . $file_info['path'])) {
// Getting file && API call
$previous = json_encode(array(
'status' => $file_info['status'],
'severity' => $file_info['severity'],
'pscan_status' => $file_info['pscan_status'],
'pscan_processing_status' => $file_info['pscan_processing_status'],
'pscan_balls' => $file_info['pscan_balls'],
'pscan_file_id' => $file_info['pscan_file_id'],
));
// Updating all other statuses
$wpdb->update(
SPBC_TBL_SCAN_FILES,
array(
'status' => 'APPROVED_BY_USER',
'previous_state' => $previous,
'pscan_pending_queue' => null,
'pscan_status' => null,
'pscan_processing_status' => null,
'pscan_balls' => null,
'pscan_file_id' => null,
),
array('fast_hash' => $file_id),
array('%s', '%s', '%s'),
array('%s')
);
// Set severity to NULL
// Using strait query because WPDB doesn't support NULL values
$sql = 'UPDATE ' . SPBC_TBL_SCAN_FILES . '
SET severity = NULL
WHERE fast_hash = "' . $file_id . '"';
$sql_result = $wpdb->query($sql, ARRAY_A);
if ($sql_result !== false) {
$output = array('success' => true);
} else {
$output = array('error' => 'DB_COULDNT_UPDATE_ROW_APPROVE');
}
} else {
$output = array('error' => 'FILE_NOT_READABLE');
}
} else {
$output = array('error' => 'FILE_NOT_EXISTS');
}
} else {
$output = array('error' => 'FILE_NOT_FOUND');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
$exec_time = round(microtime(true) - $time_start);
$output['exec_time'] = $exec_time;
if ($direct_call) {
return $output;
} else {
wp_send_json($output);
}
}
/**
* Checks analysis status of passed file(s)
*
* @param bool $direct_call Direct call flag. Show that the function was called directly from other function, not from AJAX
* @param string|array $file_ids_input IDs of files to check the analysis status
* @return array|true[]
*/
function spbc_scanner_pscan_check_analysis_status($direct_call = false, $file_ids_input = '')
{
// Check ajax nonce
if ( !$direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
$file_ids_input = Post::get('file_id', 'hash') ? (string)Post::getString('file_id') : '';
}
global $spbc, $wpdb;
// Parse if there are more than 1 file
$_file_ids_input = is_string($file_ids_input) ? explode(',', $file_ids_input) : $file_ids_input;
// Prepare counters
$counters = array(
'queued' => 0,
'updated' => 0,
'failed' => 0,
'skipped' => 0,
'total' => count($_file_ids_input)
);
// Prepare out array
$out = array('counters' => $counters);
/*
* Processing files start
*/
foreach ( $_file_ids_input as $file_id ) {
/*
* Process a single file start
*/
// Get file info.
$file_info = spbc_scanner_get_file_by_id($file_id);
// Validate file info
try {
$file_info = spbc_scanner_pscan_validate_file_info($file_info);
} catch (\Exception $e) {
$out['error_detail'][] = array(
'error' => $e->getMessage(),
);
$counters['failed']++;
continue;
}
try {
$file_info = spbc_scanner_pscan_update_check_exclusions($file_info);
} catch (\Exception $e) {
switch ($e->getMessage()) {
case 'skipped':
$counters['skipped']++;
break;
case 'queued':
$counters['queued']++;
break;
}
continue;
}
// Perform API call
$api_response = SpbcAPI::method__security_pscan_status(
$spbc->settings['spbc_key'],
$file_info['pscan_file_id']
);
// Validate API response
try {
$api_response = spbc_scanner_validate_pscan_status_response($api_response);
} catch ( Exception $exception ) {
$out['error_detail'][] = array(
'file_path' => $file_info['path'],
'pscan_file_id' => $file_info['pscan_file_id'],
'fast_hash' => $file_id,
'error' => 'API response validation failed "' . $exception->getMessage() . "\"",
);
continue;
}
if ( $api_response['processing_status'] !== 'DONE' ) {
/*
* If file process is not finished, update data
*/
$update_result = $wpdb->query(
$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' ) {
/*
* Do something with SAFE files
*/
// Prepare query for good files update
$update_query = $wpdb->prepare(
'UPDATE ' . SPBC_TBL_SCAN_FILES
. ' SET '
. ' pscan_processing_status = "DONE",'
. ' pscan_pending_queue = 0, '
. ' pscan_status = "SAFE",'
. ' pscan_balls = %s,'
. ' status = "APPROVED_BY_CLOUD",'
. ' pscan_estimated_execution_time = NULL'
. ' WHERE pscan_file_id = %s',
isset($api_response['file_balls']) ? $api_response['file_balls'] : '{SAFE:0}',
$file_info['pscan_file_id']
);
} else {
/*
* Do something with DANGEROUS files
*/
// Prepare query for bad files update
$update_query = $wpdb->prepare(
'UPDATE ' . SPBC_TBL_SCAN_FILES
. ' SET '
. ' pscan_processing_status = "DONE",'
. ' pscan_pending_queue = 0, '
. ' pscan_status = %s ,'
. ' severity = "CRITICAL",'
. ' pscan_balls = %s,'
. ' status = "DENIED_BY_CLOUD",'
. ' pscan_estimated_execution_time = NULL'
. ' WHERE pscan_file_id = %s',
$api_response['file_status'],
isset($api_response['file_balls']) ? $api_response['file_balls'] : '{DANGEROUS:0}',
$file_info['pscan_file_id']
);
}
// Run prepared query and keep update result
$update_result = $wpdb->query($update_query);
}
if ( $update_result === false ) {
// Collect errors
$out['error_detail'][] = array(
'file_path' => $file_info['path'],
'pscan_file_id' => $file_info['pscan_file_id'],
'fast_hash' => $file_id,
'error' => 'COULDNT_UPDATE file status',
);
} else {
// All is fine, inc updated counter
$counters['updated']++;
}
/*
* Process a single file end
*/
}
/*
* Processing files end
*/
// Process errors
if ( !empty($out['error_detail']) ) {
$out['error'] = 'Some files where not updated.';
}
// Fill counters
$out['counters'] = $counters;
// Shift cron task
\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
);
// Resend queued files if available
if ( $counters['queued'] > 0 && $spbc->settings['spbc_scanner_user_can_force_pscan_update'] ) {
spbc_scanner_resend_pscan_files();
}
if (!$direct_call) {
wp_send_json_success($out);
}
return $out;
}
/**
* Validate file info collected for pscan status updating process
* @param $file_info array|false array of file info or false if we could not collect this
* @return array origin array of file info
* @throws Exception
*/
function spbc_scanner_pscan_validate_file_info($file_info)
{
// SQL query result validation
if ( $file_info !== false ) {
// Validate path
$file_path = !empty($file_info['path']) ? $file_info['path'] : false;
if (!$file_path) {
throw new Exception('can not get file path');
}
// Set pscan file id to be sure that file is correctly reached from db
$pscan_file_id = !empty($file_info['pscan_file_id']) ? $file_info['pscan_file_id'] : false;
if ( !$pscan_file_id ) {
throw new Exception('can not get pscan_file_id');
}
} else {
throw new Exception('can not get file info');
}
return $file_info;
}
/**
* Check if status updater should skip this file
* @param $file_info
* @return array array of file info
* @throws Exception message contains reason for skip the file
*/
function spbc_scanner_pscan_update_check_exclusions(array $file_info)
{
global $spbc;
//skip quarantined files
if (isset($file_info['status']) && $file_info['status'] === 'QUARANTINED') {
throw new Exception('skipped');
}
// skip not queued files
$pscan_pending_queue = isset($file_info['pscan_pending_queue']) && $file_info['pscan_pending_queue'] == '1';
if ($pscan_pending_queue) {
throw new Exception('queued');
}
//skip maual analysis checked
if (empty($file_info['pscan_processing_status'])) {
throw new Exception('skipped');
}
return $file_info;
}
/**
* @param array $response API Response
* @param bool $await_estimated_data Do await estimated data set on undone files check
* @return mixed API Response
* @throws Exception if validation failed
*/
function spbc_scanner_validate_pscan_status_response($response)
{
// Check if API error
if ( !empty($response['error']) ) {
throw new Exception('API error: "' . $response['error'] . "\"");
}
// Check if processing_status is set and correct
if ( !isset($response['processing_status']) ) {
throw new Exception('response provided no processing status');
}
// Set allowed statuses
$allowed_statuses_array = array(
'NEW',
'DONE',
'ERROR',
'IN_SCANER',
'NEW_CLOUD',
'IN_CLOUD',
'IN_SANDBOX',
'NEW_SANDBOX',
'UNKNOWN'
);
// Check allowed statuses
if ( !in_array($response['processing_status'], $allowed_statuses_array) ) {
throw new Exception('response provided unknown processing status "' . $response['processing_status'] . "\"");
}
// Check precessing status
if ( $response['processing_status'] === 'DONE' && !isset($response['file_status']) ) {
throw new Exception('process finished, but status is unset');
}
if ( $response['processing_status'] === 'DONE' ) {
// Check file_status
if ( !in_array($response['file_status'], array('DANGEROUS', 'SAFE')) ) {
throw new Exception('process finished, but status is unknown: "' . $response['file_status'] . "\"");
}
}
//estimated time validation
if ( $response['processing_status'] !== 'DONE' ) {
if ( ! isset($response['estimated_execution_time'])) {
throw new Exception('response provided no estimated scan time');
}
//todo remove on business decision
//if ( ! isset($response['number_of_files'])) {
// throw new Exception('response provided no number of estimated files');
//}
//if ( ! isset($response['number_of_files_scanned'])) {
// throw new Exception('response provided no number of already scanned files');
//}
}
return $response;
}
/**
* Approve files in bulk
* @param array $ids
* @return array
*/
function spbc_scanner_file_approve__bulk($ids = array())
{
if ( ! $ids) {
return array('error' => 'Noting to approve');
}
$out = array();
foreach ($ids as $id) {
$result = spbc_scanner_file_approve(true, $id);
if ( ! empty($result['error'])) {
$file_info = spbc_scanner_get_file_by_id($id);
$file_path = isset($file_info['path']) ? $file_info['path'] : 'UNKNOWN_FILE';
$out['error'] = 'Some files where not updated.';
$out['error_detail'][] = array(
'file_path' => $file_path,
'error' => $result['error'],
);
}
}
return $out;
}
/**
* Disapprove files in bulk
* @param array $ids
* @return array
*/
function spbc_scanner_file_disapprove__bulk($ids = array())
{
if ( ! $ids ) {
return array('error' => 'Nothing to disapprove');
}
$out = array();
foreach ($ids as $id) {
$result = spbc_scanner_file_disapprove(true, $id);
if ( ! empty($result['error']) ) {
$file_info = spbc_scanner_get_file_by_id($id);
$file_path = isset($file_info['path']) ? $file_info['path'] : 'UNKNOWN_FILE';
$out['error'] = 'Some files where not updated.';
$out['error_detail'][] = array(
'file_path' => $file_path,
'error' => $result['error'],
);
}
}
return $out;
}
/**
* Approve pages in bulk
* @param string $action
* @return array
*/
function spbc_scanner_page_approve_process__bulk($action)
{
global $wpdb;
$action = $action === 'approve' ? '1' : '0';
$out = array();
$sql = 'UPDATE ' . SPBC_TBL_SCAN_FRONTEND . ' SET approved = ' . $action;
$sql = $wpdb->prepare($sql);
$sql_result = $wpdb->query($sql);
if ($sql_result === false) {
$out['error'] = 'COULDNT_UPDATE_DB';
}
$ids = $wpdb->get_col("SELECT page_id FROM " . SPBC_TBL_SCAN_FRONTEND);
foreach ($ids as $id) {
$update_meta_result = update_post_meta($id, '_spbc_frontend__approved', (int)$action);
if ($update_meta_result === false) {
$out['error'] = 'COULDNT_UPDATE_POST_META';
}
}
return $out;
}
/**
* Send files for analysis in bulk
* @param array $fast_hashes_list
* @return array
*/
function spbc_scanner_file_send_for_analysis__bulk($fast_hashes_list = array())
{
if ( ! $fast_hashes_list) {
return array('error' => 'Record has no file id to send.');
}
global $wpdb;
$sql_result = $wpdb->get_results(
'SELECT fast_hash FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE last_sent IS NULL
AND status NOT IN ("APPROVED_BY_USER","APPROVED_BY_CT","APPROVED_BY_CLOUD","DENIED_BY_CT")',
ARRAY_A
);
$sql_result = array_map(
function ($data) {
return $data['fast_hash'];
},
$sql_result
);
$not_sent_files_intersection = array_values(array_intersect($fast_hashes_list, $sql_result));
$out = array(
'files_sent_counter' => 0
);
if ( ! empty($not_sent_files_intersection)) {
foreach ($not_sent_files_intersection as $fast_hash) {
$result = spbc_scanner_file_send(true, $fast_hash);
if ( ! empty($result['error'])) {
$file_info = spbc_scanner_get_file_by_id($fast_hash);
$file_path = isset($file_info['path']) ? $file_info['path'] : 'UNKNOWN_FILE';
$out['error'] = 'Some files where not updated.';
$out['error_detail'][] = array(
'file_path' => $file_path,
'error' => $result['error'],
);
}
$out['files_sent_counter']++;
}
} else {
$out['error'] = __('All the available files have been already sent.', 'security-malware-firewall');
}
return $out;
}
/**
* Get SQL *WHERE* suffix for SELECT query depends on files category.
* @param string $category Category of files category which needs to be searched for
* @return string SQL *WHERE* suffix.
*/
function spbc_get_sql_where_addiction_for_table_of_category($category)
{
global $spbc;
switch ($category) {
case 'critical':
$res = ' WHERE status IN ("DENIED_BY_CLOUD", "DENIED_BY_CT")
OR (
severity = "CRITICAL"
AND (
status <> "QUARANTINED" AND
status <> "APPROVED_BY_USER" AND
status <> "APPROVED_BY_CT" AND
status <> "OK"
)
AND (
last_sent IS NULL OR
pscan_status = "DANGEROUS"
)
)';
break;
case 'suspicious':
$res = ' WHERE severity <> "CRITICAL" AND
last_sent IS NULL AND
(status = "MODIFIED" AND source_type IS NOT NULL)
OR (status = "INFECTED" AND severity = "SUSPICIOUS" AND last_sent IS NULL)';
break;
case 'approved':
$res = ' WHERE status = "APPROVED_BY_USER" AND source_type IS NULL';
break;
case 'approved_by_cloud':
$res = ' WHERE ( status = "APPROVED_BY_CT" OR status = "APPROVED_BY_CLOUD") AND source_type IS NULL';
break;
case 'analysis_log':
$res = ' WHERE last_sent IS NOT NULL';
break;
case 'unknown':
$res = ' WHERE status NOT IN ("APPROVED_BY_USER","APPROVED_BY_CT","APPROVED_BY_CLOUD","DENIED_BY_CT","ERROR") AND
detected_at >= ' . (time() - $spbc->settings['scanner__list_unknown__older_than'] * 86400) . ' AND
source IS NULL AND
source_type IS NULL AND
size > 0 AND
path NOT LIKE "%wp-content%themes%" AND
path NOT LIKE "%wp-content%plugins%" AND
path NOT LIKE "%wp-content%cache%" AND
path NOT LIKE "%wp-config.php" AND
(severity IS NULL OR severity NOT IN ("CRITICAL", "SUSPICIOUS")) AND
last_sent IS NULL AND
fast_hash NOT IN (SELECT fast_hash FROM ' . SPBC_TBL_CURE_LOG . ' WHERE cure_status = 1)';
break;
case 'quarantined':
$res = ' WHERE status = "QUARANTINED"';
break;
case 'frontend_malware':
$res = ' WHERE approved IS NULL OR approved <> 1';
break;
case 'frontend_scan_results_approved':
$res = ' WHERE approved = 1';
break;
case 'skipped':
$res = ' WHERE status = "ERROR" AND error_msg IS NOT NULL AND error_msg NOT LIKE "%FILE_SIZE_ZERO%"';
break;
default:
$res = '';
}
return $res;
}
/**
* Get all files IDs of the category.
* @param string $category Category of files category which needs to be searched for
* @param bool $count Whether to return the count of files or the files themselves
* @return array Array of IDs
*/
function spbc_scanner_get_files_by_category($category, $count = false)
{
global $wpdb;
$ids = array();
// No need to select IDs for Frontend Malware Scanner
if ( $category === 'frontend_malware' ) {
return $ids;
}
$query = $count ? 'SELECT COUNT(fast_hash) from ' : 'SELECT fast_hash from ';
$query .= 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) {
$ids[] = isset($tmp->fast_hash) ? $tmp->fast_hash : null;
}
return $ids;
}
/**
* Disapprove file
* @param bool $direct_call
* @param int|string $file_id
* @return void|array
*/
function spbc_scanner_file_disapprove($direct_call = false, $file_id = null)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$time_start = microtime(true);
global $wpdb;
$root_path = spbc_get_root_path();
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
if ($file_id) {
// Getting file info.
$sql = $wpdb->prepare('SELECT path, full_hash, previous_state
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1', $file_id);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
$file_info = $sql_result[0];
if ( ! empty($file_info) ) {
if (file_exists($root_path . $file_info['path'])) {
if (is_readable($root_path . $file_info['path'])) {
// Getting file && API call
$previous = is_string($file_info['previous_state'])
? json_decode($file_info['previous_state'], true)
: false;
if ( ! $previous ) {
// Placeholders for the approved by CT files
$previous['status'] = 'OK';
$previous['severity'] = null;
$previous['pscan_status'] = null;
$previous['pscan_processing_status'] = null;
$previous['pscan_balls'] = null;
$previous['pscan_file_id'] = null;
}
$sql_upd_result = $wpdb->update(
SPBC_TBL_SCAN_FILES,
array(
'status' => $previous['status'],
'severity' => $previous['severity'],
'pscan_status' => $previous['pscan_status'],
'pscan_processing_status' => $previous['pscan_processing_status'],
'pscan_balls' => $previous['pscan_balls'],
'pscan_file_id' => $previous['pscan_file_id'],
),
array('fast_hash' => $file_id),
array('%s', '%s', '%s', '%s', '%s', '%d'),
array('%s')
);
if ($sql_upd_result !== false) {
$output = array('success' => true);
} else {
$output = array('error' => 'DB_COULDNT_UPDATE_ROW_APPROVE');
}
} else {
$output = array('error' => 'FILE_NOT_READABLE');
}
} else {
$output = array('error' => 'FILE_NOT_EXISTS');
}
} else {
$output = array('error' => 'FILE_NOT_FOUND');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
$exec_time = round(microtime(true) - $time_start);
$output['exec_time'] = $exec_time;
if ($direct_call) {
return $output;
} else {
wp_send_json($output);
}
}
function spbc_scanner_page_view($direct_call = false, $page_url = false)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$time_start = microtime(true);
global $spbc, $wpdb;
$page_url = $direct_call
? $page_url
: Post::get('page_url', null, 'url');
$page_content = HTTP::getContentFromURL($page_url);
if ( ! empty($page_content)) {
// Getting signatures
$check_list = array('redirects', 'dbd', 'signatures_js', 'signatures_html');
if ($spbc->settings['scanner__frontend_analysis__csrf']) {
$check_list[] = 'csrf';
}
$fe_scanner = new FrontendScan($check_list);
$recheck_res = $fe_scanner->setHomeUrl(get_option('home'))
->setExceptUrls(CSV::parseNSV($spbc->settings['scanner__frontend_analysis__domains_exclusions']))
->setSignatures($wpdb->get_results('SELECT * FROM ' . SPBC_TBL_SCAN_SIGNATURES, ARRAY_A))
->setContent($page_content)
->check()
->getResult();
if (count($recheck_res) === 0) {
// If the malware not more present
$page_id = $wpdb->get_var(
$wpdb->prepare(
'SELECT page_id'
. ' FROM ' . SPBC_TBL_SCAN_FRONTEND
. ' WHERE url = %s',
$page_url
)
);
delete_post_meta($page_id, '_spbc_frontend__last_checked');
delete_post_meta($page_id, 'spbc_frontend__last_checked');
$wpdb->query(
$wpdb->prepare(
'DELETE'
. ' FROM ' . SPBC_TBL_SCAN_FRONTEND
. ' WHERE url = %s',
$page_url
)
);
wp_send_json([
'success' => false,
'content' => esc_html__('The malware found earlier no longer present. The notice about the malware will be replaced from the results list.', 'security-malware-firewall'), // Content of the modal
'file_path' => esc_html__('The malware no longer found', 'security-malware-firewall') // Title of the modal
]);
}
$page_text = array();
$page_url_sql = str_replace('.', '%', $page_url);
// Getting file info.
$sql_result = $wpdb->get_results(
$wpdb->prepare(
'SELECT weak_spots'
. ' FROM ' . SPBC_TBL_SCAN_FRONTEND
. ' WHERE url LIKE %s'
. ' LIMIT 1',
$page_url_sql
),
ARRAY_A
);
$result = $sql_result[0];
foreach (preg_split("/((\r?\n)|(\r\n?))/", $page_content) as $line) {
$page_text[] = htmlspecialchars($line);
}
$output = array(
'success' => true,
'file' => $page_text,
'file_path' => $page_url,
'difference' => null,
'weak_spots' => $result['weak_spots']
);
} else {
$output = array('error' => 'FILE_TEXT_EMPTY');
}
$exec_time = round(microtime(true) - $time_start);
$output['exec_time'] = $exec_time;
if ($direct_call) {
return $output;
}
$red_line = '<span style=\"background: rgb(200,80,80);\">';
$red_line_end = '</span>';
$output['weak_spots'] = str_replace('__SPBCT_RED__', $red_line, $output['weak_spots']);
$output['weak_spots'] = str_replace('__SPBCT_RED_END__', $red_line_end, $output['weak_spots']);
wp_send_json($output);
}
/**
* Handler to approve or disapprove a page.
* @param string $action Could be 'approve' or 'disapprove'
* @return true
* @throws Exception
*/
function spbc_scanner_page_approve_process($action)
{
global $wpdb;
if (!in_array($action, array('approve', 'disapprove'))) {
throw new \Exception('APPROVE_PROCESS_MISTYPE');
}
$action = $action === 'approve' ? '1' : '0';
$page_url = Post::get('page_url', null, 'url');
$page_id = Post::getInt('page_id');
if (empty($page_url) || $page_id === 0 ) {
throw new \Exception('PAGE_ID_OR_PAGE_URL_IS_EMPTY');
}
if (filter_var($page_url, FILTER_VALIDATE_URL)) {
// Getting file info.
$sql = 'UPDATE ' . SPBC_TBL_SCAN_FRONTEND . ' SET approved = ' . $action . ' WHERE url = %s';
$sql = $wpdb->prepare($sql, $page_url);
$sql_result = $wpdb->query($sql);
if ($sql_result === false) {
throw new \Exception('COULDNT_UPDATE_DB');
}
$update_meta_result = update_post_meta($page_id, '_spbc_frontend__approved', (int)$action);
if (false === $update_meta_result) {
throw new \Exception('COULDNT_UPDATE_POST_META');
}
}
return true;
}
/**
* View file
* @param bool $direct_call
* @param int|string $file_id
* @return void|array
*/
function spbc_scanner_file_view($direct_call = false, $file_id = null)
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$time_start = microtime(true);
global $wpdb;
$root_path = spbc_get_root_path();
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
// check if file exists, delete it from the list if not
$check_file_exist_result = \CleantalkSP\SpbctWP\ListTable::spbcCheckFileExist($file_id);
if (isset($check_file_exist_result['error'])) {
$out = array(
'error' => $check_file_exist_result['error'],
);
wp_send_json($out);
}
if ($file_id) {
// Getting file info.
$sql = $wpdb->prepare(
'SELECT *
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1',
$file_id
);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
$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'];
if (file_exists($file_path)) {
if (is_readable($file_path)) {
// Getting file && API call
$file = file($file_path);
if ($file !== false && count($file)) {
$file_text = array();
for ($i = 0; isset($file[ $i ]); $i++) {
$file_text[ $i + 1 ] = htmlspecialchars($file[ $i ]);
$file_text[ $i + 1 ] = preg_replace("/[^\S]{4}/", " ", $file_text[ $i + 1 ]);
}
if ( ! empty($file_text)) {
$output = array(
'success' => true,
'file' => $file_text,
'file_path' => $file_path,
'difference' => $file_info['difference'],
'weak_spots' => $file_info['weak_spots']
);
} else {
$output = array('error' => 'FILE_TEXT_EMPTY');
}
} else {
$output = array('error' => 'FILE_EMPTY');
}
} else {
$output = array('error' => 'FILE_NOT_READABLE');
}
} else {
$output = array('error' => 'File not exists and will be removed from log through next scan.');
}
} else {
$output = array('error' => 'FILE_NOT_FOUND');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
$exec_time = round(microtime(true) - $time_start);
$output['exec_time'] = $exec_time;
if ($direct_call) {
return $output;
} else {
wp_send_json($output);
}
}
/**
* Replace file
* @param bool $direct_call
* @param int|string $file_id
* @param string $_platform
* @return void|array
*/
function spbc_scanner_file_replace($direct_call = false, $file_id = null, $_platform = 'wordpress')
{
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$time_start = microtime(true);
global $wpdb;
$root_path = spbc_get_root_path();
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
if ($file_id) {
// Getting file info.
$sql = $wpdb->prepare('SELECT path, source_type, source, version, status, severity, source_type
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1', $file_id);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
$file_info = $sql_result[0];
if ( ! empty($file_info)) {
if (file_exists($root_path . $file_info['path'])) {
if (is_writable($root_path . $file_info['path'])) {
// Getting file && API call
$original_file = Scanner\Helper::getOriginalFile($file_info);
if ($original_file && !isset($original_file['error'])) {
$file_desc = fopen($root_path . $file_info['path'], 'w');
if ($file_desc && is_string($original_file)) {
$res_fwrite = fwrite($file_desc, $original_file);
if ($res_fwrite) {
fclose($file_desc);
$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,);
} else {
$output = array('error' => 'FILE_DB_DELETE_FAIL');
}
} else {
$output = array('error' => 'FILE_COULDNT_WRITE');
}
} else {
$output = array('error' => 'FILE_COULDNT_OPEN');
}
} else {
$output = array('error' => 'GET_FILE_FAILED');
}
} else {
$output = array('error' => 'FILE_NOT_WRITABLE');
}
} else {
$output = array('error' => 'FILE_NOT_EXISTS');
}
} else {
$output = array('error' => 'FILE_NOT_FOUND');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
$exec_time = round(microtime(true) - $time_start);
$output['exec_time'] = $exec_time;
if ($direct_call) {
return $output;
} else {
wp_send_json($output);
}
}
/**
* Quarantine file
* @param bool $direct_call
* @param int|string $file_id
* @return void|array
*/
function spbc_scanner_file_quarantine($direct_call = false, $file_id = null)
{
global $wpdb;
if ( ! $direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$root_path = spbc_get_root_path();
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
if ($file_id) {
// Getting file info.
$sql = $wpdb->prepare('SELECT *
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1', $file_id);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
$file_info = $sql_result[0];
if ( ! empty($file_info)) {
if (file_exists($root_path . $file_info['path'])) {
if (is_writable($root_path . $file_info['path'])) {
$q_path = SPBC_PLUGIN_DIR . 'quarantine/'
. str_replace('/', '__', str_replace('\\', '__', $file_info['path'])) . '___'
. md5($file_info['path'] . rand(0, 99999999)) . '.punished';
$dir_name = SPBC_PLUGIN_DIR . 'quarantine/';
if ( ! is_dir($dir_name)) {
mkdir($dir_name);
file_put_contents($dir_name . 'index.php', '<?php');
}
if (copy($root_path . $file_info['path'], $q_path)) {
$result = $wpdb->update(
SPBC_TBL_SCAN_FILES,
array(
'status' => 'QUARANTINED',
'q_path' => $q_path,
//should be offset to use in date()
'q_time' => current_time('timestamp'),
'previous_state' => json_encode(array(
'status' => $file_info['status'],
)),
),
array('full_hash' => $file_info['full_hash'], 'fast_hash' => $file_info['fast_hash']),
array('%s', '%s', '%d', '%s'),
array('%s', '%s')
);
if ($result !== false && $result > 0) {
if (unlink($root_path . $file_info['path'])) {
$response_content = HTTP::getContentFromURL(get_option('home'));
$response_content_admin = HTTP::getContentFromURL(get_option('home') . '/wp-admin/');
if (
isset(
$response_content['error'],
$response_content_admin['error']
) ||
spbc_search_page_errors($response_content) ||
spbc_search_page_errors($response_content_admin)
) {
$output = array('error' => 'WEBSITE_RESPONSE_BAD');
$result = spbc_scanner_file_quarantine__restore(true, $file_info['fast_hash']);
$output['error'] .= ! empty($result['error']) ? ' REVERT_FAILED ' . $result['error'] : ' REVERT_OK';
} else {
$output = array('success' => true,);
}
} else {
$output = array('error' => 'DELETE_FAILED');
}
} else {
$output = array('error' => 'UPDATE_TABLE_FAILED');
}
} else {
$output = array('error' => 'COPY_FAILED');
}
} else {
$output = array('error' => 'FILE_NOT_WRITABLE');
}
} else {
$output = array('error' => 'FILE_NOT_EXISTS');
}
} else {
$output = array('error' => 'FILE_NOT_FOUND');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
if ($direct_call) {
return spbc_humanize_output($output);
} else {
wp_send_json($output);
}
}
/**
* Restore file from quarantine
* @param bool $direct_call
* @param int|string $file_id
* @return void|array
*/
function spbc_scanner_file_quarantine__restore($direct_call = false, $file_id = null)
{
global $wpdb;
if ( ! $direct_call) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$root_path = spbc_get_root_path();
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
if ($file_id) {
// Getting file info.
$sql = $wpdb->prepare('SELECT *
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1', $file_id);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
$file_info = $sql_result[0];
if ( ! empty($file_info)) {
if (
!empty($file_info['q_path']) &&
is_string($file_info['q_path']) &&
file_exists($file_info['q_path'])
) {
if (is_writable($file_info['q_path'])) {
if (copy($file_info['q_path'], $root_path . $file_info['path'])) {
$previous = json_decode($file_info['previous_state'], true);
$result = $wpdb->update(
SPBC_TBL_SCAN_FILES,
array(
'status' => $previous['status'],
'q_path' => null,
'q_time' => null,
),
array('fast_hash' => $file_info['fast_hash']),
array('%s', '%s', '%d',),
array('%s')
);
if ($result !== false && $result > 0) {
if (unlink($file_info['q_path'])) {
$output = array('success' => true,);
} else {
$output = array('error' => 'DELETE_FAILED');
}
} else {
$output = array('error' => 'UPDATE_TABLE_FAILED');
}
} else {
$output = array('error' => 'COPY_FAILED');
}
} else {
$output = array('error' => 'FILE_NOT_WRITABLE');
}
} else {
$output = array('error' => 'FILE_NOT_EXISTS');
}
} else {
$output = array('error' => 'FILE_NOT_FOUND');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
if ($direct_call) {
return $output;
} else {
wp_send_json($output);
}
}
/**
* Download file handler
*/
function spbc_scanner_file_download_handler($file_id)
{
global $wpdb;
if (!$file_id) {
return array('error' => 'WRONG_FILE_ID');
}
$sql = $wpdb->prepare('SELECT *
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = %s
LIMIT 1', $file_id);
$sql_result = $wpdb->get_results($sql, ARRAY_A);
if (empty($sql_result) || !isset($sql_result[0])) {
return array('error' => 'FILE_NOT_FOUND');
}
$file_info = $sql_result[0];
if (empty($file_info) || !isset($file_info['q_path'])) {
return array('error' => 'FILE_NOT_FOUND');
}
if (!file_exists($file_info['q_path'])) {
return array('error' => 'FILE_NOT_EXISTS');
}
if (!is_readable($file_info['q_path'])) {
return array('error' => 'FILE_NOT_READABLE');
}
// Getting file && API call
$file_path = substr($file_info['q_path'], stripos($file_info['q_path'], 'wp-content'));
$file_content = HTTP::getContentFromURL(get_home_url() . '/' . $file_path);
if (!empty($file_content['error'])) {
return array('error' => 'FILE_EMPTY');
}
return array(
'file_name' => preg_replace('/.*(\/|\\\\)(.*)/', '$2', $file_info['path']),
'file_content' => $file_content,
);
}
/**
* Download file function
*/
function spbc_scanner_file_download($direct_call = false, $file_id = null)
{
$file_id = $direct_call
? $file_id
: Post::get('file_id', 'hash');
$result = spbc_scanner_file_download_handler($file_id);
if ($direct_call) {
return $result;
} else {
wp_send_json($result);
}
}
/**
* Delete file from analysis log
*/
function spbc_scanner_analysis_log_delete_from_log($direct_call = false)
{
// Check ajax nonce
if ( ! $direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
global $wpdb;
$file_ids = Post::getArray('file_ids'); // validation is done in Validate::isHash method next line
$file_ids_clean = [];
if ( is_array($file_ids) ) {
// Validate if the ID is hash (SQL-clear)
$file_ids_clean = array_map(function ($_id) {
if ( \CleantalkSP\Common\Validate::isHash($_id) ) {
return $_id;
}
}, $file_ids);
}
$output = array('error' => false);
if ( $file_ids_clean ) {
$file_ids_string = '';
foreach ( $file_ids_clean as $id ) {
$file_ids_string .= '"' . $id . '",';
}
$query = "UPDATE " . SPBC_TBL_SCAN_FILES . " SET
last_sent = null,
pscan_status = null,
pscan_processing_status = null,
pscan_pending_queue = null,
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) {
$output = array('error' => 'DB_ERROR');
}
} else {
$output = array('error' => 'WRONG_FILE_ID');
}
if ( ! $direct_call ) {
wp_send_json($output);
}
return $output;
}
/**
* Replacing error codes by readable and translatable format.
* We have to add new error descriptions here future.
*
* @param $output_array
*
* @return array
*/
function spbc_humanize_output($output_array)
{
if (is_array($output_array) && array_key_exists('error', $output_array)) {
$errors_codes = array(
'WEBSITE_RESPONSE_BAD',
'REVERT_OK'
);
$errors_texts = array(
esc_html__('The requested action caused a website error.', 'security-malware-firewall'),
// WEBSITE_RESPONSE_BAD
esc_html__('The changes were reverted.', 'security-malware-firewall'),
// REVERT_OK
);
foreach ($output_array as $key => $item) {
$output_array[ $key ] = str_replace($errors_codes, $errors_texts, $item);
}
}
return $output_array;
}
/**
* Get file by ID
* @param string $file_id
* @return array|false
*/
function spbc_scanner_get_file_by_id($file_id)
{
global $wpdb;
$file_info = $wpdb->get_row(
'SELECT *
FROM ' . SPBC_TBL_SCAN_FILES . '
WHERE fast_hash = "' . $file_id . '"
LIMIT 1',
ARRAY_A
);
return $file_info ?: false;
}
/**
* Load more scan logs on the scan results page
*/
function spbc_scanner_load_more_scan_logs()
{
$offset = Post::getInt('offset');
$scan_logs = ScanningLogFacade::render(100, (int)$offset, true);
wp_send_json_success($scan_logs);
}
/**
* Save scanner logs to pdf
*
* @param $direct_call
*
* @return array|void
*/
function spbc_scanner_save_to_pdf($direct_call = false)
{
if ( !$direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
$pdf = new Pdf();
$pdf->AliasNbPages();
$pdf->AddPage();
$pdf->drawScanCommonStatsTable();
$pdf->Ln();
$pdf->drawScanResultsOfScanType('heuristic_results');
$pdf->drawScanResultsOfScanType('signature_results');
$pdf->Ln();
$pdf->drawFilesListByType('cure_log');
$pdf->AddPage();
$pdf->drawFilesListByType('critical_files');
$pdf->AddPage();
$pdf->drawFilesListByType('suspicious_files');
$pdf->AddPage();
$pdf->drawScanLogs();
$pdf->Output();
}
/**
* Get PDF file name
* @param bool $direct_call
* @return void|string
*/
function spbc_scanner_get_pdf_file_name($direct_call = false)
{
if ( !$direct_call ) {
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
}
global $spbc;
wp_send_json_success('spbct-mscan-'
. preg_replace('/^http(s)?:\/\//', '', site_url())
. '-'
. date('M-d-Y', $spbc->data['scanner']['last_scan'])
. '.pdf');
}
/**
* AJAX handler for cure action.
* @return void|string|WP_Error
*/
function spbc_file_cure_ajax_action()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
$file_fast_hash = isset($_POST['file_fast_hash']) ? esc_sql($_POST['file_fast_hash']) : null;
$result = spbc_cure_file($file_fast_hash);
if (is_wp_error($result)) {
wp_send_json_error($result->get_error_message());
}
wp_send_json_success($result);
}
/**
* AJAX handler for cure action.
* @param string $file_fast_hash
* @return string|WP_Error
*/
function spbc_cure_file($file_fast_hash)
{
global $wpdb;
if (is_null($file_fast_hash)) {
return new WP_Error(
'422',
esc_html__('Error: File not found.', 'security-malware-firewall')
);
}
$file_data = $wpdb->get_row(
'SELECT * '
. ' FROM ' . SPBC_TBL_SCAN_FILES
. ' WHERE fast_hash="' . $file_fast_hash . '";',
ARRAY_A
);
if (empty($file_data)) {
return new WP_Error(
'422',
esc_html__('Error: File not found in table.', 'security-malware-firewall')
);
}
// rescan file on a single run!
$rescan_results = spbc_scanner_rescan_single_file($file_data['path'], md5_file(spbc_get_root_path() . $file_data['path']), spbc_get_root_path());
$merged_result = $rescan_results['merged_result'];
$verdict = isset($rescan_results['signature_result']) && $rescan_results['signature_result'] instanceof Verdict
? $rescan_results['signature_result']
: new Verdict();
$cure_log = new CureLog();
if ( $verdict->status === 'OK') {
$cure_log->deleteCureLogRecord($file_data['fast_hash']);
// update file in the table
$wpdb->update(
SPBC_TBL_SCAN_FILES,
array(
'checked_signatures' => 1,
'checked_heuristic' => 1,
'status' => $file_data['status'] === 'MODIFIED' ? 'MODIFIED' : $merged_result['status'],
'severity' => $merged_result['severity'],
'weak_spots' => json_encode($merged_result['weak_spots']),
'full_hash' => md5_file(spbc_get_root_path() . $file_data['path']),
),
array('fast_hash' => $file_data['fast_hash']),
array('%s', '%s', '%s', '%s', '%s', '%s'),
array('%s')
);
return esc_html__('No threats detected for current file statement.', 'security-malware-firewall');
}
$cure_stage = new CureStage(DB::getInstance());
$cure_log_record = $cure_stage->processCure($file_data);
$cure_log->logCureResult($cure_log_record);
if ( !empty($cure_log_record->fail_reason) ) {
return new WP_Error(
'422',
esc_html__('Error: ' . $cure_log_record->fail_reason, 'security-malware-firewall')
);
}
return esc_html__('Success!', 'security-malware-firewall');
}
/**
* Restore file from backup handler
* @param string $id
* @return array
*/
function spbc_restore_file_from_backup_ajax_action_handler($id)
{
global $wpdb;
if (is_null($id)) {
return array('error' => esc_html__('Error: File not found.', 'security-malware-firewall'));
}
// Getting file path
$file_path_sql_prepared = $wpdb->prepare(
'SELECT path '
. ' FROM ' . SPBC_TBL_SCAN_FILES
. ' WHERE fast_hash=%s;',
$id
);
$file_path = $wpdb->get_row($file_path_sql_prepared, ARRAY_A);
if (is_null($file_path)) {
return array('error' => esc_html__('Error: File path not found.', 'security-malware-firewall'));
}
$file_path = $file_path['path'];
$full_file_path = ABSPATH . ltrim($file_path, '\\');
// Getting backup path
$sql_prepared = $wpdb->prepare(
'SELECT back_path, backup_prev_results_state '
. ' FROM ' . SPBC_TBL_BACKUPED_FILES
. ' WHERE real_path="%s"'
. ' ORDER BY backup_id DESC LIMIT 1;',
$file_path
);
// prepare backup data
$backup_data = $wpdb->get_results($sql_prepared, ARRAY_A);
$backup_path = isset($backup_data[0]['back_path']) ? $backup_data[0]['back_path'] : null;
// prepare previous state of the file
$backup_prev_results_state = isset($backup_data[0]['backup_prev_results_state']) ? $backup_data[0]['backup_prev_results_state'] : null;
$backup_prev_results_state = empty($backup_prev_results_state) || json_decode($backup_prev_results_state, true) === false
? null
: json_decode($backup_prev_results_state, true);
if (
!isset(
$backup_prev_results_state['weak_spots'],
$backup_prev_results_state['checked_heuristic'],
$backup_prev_results_state['checked_signatures'],
$backup_prev_results_state['status'],
$backup_prev_results_state['severity'],
$backup_prev_results_state['path']
)
) {
$backup_prev_results_state = null;
}
if (is_null($backup_path)) {
return array('error' => esc_html__('Error: Backup not found.', 'security-malware-firewall'));
}
$full_backup_path = ABSPATH . ltrim($backup_path, '/');
// Trying to replace backup and original file
$backup_content = file_get_contents($full_backup_path);
if ($backup_content === false) {
return array('error' => esc_html__('Error: File not exists or permissions denied.', 'security-malware-firewall'));
}
if (!file_exists($full_file_path)) {
return array('error' => esc_html__('Error: Original file not exists.', 'security-malware-firewall'));
}
$result = file_put_contents($full_file_path, $backup_content);
if ($result === false) {
return array('error' => esc_html__('Error: Permissions denied.', 'security-malware-firewall'));
}
// Success: remove all data about backup
$error_msg = array(
spbct_delete_backup_file($full_backup_path),
spbct_remove_backup_from_db($file_path),
spbct_update_cure_log($file_path),
spbct_update_scan_results($file_path, $backup_prev_results_state)
);
foreach ($error_msg as $value) {
if (array_key_exists('error', $value)) {
return $value;
}
}
return array('success' => true);
}
/**
* Deleting a backup file
* @param string $full_backup_path
* @return array
*/
function spbct_delete_backup_file($full_backup_path)
{
if ($full_backup_path) {
if (!unlink($full_backup_path)) {
return array('error' => esc_html__('Deleting backup error: Permissions denied.', 'security-malware-firewall'));
}
} else {
return array('error' => esc_html__('Deleting backup error: Incorrect backup file path.', 'security-malware-firewall'));
}
return array();
}
/**
* Deleting a backup from a database
* @param string $file_path
* @return array
*/
function spbct_remove_backup_from_db($file_path)
{
global $wpdb;
if ($file_path) {
$sql_prepared = $wpdb->prepare(
'DELETE FROM ' . SPBC_TBL_BACKUPED_FILES . ' WHERE real_path = %s;',
$file_path
);
if ($wpdb->query($sql_prepared) === false) {
return array('error' => esc_html__('Deleting backup error: Something is wrong during deleting backup.', 'security-malware-firewall'));
}
} else {
return array('error' => esc_html__('Deleting backup error: Incorrect backup file path for the database query.', 'security-malware-firewall'));
}
return array();
}
/**
* Changing the file information in the cure_log table regarding file recovery
* @param string $file_path
* @return array
*/
function spbct_update_cure_log($file_path)
{
global $wpdb;
if ($file_path) {
$sql_prepared = $wpdb->prepare(
'UPDATE ' . SPBC_TBL_CURE_LOG . ' SET is_restored = 1, cure_status = 0 WHERE real_path = %s;',
$file_path
);
if ($wpdb->query($sql_prepared) === false) {
return array('error' => esc_html__('Error update cure log: Something is wrong during updating cure log.', 'security-malware-firewall'));
}
} else {
return array('error' => esc_html__('Error update cure log: Incorrect backup file path for the database query.', 'security-malware-firewall'));
}
return array();
}
/**
* Changing file information after recovery
* @param string $file_path
* @param array $backup_prev_results_state
* @return array
*/
function spbct_update_scan_results($file_path, $backup_prev_results_state)
{
global $wpdb;
if ($file_path && $backup_prev_results_state !== null) {
$sql_prepared = $wpdb->prepare(
'UPDATE ' . SPBC_TBL_SCAN_FILES . ' SET weak_spots = %s, checked_heuristic = %s, checked_signatures = %s, status = %s, severity = %s WHERE path = %s;',
array(
$backup_prev_results_state['weak_spots'],
$backup_prev_results_state['checked_heuristic'],
$backup_prev_results_state['checked_signatures'],
$backup_prev_results_state['status'],
$backup_prev_results_state['severity'],
$file_path
)
);
if ($wpdb->query($sql_prepared) === false) {
return array('error' => esc_html__('Error update scan results: Something is wrong during saving previous state of file.', 'security-malware-firewall'));
}
} else {
return array('error' => esc_html__('Error update scan results: Incorrect data for changing the recovery results about the file', 'security-malware-firewall'));
}
return array();
}
/**
* Restore file from backup AJAX action handler
* @return void|string|WP_Error
*/
function spbc_restore_file_from_backup_ajax_action()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');
$file_fast_hash = isset($_POST['file_fast_hash']) ? esc_sql($_POST['file_fast_hash']) : null;
$restore_result = spbc_restore_file_from_backup_ajax_action_handler($file_fast_hash);
if (isset($restore_result['success'])) {
wp_send_json_success(esc_html__('Success!', 'security-malware-firewall'));
} elseif (isset($restore_result['error'])) {
wp_send_json_error($restore_result['error']);
}
}
/**
* @param $paths array
*
* @return array
*/
function spbc__get_exists_directories($paths)
{
$exists_dirs = array();
foreach ($paths as $path) {
if (is_dir(ABSPATH . $path)) {
$exists_dirs[] = $path;
}
}
return $exists_dirs;
}
/**
* Get JSON string of accordion row actions that do not need to be confirmed.
* @return string
*/
function spbc_get_no_confirm_row_actions()
{
global $spbc;
// by defaults
$actions = array (
'defaults' => array(
'copy_file_info',
'check_analysis_status',
),
'restricted' => array(),
);
// if license is trial
if ($spbc->data['license_trial']) {
$actions['restricted'][] = 'delete';
$actions['restricted'][] = 'quarantine';
$actions['restricted'][] = 'approve';
}
$actions['any'] = array_merge($actions['restricted'], $actions['defaults']);
$actions = json_encode($actions);
return is_string($actions) ? $actions : '{[]}';
}