Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New. Scan. Added OSCron module. #428

Closed
wants to merge 5 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
OSCron. Ready.
alexandergull committed Nov 21, 2024
commit e74951c24c6df755718e648dbe7e2dcd1d63598f
2 changes: 1 addition & 1 deletion css/spbc-settings.min.css

Large diffs are not rendered by default.

20 changes: 11 additions & 9 deletions inc/spbc-admin.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
<?php

use CleantalkSP\SpbctWP\AdjustToEnvironmentModule\AdjustToEnvironmentHandler;
use CleantalkSP\SpbctWP\CleantalkSettingsTemplates;
use CleantalkSP\SpbctWP\Cron;
use CleantalkSP\SpbctWP\LinkConstructor;
use CleantalkSP\SpbctWP\Scanner\Cure;
use CleantalkSP\SpbctWP\Escape;
use CleantalkSP\Variables\Post;
use CleantalkSP\Variables\Server;
use CleantalkSP\SpbctWP\Firewall\WAF;
use CleantalkSP\SpbctWP\LinkConstructor;
use CleantalkSP\SpbctWP\ListTable;
use CleantalkSP\SpbctWP\Scanner\Cure;
use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OsCronTasksStorage;
use CleantalkSP\SpbctWP\Scanner\ScannerQueue;
use CleantalkSP\SpbctWP\CleantalkSettingsTemplates;
use CleantalkSP\SpbctWP\G2FA\GoogleAuthenticator;
use CleantalkSP\SpbctWP\Variables\Cookie;
use CleantalkSP\SpbctWP\VulnerabilityAlarm\VulnerabilityAlarm;
use CleantalkSP\Variables\Post;
use CleantalkSP\Variables\Server;

// Settings page
require_once('spbc-settings.php');
@@ -1059,13 +1058,16 @@ function spbc_set_malware_scan_warns()
AND (pscan_processing_status <> "DONE")';
$analysis_has_uncheked = (int)$wpdb->get_var($query) <> 0;

$oscron_has_dangerous = OsCronTasksStorage::getCountOfDangerousTasks();

