| Current Path : /var/www/html/plugins/system/rsfirewallconsole/src/ |
| Current File : /var/www/html/plugins/system/rsfirewallconsole/src/CheckMalwareCommand.php |
<?php
/*
* @package RSFirewall!
* @copyright (c) 2009 - 2024 RSJoomla!
* @link https://www.rsjoomla.com/joomla-extensions/joomla-security.html
* @license GNU General Public License https://www.gnu.org/licenses/gpl-3.0.en.html
*/
namespace Rsjoomla\Plugin\System\Rsfirewallconsole;
use Joomla\Console\Command\AbstractCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\InputArgument;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
defined('_JEXEC') or die;
class CheckMalwareCommand extends AbstractCommand
{
use HelperFunctions;
protected $symfonyStyle;
protected $num_files;
protected static $defaultName = 'rsfirewall:check-malware';
protected function doExecute(InputInterface $input, OutputInterface $output): int
{
require_once JPATH_ADMINISTRATOR . '/components/com_rsfirewall/models/check.php';
$this->symfonyStyle = new SymfonyStyle($input, $output);
// load the Joomla library language file
Factory::getApplication()->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR);
Factory::getApplication()->getLanguage()->load('com_rsfirewall', JPATH_ADMINISTRATOR);
$useLog = boolval($input->getOption('log'));
$scriptStart = microtime(true);
try
{
$this->symfonyStyle->title(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE'));
$this->symfonyStyle->info(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_PHP_INFO', ini_get('memory_limit'), ini_get('max_execution_time')));
// Check if Xdebug is enabled and try to disable it
if (extension_loaded('xdebug'))
{
$this->symfonyStyle->warning(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_ENABLED'));
$this->symfonyStyle->writeln(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_TRY_DISABLE'));
// try and disable it, if no stop the script
if (function_exists('xdebug_disable'))
{
xdebug_disable();
$this->symfonyStyle->success(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_DISABLE_SUCCESS'));
}
else
{
throw new \Exception(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_XDEBUG_DISABLE_FAIL'));
}
}
$model = new \RsfirewallModelCheck();
// output a message that states we are scanning the instalation for all the files
$this->symfonyStyle->writeln(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_SCANNING'));
// count all files so that we can create the progress bar
$this->num_files = $this->numAllFiles($model);
// output the number of files we found
$this->symfonyStyle->writeln(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FOUND_NR_FILES', $this->num_files));
// create the progress bar
$this->symfonyStyle->progressStart($this->num_files);
$output = $this->checkSignatures($model);
$this->symfonyStyle->progressFinish();
if (!empty($output))
{
$this->parseOutput($output, $useLog);
}
else
{
$this->symfonyStyle->success(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_NOT_FOUND_FILES'));
}
}
catch (\Exception $e)
{
$err = $e->getMessage();
// remove the tags from the error message
$err = strip_tags($err);
$this->symfonyStyle->error($err);
return $e->getCode();
}
$time = number_format(microtime(true) - $scriptStart, 2, '.', '');
$this->symfonyStyle->writeln(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_FINISHED_IN_SECONDS', $time));
$this->symfonyStyle->success(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_SUCCESS'));
return 0;
}
protected function numAllFiles($model, $start = 'none', $limit = 0)
{
static $last_file;
$num_files = 0;
$root = JPATH_SITE;
// workaround to grab the correct root
if ($root == '') {
$root = '/';
}
// if the limit is not set get
if (empty($limit))
{
$limit = $model->getConfig()->get('offset', 300);
}
$model->setOffsetLimit($limit);
// if the start is none use the root
$start = $start == 'none' ? $root : $start;
// this is so we know where to stop
if (is_null($last_file))
{
$last_file = $model->getLastFile($root);
}
if (is_file($start) && $last_file === $start) {
return $num_files;
}
$files = $model->getFilesRecursive($start);
if ($files === false)
{
throw new \Exception($model->getError());
}
// add to the count
$num_files = count($files);
$next_file = end($files);
if ($next_file != $last_file) {
$rest = $this->numAllFiles($model, $next_file , $limit);
$num_files += $rest;
}
return $num_files;
}
protected function checkSignatures($model, $start = 'none', $limit = 0)
{
static $last_file;
static $set = 0;
$output = [];
// increment the set of files that are scanned
$set++;
$root = JPATH_SITE;
// workaround to grab the correct root
if ($root == '') {
$root = '/';
}
// if the limit is not set get
if (empty($limit))
{
$limit = $model->getConfig()->get('offset', 300);
}
$model->setOffsetLimit($limit);
// if the start is none use the root
$start = $start == 'none' ? $root : $start;
// this is so we know where to stop
if (is_null($last_file))
{
$last_file = $model->getLastFile($root);
}
if (is_file($start) && $last_file === $start) {
return $output;
}
$files = $model->getFilesRecursive($start);
if ($files === false)
{
throw new \Exception($model->getError());
}
foreach ($files as $file)
{
if ($result = $model->checkSignatures($file))
{
$result['path'] = substr_replace($file, '', 0, strlen(JPATH_SITE.DIRECTORY_SEPARATOR));
$output[] = $result;
}
}
$progress = count($files);
$this->symfonyStyle->progressAdvance($progress);
$next_file = end($files);
if ($next_file != $last_file) {
$rest = $this->checkSignatures($model, $next_file , $limit);
$output = array_merge($output, $rest);
}
return $output;
}
protected function parseOutput($output = [], $useLog = false, $restore = false, $confirm = false)
{
$problems = [];
$this->symfonyStyle->warning(Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FOUND_FILES', count($output)));
foreach ($output as $file)
{
$log = [];
// match
$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FOUND', $file['match']);
// reason
$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_REASON', $file['reason']);
// path
$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_PATH', $file['path']);
$date_log = '';
$file_time = $this->getFileTime($file['path']);
if (strlen($file_time['relative']))
{
$log[] = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FILE_AGO', $file_time['relative']);
$date_log = Text::sprintf('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FILE_DATE', $file_time['time']);
}
$cli_message = implode("\n", $log);
// show the output of the file
$this->symfonyStyle->writeln($cli_message);
$this->symfonyStyle->writeln('');
// remove the relative from the log
array_pop($log);
// add the modified date
if (strlen($date_log))
{
$log[] = $date_log;
}
$problems[] = implode("\n", $log);
}
if ($useLog && !empty($problems))
{
require_once JPATH_ADMINISTRATOR . '/components/com_rsfirewall/helpers/log.php';
$logger = \RSFirewallLogger::getInstance();
// use the server timezone
$logger->setTimezone(false);
if (!empty($problems))
{
// save them with new lines
$problems = implode("\n\n", $problems);
$logger->add('critical','MALWARE_FILES_FOUND', $problems)->save();
}
$this->symfonyStyle->newLine();
$this->symfonyStyle->writeln(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_FILES_ADDED_TO_LOG'));
}
}
protected function configure(): void
{
$this->addOption('log', 'l', InputArgument::OPTIONAL, Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_USE_LOG_OPTION'), '');
$this->setDescription(Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_DESCRIPTION'));
$help_1 = Text::_('PLG_SYSTEM_RSFIREWALLCONSOLE_COMMAND_CHECKMALWARE_HELP_1');
$this->setHelp(
<<<EOF
RSFirewall!
###########
{$help_1}
php joomla.php rsfirewall:check-malware --log=1
EOF
);
}
}