shell bypass 403
<?php
/**
* @package admintools
* @copyright Copyright (c)2010-2025 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Plugin\Actionlog\AdminTools\Extension;
defined('_JEXEC') or die;
use Akeeba\Component\AdminTools\Administrator\Controller\DatabasetoolsController;
use Akeeba\Component\AdminTools\Administrator\Model\DatabasetoolsModel;
use Akeeba\Component\AdminTools\Administrator\Model\ScanalertsModel;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\User\User;
use Joomla\Component\Actionlogs\Administrator\Plugin\ActionLogPlugin;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;
use ReflectionMethod;
class AdminTools extends ActionLogPlugin implements SubscriberInterface
{
/**
* Load the language file on instantiation.
*
* @var boolean
* @since 3.1
*/
protected $autoloadLanguage = true;
private $defaultExtension = 'com_admintools';
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 9.0.0
*/
public static function getSubscribedEvents(): array
{
// Only subscribe events if the component is installed and enabled
if (!ComponentHelper::isEnabled('com_akeebabackup'))
{
return [];
}
// Register all public onSomething methods as event handlers
$events = [];
$refClass = new \ReflectionClass(__CLASS__);
$methods = $refClass->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method)
{
$name = $method->getName();
if (substr($name, 0, 2) != 'on')
{
continue;
}
$events[$name] = $name;
}
return $events;
}
public function onComAdmintoolsAdminallowlistControllerAfterApply($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_WHITELISTEDADDRESSES_EDIT_2');
}
public function onComAdmintoolsAdminallowlistControllerAfterSave($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_WHITELISTEDADDRESSES_EDIT_2');
}
public function onComAdmintoolsAdminallowlistControllerAfterSave2new($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_WHITELISTEDADDRESSES_EDIT_2');
}
public function onComAdmintoolsAdminallowlistsControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'ip', 'COM_ADMINTOOLS_LOGS_WHITELISTEDADDRESSES_DELETE');
}
public function onComAdmintoolsAdminpasswordControllerBeforeProtect($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_ADMINPASSWORD_ENABLE', 'com_admintools');
}
public function onComAdmintoolsAdminpasswordControllerBeforeUnprotect($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_ADMINPASSWORD_DISABLE', 'com_admintools');
}
public function onComAdmintoolsAutobannedaddressesControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'ip', 'COM_ADMINTOOLS_LOGS_AUTOBANNEDADDRESSES_DELETE');
}
public function onComAdmintoolsBadwordControllerAfterApply($event)
{
$this->logCRUDSave($event, 'id', 'word', 'COM_ADMINTOOLS_LOGS_BADWORDS_EDIT_2');
}
public function onComAdmintoolsBadwordControllerAfterSave($event)
{
$this->logCRUDSave($event, 'id', 'word', 'COM_ADMINTOOLS_LOGS_BADWORDS_EDIT_2');
}
public function onComAdmintoolsBadwordControllerAfterSave2new($event)
{
$this->logCRUDSave($event, 'id', 'word', 'COM_ADMINTOOLS_LOGS_BADWORDS_EDIT_2');
}
public function onComAdmintoolsBadwordsControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'word', 'COM_ADMINTOOLS_LOGS_BADWORDS_DELETE');
}
public function onComAdmintoolsBlockedrequestslogControllerBeforeBan($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_SECURITYEXCEPTIONS_BAN_2');
}
public function onComAdmintoolsBlockedrequestslogControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'ip', 'COM_ADMINTOOLS_LOGS_SECURITYEXCEPTIONS_DELETE');
}
public function onComAdmintoolsBlockedrequestslogControllerBeforeUnban($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_SECURITYEXCEPTIONS_UNBAN_2');
}
public function onComAdmintoolsChecktempandlogdirectoriesControllerBeforeCheck($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CHECKTEMPANDLOGDIRECTORIES_RUN', 'com_admintools');
}
public function onComAdmintoolsCleantempdirectoryControllerBeforeMain($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CLEANTEMPDIRECTORY_RUN', 'com_admintools');
}
public function onComAdmintoolsConfigurepermissionsControllerAfterSavedefaults($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CONFIGUREFIXPERMISSIONS_DEFAULTS', 'com_admintools');
}
public function onComAdmintoolsConfigurepermissionsControllerBeforeSaveapplyperms($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CONFIGUREFIXPERMISSIONS_SAVEAPPLYPERMS', 'com_admintools');
}
public function onComAdmintoolsConfigurepermissionsControllerBeforeSaveperms($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CONFIGUREFIXPERMISSIONS_SAVEPERMS', 'com_admintools');
}
public function onComAdmintoolsConfigurewafControllerAfterApply($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CONFIGUREWAF_EDIT', 'com_admintools');
}
public function onComAdmintoolsConfigurewafControllerAfterSave($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_CONFIGUREWAF_EDIT', 'com_admintools');
}
/**
* @param DatabasetoolsController $controller
*/
public function onComAdmintoolsDatabasetoolsControllerAfterOptimize($event)
{
$arguments = array_values($event->getArguments());
/** @var BaseController $controller */
$controller = $arguments[0];
/** @var DatabasetoolsModel $model */
$model = $controller->getModel();
$percent = $model->getState('percent', 0);
if ($percent >= 100)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_DATABASETOOLS_REPAIR', 'com_admintools');
}
}
public function onComAdmintoolsDatabasetoolsControllerBeforePurgesessions($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_DATABASETOOLS_PURGESESSIONS', 'com_admintools');
}
public function onComAdmintoolsEmergencyofflineControllerBeforeOffline($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_EMERGENCYOFFLINE_ENABLE', 'com_admintools');
}
public function onComAdmintoolsEmergencyofflineControllerBeforeOnline($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_EMERGENCYOFFLINE_DISABLE', 'com_admintools');
}
public function onComAdmintoolsExportimportControllerBeforeDoexport($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_IMPORANDEXPORT_EXPORT', 'com_admintools');
}
public function onComAdmintoolsExportimportControllerBeforeDoimport($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_IMPORANDEXPORT_IMPORT', 'com_admintools');
}
public function onComAdmintoolsFixpermissionsControllerBeforeMain($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_FIXPERMISSIONS_RUN', 'com_admintools');
}
public function onComAdmintoolsHtaccessmakerControllerAfterApply($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_HTACCESSMAKER_EDIT', 'com_admintools');
}
public function onComAdmintoolsHtaccessmakerControllerAfterSave($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_HTACCESSMAKER_EDIT', 'com_admintools');
}
public function onComAdmintoolsIPDenyListControllerAfterApply($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_BLACKLISTEDADDRESSES_EDIT_2');
}
/* Start of CRUD tasks */
public function onComAdmintoolsIPDenyListControllerAfterSave($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_BLACKLISTEDADDRESSES_EDIT_2');
}
public function onComAdmintoolsIPDenyListControllerAfterSave2new($event)
{
$this->logCRUDSave($event, 'id', 'ip', 'COM_ADMINTOOLS_LOGS_BLACKLISTEDADDRESSES_EDIT_2');
}
public function onComAdmintoolsIPDenyListsControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'ip', 'COM_ADMINTOOLS_LOGS_BLACKLISTEDADDRESSES_DELETE');
}
public function onComAdmintoolsIpautobanhistoriesControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'ip', 'COM_ADMINTOOLS_LOGS_IPAUTOBANHISTORIES_DELETE');
}
public function onComAdmintoolsMainpasswordControllerAfterApply($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_MASTERPASSWORD_EDIT', 'com_admintools');
}
public function onComAdmintoolsMainpasswordControllerAfterSave($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_MASTERPASSWORD_EDIT', 'com_admintools');
}
public function onComAdmintoolsNginxconfmakerControllerAfterApply($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_NGINXCONFMAKER_EDIT', 'com_admintools');
}
public function onComAdmintoolsNginxconfmakerControllerAfterSave($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_NGINXCONFMAKER_EDIT', 'com_admintools');
}
public function onComAdmintoolsQuickstartControllerAfterCommit($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_QUICKSTART_SAVE', 'com_admintools');
}
public function onComAdmintoolsScanalertsControllerAfterMarkallsafe($event)
{
$scan_id = $this->getApplication()->getInput()->getInt('scan_id', 0);
if (empty($scan_id))
{
return;
}
$this->logUserAction($scan_id, 'COM_ADMINTOOLS_LOGS_SCANALERTS_MARKEDALLSAFE', 'com_admintools');
}
public function onComAdmintoolsScanalertsControllerAfterPublish($event)
{
$arguments = array_values($event->getArguments());
/** @var BaseController $controller */
$controller = $arguments[0];
/** @var ScanalertsModel $model */
$model = $controller->getModel();
$ids = $this->getIDsFromRequest();
if (!$ids)
{
return;
}
$table = $model->getTable('Scanalert', 'Administrator');
foreach ($ids as $id)
{
if (!$table->load($id))
{
continue;
}
$this->logUserAction($table->path, 'COM_ADMINTOOLS_LOGS_SCANALERTS_MARKEDSAFE', 'com_admintools');
}
}
public function onComAdmintoolsScanalertsControllerAfterUnpublish($event)
{
$arguments = array_values($event->getArguments());
/** @var BaseController $controller */
$controller = $arguments[0];
/** @var ScanalertsModel $model */
$model = $controller->getModel();
$ids = $this->getIDsFromRequest();
if (!$ids)
{
return;
}
$table = $model->getTable('Scanalert', 'Administrator');
foreach ($ids as $id)
{
if (!$table->load($id))
{
continue;
}
$this->logUserAction($table->path, 'COM_ADMINTOOLS_LOGS_SCANALERTS_MARKEDUNSAFE', 'com_admintools');
}
}
public function onComAdmintoolsScansControllerBeforeStartscan($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_SCANS_RUN', 'com_admintools');
}
public function onComAdmintoolsSeoandlinktoolsControllerAfterApply($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_SEOANDLINKTOOLS_EDIT', 'com_admintools');
}
public function onComAdmintoolsSeoandlinktoolsControllerAfterSave($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_SEOANDLINKTOOLS_EDIT', 'com_admintools');
}
public function onComAdmintoolsUrlredirectionControllerAfterApply($event)
{
$this->logCRUDSave($event, 'id', 'dest', 'COM_ADMINTOOLS_LOGS_REDIRECTIONS_EDIT_2');
}
public function onComAdmintoolsUrlredirectionControllerAfterSave($event)
{
$this->logCRUDSave($event, 'id', 'dest', 'COM_ADMINTOOLS_LOGS_REDIRECTIONS_EDIT_2');
}
public function onComAdmintoolsUrlredirectionControllerAfterSave2new($event)
{
$this->logCRUDSave($event, 'id', 'dest', 'COM_ADMINTOOLS_LOGS_REDIRECTIONS_EDIT_2');
}
public function onComAdmintoolsUrlredirectionsControllerAfterPublish($event)
{
$this->logCRUDAction($event, 'dest', 'COM_ADMINTOOLS_LOGS_REDIRECTIONS_PUBLISH_2');
}
public function onComAdmintoolsUrlredirectionsControllerAfterUnpublish($event)
{
$this->logCRUDAction($event, 'dest', 'COM_ADMINTOOLS_LOGS_REDIRECTIONS_UNPUBLISH_2');
}
public function onComAdmintoolsUrlredirectionsControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'dest', 'COM_ADMINTOOLS_LOGS_REDIRECTIONS_DELETE');
}
public function onComAdmintoolsWafdenylistControllerAfterApply($event)
{
$this->logCRUDSave($event, 'id', 'id', 'COM_ADMINTOOLS_LOGS_WAFBLACKLIST_EDIT_2');
}
public function onComAdmintoolsWafdenylistControllerAfterSave($event)
{
$this->logCRUDSave($event, 'id', 'id', 'COM_ADMINTOOLS_LOGS_WAFBLACKLIST_EDIT_2');
}
public function onComAdmintoolsWafdenylistControllerAfterSave2new($event)
{
$this->logCRUDSave($event, 'id', 'id', 'COM_ADMINTOOLS_LOGS_WAFBLACKLIST_EDIT_2');
}
public function onComAdmintoolsWafdenylistsControllerAfterDelete($event)
{
$this->logCRUDAction($event, 'id', 'COM_ADMINTOOLS_LOGS_WAFBLACKLIST_DELETE');
}
public function onComAdmintoolsWafdenylistsControllerAfterPublish($event)
{
$this->logCRUDAction($event, 'id', 'COM_ADMINTOOLS_LOGS_WAFBLACKLIST_PUBLISH_2');
}
public function onComAdmintoolsWafdenylistsControllerAfterUnpublish($event)
{
$this->logCRUDAction($event, 'id', 'COM_ADMINTOOLS_LOGS_WAFBLACKLIST_UNPUBLISH_2');
}
public function onComAdmintoolsWafexceptionControllerAfterApply($event)
{
$this->logCRUDSave($event, 'id', 'id', 'COM_ADMINTOOLS_LOGS_WAFEXCEPTIONS_EDIT_2');
}
public function onComAdmintoolsWafexceptionControllerAfterSave($event)
{
$this->logCRUDSave($event, 'id', 'id', 'COM_ADMINTOOLS_LOGS_WAFEXCEPTIONS_EDIT_2');
}
public function onComAdmintoolsWafexceptionControllerAfterSave2new($event)
{
$this->logCRUDSave($event, 'id', 'id', 'COM_ADMINTOOLS_LOGS_WAFEXCEPTIONS_EDIT_2');
}
public function onComAdmintoolsWafexceptionsControllerBeforeDelete($event)
{
$this->logCRUDAction($event, 'id', 'COM_ADMINTOOLS_LOGS_WAFEXCEPTIONS_DELETE');
}
public function onComAdmintoolsWebconfigmakerControllerAfterApply($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_WEBCONFIGMAKER_EDIT', 'com_admintools');
}
public function onComAdmintoolsWebconfigmakerControllerAfterSave($event)
{
$this->logUserAction('', 'COM_ADMINTOOLS_LOGS_WEBCONFIGMAKER_EDIT', 'com_admintools');
}
/* End of CRUD tasks */
/**
* Gets the list of IDs from the request data
*
* @return array
*/
private function getIDsFromRequest()
{
// Get the ID or list of IDs from the request or the configuration
$cid = $this->getApplication()->getInput()->get('cid', [], 'array');
$id = $this->getApplication()->getInput()->getInt('id', 0);
$ids = [];
if (is_array($cid) && !empty($cid))
{
$ids = $cid;
}
elseif (!empty($id))
{
$ids = [$id];
}
return $ids;
}
/**
* @param $controller
* @param string $displayKey
* @param string $translationKey
*/
private function logCRUDAction(Event $event, string $displayKey, string $translationKey): void
{
$arguments = array_values($event->getArguments());
/** @var BaseController $controller */
$controller = $arguments[0];
$ids = $this->getIDsFromRequest();
$model = $controller->getModel();
$tableName = $controller->getName();
try
{
$table = $model->getTable($tableName, 'Administrator');
}
catch (\Exception $e)
{
$tableName = $this->singularize($tableName);
$table = $model->getTable($tableName, 'Administrator');
}
foreach ($ids as $id)
{
if (!$table->load($id))
{
continue;
}
$primaryKey = $table->getKeyName(false);
$link = sprintf(
"index.php?option=com_admintools&view=%s&task=edit&%s=%s",
urlencode($tableName),
urlencode($primaryKey),
urlencode($table->{$primaryKey}));
$this->logUserAction([
'title' => $table->{$displayKey},
'link' => $link,
], $translationKey, 'com_admintools');
}
}
/**
* @param $controller
* @param string $primaryKey
* @param string $displayKey
* @param string $translationKey
*/
private function logCRUDSave(Event $event, string $primaryKey, string $displayKey, string $translationKey): void
{
$arguments = array_values($event->getArguments());
/** @var BaseController $controller */
$controller = $arguments[0];
$model = $controller->getModel();
$controllerName = $controller->getName();
$tableName = $controller->getName();
try
{
$table = $model->getTable($tableName, 'Administrator');
}
catch (\Exception $e)
{
$tableName = $this->singularize($tableName);
$table = $model->getTable($tableName, 'Administrator');
}
if (!$table->load($this->getApplication()->getInput()->getInt($primaryKey)))
{
return;
}
$link = sprintf(
"index.php?option=com_admintools&view=%s&task=edit&%s=%s",
urlencode($controllerName),
urlencode($primaryKey),
urlencode($table->{$primaryKey}));
$this->logUserAction([
'title' => $table->{$displayKey},
'link' => $link,
], $translationKey, 'com_admintools');
}
/**
* Log a user action.
*
* This is a simple wrapper around self::addLog
*
* @param string|array $title Language key for title or an array of additional data to record in
* the audit log.
* @param string $messageLanguageKey Language key describing the user action taken.
* @param string|null $context The name of the extension being logged (default: use
* $this->defaultExtension).
* @param User|null $user User object taking this action (default: currently logged in user).
*
* @return void
*
* @see self::addLog
* @since 9.0.0
*/
private function logUserAction($title, string $messageLanguageKey, ?string $context = null, ?User $user = null): void
{
// Get the user if not defined
$user = $user ?? $this->getApplication()->getIdentity();
// No log for guests
if (empty($user) || ($user->guest))
{
return;
}
// Default extension if none defined
$context = $context ?? $this->defaultExtension;
$message = [
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
if (!is_array($title))
{
$title = [
'title' => $title,
];
}
$message = array_merge($message, $title);
$this->addLog([$message], $messageLanguageKey, $context, $user->id);
}
/**
* Convert an English word to singular.
*
* @param string $word
*
* @return string
* @since 7.7.0
*/
private function singularize(string $word): string
{
$rules = [
// Singularization rules. The regex on the left transforms to the regex on the right.
'singularization' => [
'/cookies$/i' => 'cookie',
'/moves$/i' => 'move',
'/sexes$/i' => 'sex',
'/children$/i' => 'child',
'/men$/i' => 'man',
'/feet$/i' => 'foot',
'/people$/i' => 'person',
'/taxa$/i' => 'taxon',
'/databases$/i' => 'database',
'/menus$/i' => 'menu',
'/(quiz)zes$/i' => '\1',
'/(matr|suff)ices$/i' => '\1ix',
'/(vert|ind|cod)ices$/i' => '\1ex',
'/^(ox)en/i' => '\1',
'/(alias|status)es$/i' => '\1',
'/(tomato|hero|buffalo)es$/i' => '\1',
'/([octop|vir])i$/i' => '\1us',
'/(gen)era$/i' => '\1us',
'/(cris|^ax|test)es$/i' => '\1is',
'/is$/i' => 'is',
'/us$/i' => 'us',
'/ias$/i' => 'ias',
'/(shoe)s$/i' => '\1',
'/(o)es$/i' => '\1e',
'/(bus)es$/i' => '\1',
'/(campus)es$/i' => '\1',
'/([m|l])ice$/i' => '\1ouse',
'/(x|ch|ss|sh)es$/i' => '\1',
'/(m)ovies$/i' => '\1ovie',
'/(s)eries$/i' => '\1eries',
'/(v)ies$/i' => '\1ie',
'/([^aeiouy]|qu)ies$/i' => '\1y',
'/([lr])ves$/i' => '\1f',
'/(tive)s$/i' => '\1',
'/(hive)s$/i' => '\1',
'/([^f])ves$/i' => '\1fe',
'/(^analy)ses$/i' => '\1sis',
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
'/([ti]|addend)a$/i' => '\1um',
'/(alumn|formul)ae$/i' => '$1a',
'/(n)ews$/i' => '\1ews',
'/(.*)ss$/i' => '\1ss',
'/(.*)s$/i' => '\1',
],
// Uncountable objects are always singular
'uncountable' => [
'aircraft',
'cannon',
'deer',
'equipment',
'fish',
'information',
'money',
'moose',
'news',
'rice',
'series',
'sheep',
'species',
'swine',
],
];
if (in_array($word, $rules['uncountable']))
{
return $word;
}
foreach ($rules['singularization'] as $regexp => $replacement)
{
$matches = null;
$singular = preg_replace($regexp, $replacement, $word, -1, $matches);
if ($matches > 0)
{
return $singular;
}
}
return $word;
}
}