$spbc->data['display_scanner_warnings'] = array(
'critical' => $critical_count,
'signatures' => $signatures_count,
'frontend' => $frontend_count,
'analysis' => $analysis_has_dangerous,
'analysis_all_safe' => !$analysis_has_uncheked && !$analysis_has_dangerous,
'warn_on_admin_bar' => $critical_count || $frontend_count || $analysis_has_dangerous
'oscron' => $oscron_has_dangerous,
'analysis_all_safe' => !$analysis_has_uncheked && !$analysis_has_dangerous && !$oscron_has_dangerous,
'warn_on_admin_bar' => $critical_count || $frontend_count || $analysis_has_dangerous || $oscron_has_dangerous,
);
$spbc->notice_critical_files_warning = !empty($critical_count);
$spbc->save('data');
24 changes: 13 additions & 11 deletions inc/spbc-settings.php
Original file line number Diff line number Diff line change
@@ -10,8 +10,8 @@
use CleantalkSP\SpbctWP\LinkConstructor;
use CleantalkSP\SpbctWP\ListTable;
use CleantalkSP\SpbctWP\Scanner;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronModel;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronView;
use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OsCronTasksStorage;
use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronView;
use CleantalkSP\SpbctWP\Scanner\ScanningLog\ScanningLogFacade;
use CleantalkSP\SpbctWP\Variables\Cookie;
use CleantalkSP\SpbctWP\Views\Settings;
@@ -680,8 +680,8 @@ function spbc_settings__register()
),
'scanner__os_cron_analysis' => array(
'type' => 'field',
'title' => Scanner\OSCron\OSCronLocale::getInstance()->settings_option_title,
'description' => Scanner\OSCron\OSCronLocale::getInstance()->settings_option_description,
'title' => Scanner\OSCron\View\OSCronLocale::getInstance()->settings__option_title,
'description' => Scanner\OSCron\View\OSCronLocale::getInstance()->settings__option_description,
'long_description' => false,
),
'scanner__dir_exclusions_view' => array(
@@ -2935,7 +2935,7 @@ function spbc_scanner_oscron_count_found()
*/
function spbc_scanner_oscron_get_scanned()
{
return OSCronModel::getTasksFromStorageAsArray();
return OsCronTasksStorage::getAsArray();
}

/**
@@ -3537,7 +3537,7 @@ function spbc_field_scanner__show_accordion($direct_call = false)
}

if ($spbc->settings['scanner__os_cron_analysis']) {
$tables_files['oscron'] = Scanner\OSCron\OSCronLocale::getInstance()->settings_accordion_tab_description;
$tables_files['oscron'] = Scanner\OSCron\View\OSCronLocale::getInstance()->settings__accordion_tab_description;
}

if ($spbc->settings['scanner__list_approved_by_cleantalk']) {
@@ -3633,6 +3633,7 @@ function spbc_field_scanner__show_accordion($direct_call = false)
($type_name === 'critical' && $spbc->data['display_scanner_warnings']['critical'])
|| ($type_name === 'frontend_malware' && $spbc->data['display_scanner_warnings']['frontend'])
|| ($type_name === 'analysis_log' && $spbc->data['display_scanner_warnings']['analysis'])
|| ($type_name === 'oscron' && $spbc->data['display_scanner_warnings']['oscron'])
) {
$danger_dot = '<span class="red_dot"></span>';
}
@@ -4074,17 +4075,18 @@ function spbc_list_table__get_args_by_type($table_type)
'func_data_total' => 'spbc_scanner_oscron_count_found',
'func_data_get' => 'spbc_scanner_oscron_get_scanned',
'func_data_prepare' => 'spbc_scanner_oscron_prepare_data',
'if_empty_items' => '<p class="spbc_hint">' . __('Crontab not found.', 'security-malware-firewall') . '</p>',
'if_empty_items' => '<div class="notice notice-info spbc-icon-info" style="padding: 10px; margin: 10px 0px;">' . __('Crontab not found in the server environment or is unavailable to read/write.', 'security-malware-firewall') . '</div>',
'columns' => array(
'id' => array('heading' => 'id', 'width_percent' => 10),
'status' => array('heading' => 'Status', 'primary' => true, 'width_percent' => 15),
'command' => array('heading' => 'Command', 'width_percent' => 40),
'repeat' => array('heading' => 'Repeats on', 'width_percent' => 15),
'analysis_status' => array('heading' => 'Analysis Status', 'width_percent' => 15),
'command' => array('heading' => 'Command', 'width_percent' => 35),
'repeats' => array('heading' => 'Repeats on', 'width_percent' => 15),
'line_number' => array('heading' => 'Line number', 'width_percent' => 10),
),
'actions' => array(
'disable_oscron_task' => array('name' => Scanner\OSCron\OSCronLocale::getInstance()->action_disable_task,),
'approve_oscron_task' => array('name' => Scanner\OSCron\OSCronLocale::getInstance()->action_approve_task,),
'disable_oscron_task' => array('name' => Scanner\OSCron\View\OSCronLocale::getInstance()->action__disable_task,),
'approve_oscron_task' => array('name' => Scanner\OSCron\View\OSCronLocale::getInstance()->action__approve_task,),
),
)
);
4 changes: 2 additions & 2 deletions js/spbc-react-bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/spbc-scanner-plugin.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/spbc-scanner-plugin.min.js.map

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions lib/CleantalkSP/SpbctWP/ListTable.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

use CleantalkSP\SpbctWP\Scanner\Cure;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronController;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronLocale;
use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale;
use CleantalkSP\Variables\Post;

class ListTable
@@ -641,7 +641,9 @@ public static function ajaxRowActionHandler()
{
spbc_check_ajax_referer('spbc_secret_nonce', 'security');

if ( Post::get('add_action', null, 'word') !== 'restore' ) {
$ajax_action = Post::get('add_action', null, 'word');

if ( $ajax_action !== 'restore' && strpos($ajax_action, 'oscron_task') === false ) {
$check_file_exist_result = self::spbcCheckFileExist();

if (isset($check_file_exist_result['error'])) {
@@ -659,7 +661,7 @@ public static function ajaxRowActionHandler()
}

try {
switch ( Post::get('add_action', null, 'word') ) {
switch ( $ajax_action ) {
case 'approve':
self::ajaxRowActionHandlerApprove();
break;
@@ -712,36 +714,46 @@ public static function ajaxRowActionHandler()

public static function ajaxRowActionHandlerApproveOSCronTask()
{
global $spbc;
if ( $spbc->data['license_trial'] == 1 ) {
wp_send_json(['error' => spbc_get_trial_restriction_notice(), 'hide_support_link' => '1']);
}
$result = OSCronController::approveTask(Post::get('id', null, 'word'));
if ($result) {
if (true === $result) {
$out = array(
'html' => '<div class="spbc-popup-msg popup--green">'
. OSCronLocale::getInstance()->controller_approved
. OSCronLocale::getInstance()->controller__task_approved
. '</div>',
'success' => true,
'color' => 'black',
'background' => 'rgba(110, 240, 110, 0.7)',
);
wp_send_json($out);
} else {
wp_send_json_error(esc_html($result));
}
wp_send_json_error();
}

public static function ajaxRowActionHandlerDisableOSCronTask()
{
global $spbc;
if ( $spbc->data['license_trial'] == 1 ) {
wp_send_json(['error' => spbc_get_trial_restriction_notice(), 'hide_support_link' => '1']);
}
$result = OSCronController::disableTask(Post::get('id', null, 'word'));
if ($result) {
if (true === $result) {
$out = array(
'html' => '<div class="spbc-popup-msg popup--red">'
. OSCronLocale::getInstance()->controller_disabled
. OSCronLocale::getInstance()->controller__task_disabled
. '</div>',
'success' => true,
'color' => 'black',
'background' => 'rgba(110, 240, 110, 0.7)',
'background' => 'rgba(240, 110, 110, 0.7)',
);
wp_send_json($out);
} else {
wp_send_json_error(esc_html($result));
}
wp_send_json_error();
}

public static function ajaxRowActionHandlerApprove()
39 changes: 25 additions & 14 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronController.php
Original file line number Diff line number Diff line change
@@ -2,37 +2,48 @@

namespace CleantalkSP\SpbctWP\Scanner\OSCron;

use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OsCronTasksStorage;
use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale;

class OSCronController
{
/**
* @param $uid
* @param $status
* @return bool
* @throws \Exception
* Updates the status of a task and rewrites the cron tab file.
*
* @param string $uid The unique identifier of the task.
* @param string $status The new status of the task.
* @return string|true True if the operation is successful, error string otherwise
* @throws \Exception If an error occurs during the update.
*/
private static function updateTask($uid, $status)
{
$task_to_change = OSCronModel::getTaskById($uid);
$task_to_change = OSCronTasksStorage::getById($uid);
$task_to_change->setStatus($status);
$result = OSCronModel::updateTaskById($uid, $task_to_change);
$result = OSCronModel::updateTaskOfStorageById($uid, $task_to_change);
if (false === $result) {
return false;
return OSCronLocale::getInstance()->error__task_not_found;
}
return OSCronModel::rewriteCronTabFile();
return OSCronModel::rewriteEnvCron();
}

/**
* @param $uid
* @return bool
* @throws \Exception
* Approves a task by its unique identifier.
*
* @param string $uid The unique identifier of the task.
* @return string|true True if the operation is successful, error string otherwise
* @throws \Exception If an error occurs during the approval.
*/
public static function approveTask($uid)
{
return static::updateTask($uid, 'approved');
}

/**
* @param $uid
* @return bool
* @throws \Exception
* Disables a task by its unique identifier.
*
* @param string $uid The unique identifier of the task.
* @return string|true True if the operation is successful, error string otherwise
* @throws \Exception If an error occurs during the disablement.
*/
public static function disableTask($uid)
{
57 changes: 0 additions & 57 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronLocale.php

This file was deleted.

405 changes: 235 additions & 170 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronModel.php

Large diffs are not rendered by default.

148 changes: 0 additions & 148 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronTask.php

This file was deleted.

49 changes: 49 additions & 0 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/OSCronTaskAnalyser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron;

class OSCronTaskAnalyser
{
private $verdict = false;
public function __construct()
{
}

/**
* @param string $command
* @return $this
*/
public function check($command)
{
$this->verdict = self::hasRM($command);
//add more checks here
return $this;
}

/**
* Checks if a command is dangerous.
*
* @param string $command The command to check.
* @return bool True if the command is dangerous, false otherwise.
*/
private static function hasRM($command)
{
try {
//implement command check here
if (strpos($command, 'rm') !== false) {
return true;
}
} catch (\Exception $e) {
return false;
}
return false;
}

/**
* @return bool
*/
public function getVerdict()
{
return $this->verdict;
}
}
220 changes: 220 additions & 0 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/Objects/OSCronTask.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron\Objects;

use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale;

class OSCronTask
{
/**
* @var string The unique identifier for the task.
*/
public $id;
/**
* @var string The whole line of the task.
*/
public $whole_line;
/**
* @var string The cron pattern for task repetition.
*/
public $status;
/**
* @var string The cron pattern for task repetition.
*/
public $repeats;
/**
* @var string The command to be executed by the cron task.
*/
public $command;
/**
* @var int The line number in the cron file.
* @psalm-suppress PossiblyUnusedProperty
*/
public $line_number;
/**
* @var string The hash of the task.
*/
public $hash;
/**
* @var array List of possible statuses for the task.
*/
private $statuses_list = array(
'found',
'approved',
'disabled',
'dangerous',
);
/**
* @var array List of possible statuses for the task.
*/
private $analysis_statuses_list = array(
'safe',
'dangerous',
);
/**
* @var string
* @psalm-suppress UnusedProperty
*/
private $analysis_status;

/**
* Constructor to initialize the task with an optional item.
*
* @param string $item Optional item to initialize the task.
* @throws \Exception If the item is invalid.
*/
public function __construct($item = '')
{
$this->setWholeLine($item);
$this->setID();
$this->setHash(md5($item));
$this->setAnalysisStatus('safe');
}

/**
* Sets a unique identifier for the task.
*/
private function setID()
{
$this->id = uniqid();
}

/**
* Sets the whole line for the task. Means the whole line of the task in current crontab state.
*
* @param string $whole_line The whole line to set.
* @return OSCronTask
* @throws \Exception If the command is invalid.
*/
public function setWholeLine($whole_line)
{
if (!is_string($whole_line)) {
throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error__invalid_arg);
}
$this->whole_line = $whole_line;
return $this;
}

/**
* Sets the status of the task.
*
* @param string $status The status to set.
* @return OSCronTask
* @throws \Exception If the status is invalid.
*/
public function setStatus($status)
{
if (!is_string($status) || !in_array($status, $this->statuses_list)) {
throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error__invalid_arg);
}

$this->status = $status;
return $this;
}

/**
* Sets the command for the task.
*
* @param string $command The command to set.
* @return OSCronTask
* @throws \Exception If the command is invalid.
*/
public function setCommand($command)
{
if (!is_string($command)) {
throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error__invalid_arg);
}
$this->command = $command;
return $this;
}

/**
* Sets the cron pattern for task repetition.
*
* @param string $repeats The cron pattern to set.
* @return OSCronTask
* @throws \Exception If the pattern is invalid.
*/
public function setRepeatsPattern($repeats)
{
if (!is_string($repeats)) {
throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error__invalid_arg);
}
$this->repeats = $repeats;
return $this;
}

/**
* Sets the line number in the cron file.
*
* @param int $line_number The line number to set.
* @return OSCronTask
* @throws \Exception If the line number is invalid.
*/
public function setLineNumber($line_number)
{
if (!is_int($line_number)) {
throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' ' . OSCronLocale::getInstance()->error__invalid_arg);
}
$this->line_number = $line_number;
return $this;
}

/**
* Sets the hash for the task.
*
* @param string $hash The hash to set.
*/
private function setHash($hash)
{
if (!is_string($hash)) {
$hash = '';
}
$this->hash = $hash;
}

/**
* Sets the vulnerability status for the task.
*
* @param string|false $analysis_status The vulnerability to set.
*/
public function setAnalysisStatus($analysis_status)
{
if (!is_string($analysis_status) || !in_array($analysis_status, $this->analysis_statuses_list)) {
$analysis_status = 'safe';
}
$this->analysis_status = $analysis_status;
}

/**
* Validates the task properties.
*
* @return OSCronTask
* @throws \Exception If any property is missing.
*/
public function validate()
{
foreach (get_object_vars($this) as $property => $value) {
if (is_null($value)) {
throw new \Exception(__CLASS__ . '::' . __FUNCTION__ . ' - ' . $property . ' ' . OSCronLocale::getInstance()->error__missing_property);
}
}
return $this;
}


/**
* Returns the task properties as an array.
*
* @return array The task properties.
*/
public function getArray()
{
$result = array();
foreach (get_object_vars($this) as $property => $value) {
$result[$property] = $value;
}
unset($result['statuses_list']);
return $result;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron;
namespace CleantalkSP\SpbctWP\Scanner\OSCron\Shell;

class OSCronGetEnvCron
{
/**
* @var string The command to read the current crontab.
*/
private static $read_command = 'crontab -l';

/**
* @return string
* Retrieves the current crontab entries.
*
* @return string The current crontab entries.
* @psalm-suppress ForbiddenCode
*/
public static function get()
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron;
namespace CleantalkSP\SpbctWP\Scanner\OSCron\Shell;

class OSCronSetEnvCron
{
/**
* @var string The template for the command to write to the crontab.
*/
private static $write_command_template = 'echo "%s"| crontab -';

/**
* @return string
* Sets a new cron set using the provided command.
*
* @param string $command The command to be added to the crontab.
* @return void
* @psalm-suppress ForbiddenCode
*/
public static function set($command)
{
//probably need to sanitize $command
//$command = escapeshellarg($command);
$command = sprintf(static::$write_command_template, $command);
return (string)@shell_exec($command);
@shell_exec($command);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron\Storages;

class OSCronFileStorage
{
/**
* @var string The name of the option to store the cron file.
*/
private static $file_storage_name = 'spbc_oscron_file';

/**
* Saves the cron file to storage.
*
* @param string $cron_file The content of the cron file.
*/
public static function set($cron_file)
{
update_option(static::$file_storage_name, @base64_encode($cron_file));
}

/**
* Loads the cron file from storage.
*
* @return string The content of the cron file or empty string if not found.
* @throws \Exception If an error occurs during loading.
*/
public static function get()
{
$result = get_option(static::$file_storage_name);
if (false !== $result) {
$result = @base64_decode($result);
if (false !== $result) {
return $result;
}
}
return '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron\Storages;

use CleantalkSP\SpbctWP\Scanner\OSCron\Objects\OSCronTask;
use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale;

class OsCronTasksStorage
{
/**
* @var string The name of the option to store tasks.
*/
private static $tasks_storage_name = 'spbc_oscron_tasks';

/**
* Saves the tasks to storage.
*
* @param OSCronTask[] $tasks The tasks to save.
*/
public static function set($tasks)
{
update_option(static::$tasks_storage_name, $tasks);
}

/**
* Retrieves the tasks from storage.
*
* @return OSCronTask[] The tasks from storage.
*/
public static function get()
{
$tasks = get_option(static::$tasks_storage_name, []);

if (is_null($tasks) || false === $tasks) {
return array();
}

return $tasks;
}

/**
* Retrieves a task by its unique identifier.
*
* @param string $uid The unique identifier of the task.
* @return OSCronTask|false The task if found, false otherwise.
* @throws \Exception If an error occurs during retrieval.
*/
public static function getById($uid)
{
$tasks = static::get();
foreach ($tasks as $task) {
if ($task->id === $uid) {
return $task;
}
}
return false;
}

/**
* Get count of dangerous tasks.
* @return int
*/
public static function getCountOfDangerousTasks()
{
$count = 0;
$tasks = static::get();
foreach ($tasks as $task) {
if ($task->status === 'dangerous') {
$count++;
}
}
return $count;
}


/**
* Retrieves the tasks from storage as an array.
*
* @return array The tasks from storage as an array.
*/
public static function getAsArray()
{
global $spbc;
$tasks = static::get();
$result_array = array();
foreach ($tasks as $task) {
if ($task instanceof OSCronTask) {
$result_array[] = $task->getArray();
} else {
$spbc->error_add('', OSCronLocale::getInstance()->error__load_cron_storage);
}
}
return $result_array;
}
}
59 changes: 59 additions & 0 deletions lib/CleantalkSP/SpbctWP/Scanner/OSCron/View/OSCronLocale.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron\View;

use CleantalkSP\Templates\Singleton;

class OSCronLocale
{
use Singleton;

public $task__status_approved;
public $task__status_disabled;
public $task__status_dangerous;
public $task__status_found;
public $task__analysis_status_dangerous;
public $task__analysis_status_safe;
public $no_windows_support;
//public $error__write_cron_storage;
public $error__write_cron_env;
public $error__load_cron_storage;
public $error__load_env_cron;
public $error__invalid_arg;
public $error__missing_property;
public $error__invalid_cron_expression;
public $error__task_not_found;
public $controller__task_approved;
public $controller__task_disabled;
public $action__disable_task;
public $action__approve_task;
public $settings__accordion_tab_description;
public $settings__option_title;
public $settings__option_description;

public function init()
{
$this->task__status_approved = __('Approved', 'security-malware-firewall');
$this->task__status_disabled = __('Disabled', 'security-malware-firewall');
$this->task__status_dangerous = __('Dangerous', 'security-malware-firewall');
$this->task__status_found = __('Found', 'security-malware-firewall');
$this->task__analysis_status_dangerous = __('Dangerous', 'security-malware-firewall');
$this->task__analysis_status_safe = __('Safe', 'security-malware-firewall');
$this->action__disable_task = __('Disable', 'security-malware-firewall');
$this->action__approve_task = __('Approve', 'security-malware-firewall');
$this->no_windows_support = __('Windows OS cron handling is not supported.', 'security-malware-firewall');
$this->controller__task_approved = __('Task has been approved.', 'security-malware-firewall');
$this->controller__task_disabled = __('Task has been disabled.', 'security-malware-firewall');
//$this->error__write_cron_storage = __('Storage: Cannot write cron', 'security-malware-firewall');
$this->error__write_cron_env = __('Environment: Cannot write cron', 'security-malware-firewall');
$this->error__load_cron_storage = __('Storage: Cannot load cron', 'security-malware-firewall');
$this->error__load_env_cron = __('Environment: Cannot load cron', 'security-malware-firewall');
$this->error__invalid_arg = __('invalid argument', 'security-malware-firewall');
$this->error__missing_property = __('property is no set', 'security-malware-firewall');
$this->error__invalid_cron_expression = __('Invalid cron expression', 'security-malware-firewall');
$this->error__task_not_found = __('Task tried to update is not found', 'security-malware-firewall');
$this->settings__accordion_tab_description = __('This section provides an overview of scheduled cron jobs on server that perform automated tasks. Check your cron jobs for suspicious entries and delete, modify, or send them to the CleanTalk Cloud for analysis necessary to ensure security.', 'security-malware-firewall');
$this->settings__option_title = __('Operating system cron tasks analysis', 'security-malware-firewall');
$this->settings__option_description = __('This functional provides an overview of scheduled cron jobs on server that perform automated tasks.', 'security-malware-firewall');
}
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
<?php

namespace CleantalkSP\SpbctWP\Scanner\OSCron;
namespace CleantalkSP\SpbctWP\Scanner\OSCron\View;

use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OsCronTasksStorage;

class OSCronView
{
/**
* @return int
* Returns the count of tasks scanned.
*
* @return int The number of tasks scanned.
*/
public static function getCountOfTasksScanned()
{
return count(OSCronModel::getTasksFromStorage());
return count(OsCronTasksStorage::get());
}

/**
* @param object $table
* @return object
* Prepares the table data for display.
*
* @param object $table The table object containing rows of data.
* @return object The modified table object with prepared data.
*/
public static function prepareTableData($table)
{
foreach ($table->rows as $key => $row) {
$actions = $row['actions'];
// drop inverted actions
if ($row['status'] === 'approved') {
unset($actions['approve_oscron_task']);
}
if ($row['status'] === 'disabled') {
unset($actions['disable_oscron_task']);
}
// prepare
$table->items[$key] = array(
'uid' => $row['id'],
'cb' => $row['id'],
'id' => $row['id'],
'repeat' => static::timePatternToHumanReadable($row['repeats']),
// do not convert to human-readable if status is 'disabled'
'repeats' => $row['status'] !== 'disabled' ? static::timePatternToHumanReadable($row['repeats']) : '-',
'command' => $row['command'],
'analysis_status' => static::decorateAnalysisStatus($row['analysis_status']),
'status' => static::decorateStatus($row['status']),
'line_number' => $row['line_number'],
'actions' => $actions,
@@ -40,40 +50,65 @@ public static function prepareTableData($table)
return $table;
}

/**
* Decorates the status with appropriate HTML and CSS classes.
*
* @param string $status The status of the task.
* @return string The decorated status with HTML and CSS classes.
*/
private static function decorateStatus($status)
{
$class = '';
if ($status === 'dangerous') {
$status = OSCronLocale::getInstance()->task_status_dangerous;
$status = OSCronLocale::getInstance()->task__status_dangerous;
$class = 'spbc-icon-attention-alt spbc---red';
}
if ($status === 'approved') {
$status = OSCronLocale::getInstance()->task_status_approved;
$status = OSCronLocale::getInstance()->task__status_approved;
$class = 'spbc-icon-ok spbc---green';
}
//disabled
if ($status === 'disabled') {
$status = OSCronLocale::getInstance()->task_status_disabled;
$status = OSCronLocale::getInstance()->task__status_disabled;
$class = 'spbc-icon-cancel spbc---gray';
}
if ($status === 'found') {
$status = OSCronLocale::getInstance()->task_status_found;
$status = OSCronLocale::getInstance()->task__status_found;
$class = 'spbc-icon-search';
}
return '<b class="'. esc_html($class) .'">' . $status . '</b>';
return '<b class="' . esc_html($class) . '">' . $status . '</b>';
}

/**
* Decorates the analysis status with appropriate HTML and CSS classes.
*
* @param string $status The status of the task.
* @return string The decorated status with HTML and CSS classes.
*/
private static function decorateAnalysisStatus($status)
{
$class = 'spbc---gray';
if ($status === 'dangerous') {
$status = OSCronLocale::getInstance()->task__analysis_status_dangerous;
}
if ($status === 'safe') {
$status = OSCronLocale::getInstance()->task__analysis_status_safe;
}
return '<span class="' . esc_html($class) . '">' . strtoupper($status) . '</span>';
}

/**
* Convert cron time to a human-readable string.
* @param $time
* @return string
* Converts a cron time pattern to a human-readable string.
*
* @param string $time The cron time pattern.
* @return string The human-readable string representation of the cron time pattern.
*/
private static function timePatternToHumanReadable($time)
{
$cronParts = explode(' ', $time);

if (count($cronParts) !== 5) {
return OSCronLocale::getInstance()->error_invalid_cron_expression;
return OSCronLocale::getInstance()->error__invalid_cron_expression;
}

list($minute, $hour, $dayOfMonth, $month, $dayOfWeek) = $cronParts;
2 changes: 0 additions & 2 deletions lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php
Original file line number Diff line number Diff line change
@@ -1034,8 +1034,6 @@ public function os_cron_analysis() // phpcs:ignore PSR1.Methods.CamelCapsMethodN

$result = OSCronModel::run();

error_log('CTDEBUG: [' . __FUNCTION__ . '] [$result]: ' . var_export($result,true));

if (true !== $result) {
ScanningLogFacade::writeToLog(
'<b>' . $stage_data_obj::getTitle() . '</b> ' . $result
1 change: 1 addition & 0 deletions lib/CleantalkSP/SpbctWP/State.php
Original file line number Diff line number Diff line change
@@ -201,6 +201,7 @@ class State extends \CleantalkSP\Common\State
'signatures' => false,
'frontend' => false,
'analysis' => false,
'oscron' => false,
'warn_on_admin_bar' => false
),
'site_utc_offset_in_seconds' => 0,
138 changes: 68 additions & 70 deletions tests/Scanner/TestOSCron.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?php

use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronLocale;
use CleantalkSP\SpbctWP\Scanner\OSCron\Objects\OSCronTask;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronModel;
use CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask;
use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OSCronFileStorage;
use CleantalkSP\SpbctWP\Scanner\OSCron\Storages\OsCronTasksStorage;
use CleantalkSP\SpbctWP\Scanner\OSCron\View\OSCronLocale;

class TestOSCron extends PHPUnit\Framework\TestCase
{
@@ -15,32 +17,32 @@ public function setUp()
}

public function testTasksSaveLoad() {
$tasks = OSCronModel::parseTasks($this->test_cron_content);
OSCronModel::saveTasksToStorage($tasks);
$saved_tasks = OSCronModel::getTasksFromStorage(false);
$tasks = OSCronModel::parseEnvTasks($this->test_cron_content);
OsCronTasksStorage::set($tasks);
$saved_tasks = OsCronTasksStorage::get();
$this->assertEquals($tasks, $saved_tasks);
}

public function testContentSaveLoad() {
OSCronModel::saveCronFileToStorage($this->test_cron_content);
$this->assertEquals($this->test_cron_content, OSCronModel::loadCronFileFromStorage());
OSCronFileStorage::set($this->test_cron_content);
$this->assertEquals($this->test_cron_content, OSCronFileStorage::get());
}

public function testNoDifference()
{
OSCronModel::saveCronFileToStorage($this->test_cron_content);
OSCronFileStorage::set($this->test_cron_content);
$this->expectException(\Exception::class);
if (OSCronModel::loadCronFileFromStorage() === $this->test_cron_content) {
if (OSCronFileStorage::get() === $this->test_cron_content) {
throw new \Exception(__('No difference found since last check.', 'security-malware-firewall'));
}
}

public function testParseTasks()
{
$tasks = OSCronModel::parseTasks($this->test_cron_content);
$tasks = OSCronModel::parseEnvTasks($this->test_cron_content);
$this->assertIsArray($tasks);
foreach ($tasks as $task) {
$this->assertInstanceOf('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task);
$this->assertInstanceOf('CleantalkSP\SpbctWP\Scanner\OSCron\Objects\OSCronTask', $task);
}
}

@@ -52,23 +54,23 @@ public function testValidateValidTasks()
->setLineNumber(1)
->setRepeatsPattern('1 * * * *')
->validate();
$this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task);
$this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\Objects\OSCronTask', $task);

$task = new OSCronTask();
$task->setCommand('echo')
->setStatus('approved')
->setLineNumber(1211)
->setRepeatsPattern('9 * * * *')
->validate();
$this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task);
$this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\Objects\OSCronTask', $task);

$task = new OSCronTask();
$task->setCommand('echo')
->setStatus('disabled')
->setLineNumber(221)
->setRepeatsPattern('10 * * * *')
->validate();
$this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\OSCronTask', $task);
$this->assertInstanceof('CleantalkSP\SpbctWP\Scanner\OSCron\Objects\OSCronTask', $task);
}

public function testValidateInValidTasks()
@@ -114,9 +116,9 @@ public function testUpdateTaskFail()
->setLineNumber(2212)
->setRepeatsPattern('1 * * * *')
->validate();
OSCronModel::saveTasksToStorage(array($task));
OsCronTasksStorage::set(array($task));
//wrong uid
$this->assertFalse(OSCronModel::updateTaskById('123', $task));
$this->assertFalse(OSCronModel::updateTaskOfStorageById('123', $task));
}

public function testUpdateTaskOK()
@@ -127,67 +129,63 @@ public function testUpdateTaskOK()
->setLineNumber(2212)
->setRepeatsPattern('1 * * * *')
->validate();
OSCronModel::saveTasksToStorage(array($task));
$this->assertIsInt(OSCronModel::updateTaskById($task->id, $task));
OsCronTasksStorage::set(array($task));
$this->assertIsString(OSCronModel::updateTaskOfStorageById($task->id, $task));
}

// public function testApprove()
// {
// $task = new OSCronTask();
// $task->setCommand('echo')
// ->setStatus('found')
// ->setLineNumber(2212)
// ->setRepeatsPattern('1 * * * *')
// ->validate();
// OSCronModel::saveTasksToStorage(array($task));
// $this->assertTrue(OSCronController::approveTask($task->id));
// }
//
// public function testDisable()
// {
// $task = new OSCronTask();
// $task->setCommand('echo')
// ->setStatus('found')
// ->setLineNumber(2212)
// ->setRepeatsPattern('1 * * * *')
// ->validate();
// OSCronModel::saveTasksToStorage(array($task));
// $this->assertTrue(OSCronController::disableTask($task->id));
// }

public function testisWindows()
{
$this->assertTrue(OSCronModel::isWindows());
}
public function testisWindows()
{
$this->assertIsBool(OSCronModel::isWindows());
}

public function testRun()
{
$result = OSCronModel::run();
if (OSCronModel::isWindows()) {
public function testRun()
{
$result = OSCronModel::run();
if (OSCronModel::isWindows()) {
$this->assertIsString($result);
$this->assertStringContainsString('Windows', $result);
} else {
if (true !== $result) {
$this->assertIsString($result);
$this->assertStringContainsString('Windows', $result);
} else {
if (true !== $result) {
$this->assertIsString($result);
}
}
}
}

public function testLocale()
{
$this->assertIsString(OSCronLocale::getInstance()->error_invalid_cron_expression);
}
public function testLocale()
{
$this->assertIsString(OSCronLocale::getInstance()->error__invalid_cron_expression);
}

public function testCommandCheck()
{
$task = new OSCronTask();
$task->setCommand('rm')
->setStatus('approved')
->setLineNumber(1)
->setRepeatsPattern('1 * * * *')
->validate();
$tasks = OSCronModel::analyzeTasks(array($task));
$this->assertEquals('dangerous', $tasks[0]->status);
}
public function testCommandCheck()
{
//danger without precheck
$task = new OSCronTask();
$task->setCommand('rm')
->setStatus('found')
->setLineNumber(1)
->setRepeatsPattern('1 * * * *')
->validate();
$tasks = OSCronModel::analyzeEnvTasks(array($task));
$this->assertEquals('dangerous', $tasks[0]->status);

// approved with precheck
OsCronTasksStorage::set(array());
$storage_task = new OSCronTask('1 * * * * rm');
$storage_task->setCommand('rm')
->setStatus('approved')
->setLineNumber(1)
->setRepeatsPattern('1 * * * *')
->validate();
OsCronTasksStorage::set(array($storage_task));

$task = new OSCronTask('1 * * * * rm');
$task->setCommand('rm')
->setStatus('found')
->setLineNumber(1)
->setRepeatsPattern('1 * * * *')
->validate();
$tasks = OSCronModel::analyzeEnvTasks(array($task));
$this->assertEquals('approved', $tasks[0]->status);
}

}