Server : Apache/2.4.18 (Ubuntu) System : Linux canvaswebdesign 3.13.0-71-generic #114-Ubuntu SMP Tue Dec 1 02:34:22 UTC 2015 x86_64 User : oppastar ( 1041) PHP Version : 7.0.33-0ubuntu0.16.04.15 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, Directory : /var/www/andreassugianto.com/public_html/plugins/system/admintools/admintools/ |
Upload File : |
<?php /* * Administrator Tools * Copyright (C) 2010-2013 Nicholas K. Dionysopoulos / AkeebaBackup.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ defined('_JEXEC') or die(); // Compatibility with @securejoomla audit service if (defined('_BF_AUDIT')) return; JLoader::import('joomla.application.plugin'); class plgSystemAdmintoolsPro extends JPlugin { private $cparams = null; private $hasGeoIPPECL = false; private $exceptions = array(); private $skipFiltering = false; private $timestamps = array(); public function __construct(& $subject, $config = array()) { JLoader::import('joomla.html.parameter'); JLoader::import('joomla.plugin.helper'); JLoader::import('joomla.application.component.helper'); $plugin = JPluginHelper::getPlugin('system', 'admintools'); $defaultConfig = (array) ($plugin); $config = array_merge($defaultConfig, $config); // Use the parent constructor to create the plugin object parent::__construct($subject, $config); // Work around non-transparent proxy and reverse proxy IP issues include_once JPATH_ADMINISTRATOR . '/components/com_admintools/helpers/ip.php'; if (class_exists('AdmintoolsHelperIp', false)) { AdmintoolsHelperIp::workaroundIPIssues(); } // Load the components parameters JLoader::import('joomla.application.component.model'); require_once JPATH_ROOT . '/administrator/components/com_admintools/models/storage.php'; if (interface_exists('JModel')) { $this->cparams = JModelLegacy::getInstance('Storage', 'AdmintoolsModel'); } else { $this->cparams = JModel::getInstance('Storage', 'AdmintoolsModel'); } // Check if the GeoIP PECL extension is loaded, otherwise load the // PHP-based implementation of the library. if (@file_exists(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat')) { if (!function_exists('geoip_country_code_by_name')) { require_once JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'geoip.php'; $this->hasGeoIPPECL = false; } else { $this->hasGeoIPPECL = true; } } // Load WAF exceptions $this->loadExceptions(); if (empty($this->exceptions)) { $this->exceptions = array(); } else { if (empty($this->exceptions[0])) { $this->skipFiltering = true; } } } /** * Log a security exception coming from a third party application. It's * supposed to be used by 3PD to log security exceptions in Admin Tools' * log. * * @param string $message The blocking reason to show to the administrator. MANDATORY. * @param string $message The message to show to the user being blocked. MANDATORY. * @param array $extraInfo Any extra information to record to the log file (hash array). * @param boolean $autoban Should we also auto-ban this IP immediately? * * @return void */ public function onAdminToolsThirdpartyException($reason, $message, $extraInfo = array(), $autoban = false) { if (empty($message)) { return; } // Block the request $this->blockRequest('external', $message, $extraInfo, $reason); // Automatically ban the IP if auto IP banning is enabled and the $autoban flag is set $autobanipenabled = $this->cparams->getValue('tsrenable', 0); if ($autobanipenabled && $autoban) { $this->autoBan('external'); } } /** * Hooks to the onAfterInitialize system event, the first time in the * Joomla! page load workflow which fires a plug-in event */ public function onAfterInitialise() { // Automatic banning $this->AutoIPFiltering(); // IP Blacklisting if ($this->cparams->getValue('ipbl', 0) == 1) $this->IPFiltering(); // GeoBlocking $cnt = $this->cparams->getValue('geoblockcountries', ''); $con = $this->cparams->getValue('geoblockcontinents', ''); if (!empty($cnt) || !empty($con)) { $this->geoBlocking(); } // Check for admin access if ($this->isAdminAccessAttempt()) { // IP Whitelist filtering for back-end access if ($this->cparams->getValue('ipwl', 0) == 1) $this->adminIPFiltering(); // Administrator "secret word" protection $this->adminPasswordProtection(); if ($this->cparams->getValue('twofactorauth', 0) == 1) { $version = explode('.', JVERSION); $version = $version[0] . $version[1]; $template = JFactory::getApplication()->getTemplate(); $cssAlt = array( "login.css", "login-$version.css", "login-$version-$template.css", ); $paths = array( '../media/com_admintools/css', "templates/$template/media/com_admintools/css" ); JLoader::import('joomla.filesystem.file'); foreach ($paths as $path) { foreach ($cssAlt as $cssFile) { $url = $path . '/' . $cssFile; $filename = JPATH_ADMINISTRATOR . '/' . $url; if (JFile::exists($filename)) { JFactory::getDocument()->addStyleSheet($url); } } } } } if ($this->isAdminAccessAttempt(true)) { if ($this->cparams->getValue('twofactorauth', 0) == 1) { $this->twoFactorAuthentication_verify(); } } // Remove inactive users if ($this->params->get('deleteinactive', 0) == 1) $this->removeInactiveUsers(); // Remove old log records if ($this->params->get('maxlogentries', 0) > 0) $this->removeOldLogEntries(); $app = JFactory::getApplication(); // Back-end stuff if (in_array($app->getName(), array('administrator', 'admin'))) { // Block access to the extensions installer if ($this->cparams->getValue('blockinstall', 0) > 0) $this->blockInstall(); // Disable editing of back-end users if ($this->cparams->getValue('nonewadmins', 0) == 1) $this->noNewAdmins(); // Email on administrator access $emailonadmin = $this->cparams->getValue('emailonadminlogin', ''); if (!empty($emailonadmin)) $this->emailOnAdminLogin(); // If there is an administrator secret word set, upon logout redirect to the site's home page $password = $this->cparams->getValue('adminpw', ''); if (!empty($password)) { if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option', ''); $task = $input->getCmd('task', ''); $uid = $input->getInt('uid', 0); } else { $option = JRequest::getCmd('option', ''); $task = JRequest::getCmd('task', ''); $uid = JRequest::getInt('uid', 0); } $loggingMeOut = true; if (!empty($uid)) { $myUID = JFactory::getUser()->id; $loggingMeOut = $myUID == $uid; } if (($option == 'com_login') && ($task == 'logout') && $loggingMeOut) { // Logout and redirect to the homepage $result = $app->logout(); $baseURL = JURI::base(); $baseURL = str_replace('/administrator', '', $baseURL); $app->redirect($baseURL); } } } else // Front-end stuff { if (!$this->skipFiltering) { // HTTP:BL integration if ($this->cparams->getValue('httpblenable', 0) == 1) $this->ProjectHoneypotHTTPBL(); // SQL Injection shielding if ($this->cparams->getValue('sqlishield', 0) == 1) $this->SQLiShield(); // XSS shielding if ($this->cparams->getValue('xssshield', 0) == 1) $this->XSSShield(); // Malicious User Agent shielding if ($this->cparams->getValue('muashield', 0) == 1) $this->MUAShield(); // CSRF shield / anti-spam form filtering if ($this->cparams->getValue('csrfshield', 0) == 1) { $this->CSRFShield_BASIC(); } elseif ($this->cparams->getValue('csrfshield', 0) == 2) { $this->CSRFShield_ADVANCED(); } // RFIShield if ($this->cparams->getValue('rfishield', 1) == 1) $this->RFIShield(); // DFIShield if ($this->cparams->getValue('dfishield', 1) == 1) $this->DFIShield(); // UploadShield if ($this->cparams->getValue('uploadshield', 1) == 1) $this->UploadShield(); // Anti-spam if ($this->cparams->getValue('antispam', 0) == 1) $this->antiSpam(); // Disable template switching (tmpl) if ($this->cparams->getValue('tmpl', 0) == 1) $this->disableTmplSwitch(); // Disable template switching (template) if ($this->cparams->getValue('template', 0) == 1) $this->disableTemplateSwitch(); } // Custom URL redirection if ($this->cparams->getValue('urlredirection', 1) == 1) $this->customRouter(); // Session optimizer if ($this->params->get('sesoptimizer', 0) == 1) $this->sessionOptimizer(); // Session cleaner if ($this->params->get('sescleaner', 0) == 1) $this->sessionCleaner(); // Cache cleaner if ($this->params->get('cachecleaner', 0) == 1) $this->cacheCleaner(); // Cache expiration if ($this->params->get('cacheexpire', 0) == 1) $this->cacheExpire(); // Temp-directory cleaning if ($this->params->get('cleantemp', 0) == 1) $this->cleanTemp(); // Log purging if ($this->params->get('purgelog', 0) == 1) $this->purgeLog(); } } public function onAfterRender() { $app = JFactory::getApplication(); if ($this->isAdminAccessAttempt()) { if ($this->cparams->getValue('twofactorauth', 0) == 1) { // Two factor authentication $this->twoFactorAuthentication_process(); } } if (in_array($app->getName(), array('administrator', 'admin'))) { } else { if ($this->cparams->getValue('csrfshield', 0) == 2) { $this->CSRFShield_PROCESS(); } } } public function onAfterRoute() { // Naughty, naughty trick if (JFactory::getSession()->get('block', false, 'com_admintools')) { // This is an underhanded way to short-circuit Joomla!'s internal router. JRequest::set(array( 'option' => 'com_admintools' ), 'get', true); } } public function onAfterDispatch() { $app = JFactory::getApplication(); // Back-end stuff if (in_array($app->getName(), array('administrator', 'admin'))) { // Email on failed administrator access if (version_compare(JVERSION, '2.5.0', 'lt')) { $emailonfailedadmin = $this->cparams->getValue('emailonfailedadminlogin', ''); if (!empty($emailonfailedadmin)) $this->emailOnFailedAdminLogin(); } } else { // Front-end stuff // Meta generator cloaking if ($this->cparams->getValue('custgenerator', 0) == 1) $this->cloakGenerator(); } } /** * Joomla! 1.5 failed login handler * @param array $response */ public function onLoginFailure($response) { if ($this->cparams->getValue('trackfailedlogins', 0)) { $this->trackFailedLogin(); } else { $app = JFactory::getApplication(); // Back-end stuff if (in_array($app->getName(), array('administrator', 'admin'))) { // Email on failed administrator access $emailonfailedadmin = $this->cparams->getValue('emailonfailedadminlogin', ''); if (!empty($emailonfailedadmin)) $this->emailOnFailedAdminLogin(true); } } } /** * Joomla! 1.6+ failed login handler * @param array $response */ public function onUserLoginFailure($response) { $this->onLoginFailure($response); } /** * User login event fired by Joomla! 1.5, redirected to the Joomla! 1.6 event * @param JUser $user * @param array $options */ public function onUserLogin($user, $options) { return $this->onLoginUser($user, $options); } /** * User login event fired by Joomla! 1.6 * @param unknown_type $user * @param unknown_type $options */ public function onLoginUser($user, $options) { $app = JFactory::getApplication(); $instance = $this->_getUser($user, $options); // Disallow front-end Super Administrator login if ($this->cparams->getValue('nofesalogin', 0) == 1) { if (!in_array($app->getName(), array('administrator', 'admin'))) { // Is the user a Super Administrator $isSuperAdmin = false; // Joomla! 1.6+ - Check all groups for core.admin privileges foreach ($instance->groups as $group) { $isSuperAdmin |= JAccess::checkGroup($group, 'core.admin'); } // Block SAs if ($isSuperAdmin) { $newopts = array(); $app->logout($instance->id, $newopts); // Damn you, Joomla! Since 2.5.5 you have to close the session before throwing // an error, otherwise the user isn't logged out. What the hell?! $session = JFactory::getSession(); $session->close(); // Throw error $this->loadLanguage('plg_system_admintools'); JError::raiseError(403, JText::_('JGLOBAL_AUTH_ACCESS_DENIED')); return false; } } } return true; } public function onUserAfterSave($user, $isnew, $success, $msg) { // Save the user's signup IP if ($this->cparams->getValue('saveusersignupip', 0) == 1) { $process = true; // Only trigger on successful user creation if (!$success) { $process = false; } // Only trigger on new user creation, not subsequent edits if (!$isnew) { $process = false; } // Only trigger on front-end user creation. if (JFactory::getApplication()->isAdmin()) { $process = false; } // Create a new user note if ($process) { // Get the user's ID $user_id = (int) $user['id']; // Get the IP address $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; if ((strpos($ip, '::') === 0) && (strstr($ip, '.') !== false)) { $ip = substr($ip, strrpos($ip, ':') + 1); } // Get the user agent string $user_agent = $_SERVER['HTTP_USER_AGENT']; // Get current date and time in database format JLoader::import('joomla.utilities.date'); $now = new JDate(); if (version_compare(JVERSION, '3.0', 'ge')) { $now = $now->toSql(); } else { $now = $now->toSql(); } // Load the component's administrator translation files $jlang = JFactory::getLanguage(); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true); // Create and save the user note $userNote = (object) array( 'user_id' => $user_id, 'catid' => 0, 'subject' => JText::_('ATOOLS_LBL_WAF_SIGNUPIP_SUBJECT'), 'body' => JText::sprintf('ATOOLS_LBL_WAF_SIGNUPIP_BODY', $ip, $user_agent), 'state' => 1, 'created_user_id' => 42, 'created_time' => $now ); $db = JFactory::getDbo(); $db->insertObject('#__user_notes', $userNote, 'id'); } } } /** * Filters back-end access by IP. If the IP of the visitor is not included * in the whitelist, he gets redirected to the home page */ private function adminIPFiltering() { // Let's get a list of allowed IP ranges $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select($db->qn('ip')) ->from($db->qn('#__admintools_adminiplist')); $db->setQuery($sql); if (version_compare(JVERSION, '3.0', 'ge')) { $ipTable = $db->loadColumn(); } else { $ipTable = $db->loadResultArray(); } if (empty($ipTable)) return; $inList = $this->IPinList($ipTable); if ($inList === false) { if (!$this->logBreaches('ipwl')) return; $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan('ipwl'); $this->redirectAdminToHome(); } } /** * Filters visitor access by IP. If the IP of the visitor is included in the * blacklist, he gets a 403 error */ private function IPFiltering() { // Let's get a list of blocked IP ranges $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select($db->qn('ip')) ->from($db->qn('#__admintools_ipblock')); $db->setQuery($sql); if (version_compare(JVERSION, '3.0', 'ge')) { $ipTable = $db->loadColumn(); } else { $ipTable = $db->loadResultArray(); } if (empty($ipTable)) return; $inList = $this->IPinList($ipTable); if ($inList === true) { $message = $this->cparams->getValue('custom403msg', ''); if (empty($message)) { $message = 'ADMINTOOLS_BLOCKED_MESSAGE'; } // Merge the default translation with the current translation $jlang = JFactory::getLanguage(); // Front-end translation $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, null, true); // Do we have an override? $langOverride = $this->params->get('language_override', ''); if (!empty($langOverride)) { $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, $langOverride, true); } if ((JText::_('ADMINTOOLS_BLOCKED_MESSAGE') == 'ADMINTOOLS_BLOCKED_MESSAGE') && ($message == 'ADMINTOOLS_BLOCKED_MESSAGE')) { $message = "Access Denied"; } else { $message = JText::_($message); } // Show the 403 message if ($this->cparams->getValue('use403view', 0)) { // Using a view if (!JFactory::getSession()->get('block', false, 'com_admintools') || JFactory::getApplication()->isAdmin()) { // This is inside an if-block so that we don't end up in an infinite rediretion loop JFactory::getSession()->set('block', true, 'com_admintools'); JFactory::getSession()->set('message', $message, 'com_admintools'); JFactory::getSession()->close(); $base = JURI::base(); if (JFactory::getApplication()->isAdmin()) { $base = rtrim($base); $base = substr($base, -13); } JFactory::getApplication()->redirect($base); } } else { if (JFactory::getApplication()->isAdmin()) { // You can't use Joomla!'s error page in the admin area. Improvise! header('HTTP/1.1 403 Forbidden'); echo $message; JFactory::getApplication()->close(); } else { // Using Joomla!'s error page JError::raiseError('403', $message); } } } } /** * Checks if the secret word is set in the URL query, or redirects the user * back to the home page. */ private function adminPasswordProtection() { $password = $this->cparams->getValue('adminpw', ''); if (empty($password)) return; $myURI = JURI::getInstance(); // If the "password" query param is not defined, the default value // "thisisnotgood" is returned. If it is defined, it will return null or // the value after the equal sign. $check = $myURI->getVar($password, 'thisisnotgood'); if ($check == 'thisisnotgood') { // Uh oh... Unauthorized access! Let's redirect the perp back to the // site's home page. if (!$this->logBreaches('adminpw')) return; $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan('adminpw'); $this->redirectAdminToHome(); } } /** * Cloak the generator meta tag */ private function cloakGenerator() { // Only do this for the front-end application $app = JFactory::getApplication(); if ($app->getName() != 'site') return; $generator = $this->cparams->getValue('generator', ''); if (empty($generator)) $generator = 'MYOB'; // Mind Your Own Business, peeping Tom! $document = JFactory::getDocument(); $document->setGenerator($generator); } /** * Disable template switching in the URL */ private function disableTmplSwitch() { $tmpl = JRequest::getCmd('tmpl', null); if (empty($tmpl)) return; $whitelist = $this->cparams->getValue('tmplwhitelist', 'component,system'); if (empty($whitelist)) $whitelist = 'component,system'; $temp = explode(',', $whitelist); $whitelist = array(); foreach ($temp as $item) { $whitelist[] = trim($item); } $whitelist = array_merge(array('component', 'system'), $whitelist); if (!is_null($tmpl) && !in_array($tmpl, $whitelist)) { if (!$this->logBreaches('tmpl')) return; $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan('tmpl'); JRequest::setVar('tmpl', null); } } /** * Disable template switching in the URL */ private function disableTemplateSwitch() { static $siteTemplates = array(); $template = JRequest::getCmd('template', null); $block = true; if (!empty($template)) { // Exception: existing site templates are allowed if (JRequest::getCmd('option', '') == 'com_mailto') { // com_email URLs in Joomla! 1.7 and later have template= defined; force $allowsitetemplate in this case $allowsitetemplate = true; } else { // Otherwise, allow only of the switch is set $allowsitetemplate = $this->cparams->getValue('allowsitetemplate', 0); } if ($allowsitetemplate) { if (empty($siteTemplates)) { JLoader::import('joomla.filesystem.folder'); $siteTemplates = JFolder::folders(JPATH_SITE . '/templates'); } $block = !in_array($template, $siteTemplates); } if ($block) { if (!$this->logBreaches('template')) return; $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan('template'); JRequest::setVar('template', null); } } } /** * Fend off most common types of SQLi attacks. See the comments in the code * for more security-minded information. */ private function SQLiShield() { // We filter all hashes separately to guard against underhanded injections. // For example, if the parameter registration to the $_REQUEST array is // GPCS, a GET variable will "hide" a POST variable during a POST request. // If the vulnerable component is, however, *explicitly* asking for the // POST variable, if we only check the $_REQUEST superglobal array we will // miss the attack: we will see the innocuous GET variable which is // registered to the $_REQUEST array due to higher precedence, while the // malicious POST payload makes it through to the component. When you are // talking about security you can leave NOTHING in the hands of Fate, or // it will come back to bite your sorry ass. $hashes = array('get', 'post'); // Removing the jos_/#__ filter as it throws false positives on posts regarding SQL commands //$regex = '#[^\s]*([\s]|/\*(.*)\*/|;|\'|"|%22){1,}(union([\s]{1,}|/\*(.*)\*/){1,}select|select(([\s]{1,}|/\*(.*)\*/|`){1,}([\w]|_|-|\.|\*){1,}([\s]{1,}|/\*(.*)\*/|`){1,}(,){0,})*from([\s]{1,}|/\*(.*)\//){1,}[a-z0-9]{1,}_|(insert|replace)(([\s]{1,}|/\*(.*)\*/){1,})((low_priority|delayed|high_priority|ignore)([\s]{1,}|/\*(.*)\*/){1,}){0,}into|drop([\s]{1,}|/\*(.*)\*/){1,}(database|schema|event|procedure|function|trigger|view|index|server|(temporary([\s]{1,}|/\*(.*)\*/){1,}){0,1}table){1,1}([\s]{1,}|/\*(.*)\*/){1,}|update([\s]{1,}|/\*[^\w]*\/){1,}(low_priority([\s]{1,}|/\*[^\w]*\/){1,}|ignore([\s]{1,}|/\*[^\w]*\/){1,})?`?[\w]*_.*set|delete([\s]{1,}|/\*(.*)\*/){1,}((low_priority|quick|ignore)([\s]{1,}|/\*(.*)\*/){1,}){0,}from|benchmark([\s]{1,}|/\*(.*)\*/){0,}\(([\s]{1,}|/\*(.*)\*/){0,}[0-9]{1,}|\#__|jos_){1}#i'; //$regex = '#[^\s]*([\s]|/\*(.*)\*/|;|\'|"|%22){1,}(union([\s]{1,}|/\*(.*)\*/){1,}select|select(([\s]{1,}|/\*(.*)\*/|`){1,}([\w]|_|-|\.|\*){1,}([\s]{1,}|/\*(.*)\*/|`){1,}(,){0,})*from([\s]{1,}|/\*(.*)\//){1,}[a-z0-9]{1,}_|(insert|replace)(([\s]{1,}|/\*(.*)\*/){1,})((low_priority|delayed|high_priority|ignore)([\s]{1,}|/\*(.*)\*/){1,}){0,}into|drop([\s]{1,}|/\*(.*)\*/){1,}(database|schema|event|procedure|function|trigger|view|index|server|(temporary([\s]{1,}|/\*(.*)\*/){1,}){0,1}table){1,1}([\s]{1,}|/\*(.*)\*/){1,}|update([\s]{1,}|/\*[^\w]*\/){1,}(low_priority([\s]{1,}|/\*[^\w]*\/){1,}|ignore([\s]{1,}|/\*[^\w]*\/){1,})?`?[\w]*_.*set|delete([\s]{1,}|/\*(.*)\*/){1,}((low_priority|quick|ignore)([\s]{1,}|/\*(.*)\*/){1,}){0,}from|benchmark([\s]{1,}|/\*(.*)\*/){0,}\(([\s]{1,}|/\*(.*)\*/){0,}[0-9]{1,}){1}#i'; $regex = '#[^\s]*([\s]|/\*(.*)\*/|;|\'|"|%22){1,}(union([\s]{1,}|/\*(.*)\*/){1,}(all([\s]{1,}|/\*(.*)\*/){1,})?select|select(([\s]{1,}|/\*(.*)\*/|`){1,}([\w]|_|-|\.|\*){1,}([\s]{1,}|/\*(.*)\*/|`){1,}(,){0,})*from([\s]{1,}|/\*(.*)\//){1,}[a-z0-9]{1,}_|(insert|replace)(([\s]{1,}|/\*(.*)\*/){1,})((low_priority|delayed|high_priority|ignore)([\s]{1,}|/\*(.*)\*/){1,}){0,}into|drop([\s]{1,}|/\*(.*)\*/){1,}(database|schema|event|procedure|function|trigger|view|index|server|(temporary([\s]{1,}|/\*(.*)\*/){1,}){0,1}table){1,1}([\s]{1,}|/\*(.*)\*/){1,}|update([\s]{1,}|/\*[^\w]*\/){1,}(low_priority([\s]{1,}|/\*[^\w]*\/){1,}|ignore([\s]{1,}|/\*[^\w]*\/){1,})?`?[\w]*_.*set|delete([\s]{1,}|/\*(.*)\*/){1,}((low_priority|quick|ignore)([\s]{1,}|/\*(.*)\*/){1,}){0,}from|benchmark([\s]{1,}|/\*(.*)\*/){0,}\(([\s]{1,}|/\*(.*)\*/){0,}[0-9]{1,}){1}#i'; foreach ($hashes as $hash) { $allVars = JRequest::get($hash, 2); if (empty($allVars)) continue; if ($this->match_array($regex, $allVars)) { $extraInfo = "Hash : $hash\n"; $extraInfo .= "Variables :\n"; $extraInfo .= print_r($allVars, true); $extraInfo .= "\n"; $this->blockRequest('sqlishield', null, $extraInfo); } } } /** * Runs a RegEx match against a string or recursively against an array. * In the case of an array, the first positive match against any level element * of the array returns true and breaks the RegEx matching loop. If you pass * any other data type except an array or string, it returns false. * * @param string $regex The regular expressions to feed to preg_match * @param mixed $array * @return <type> */ private function match_array($regex, $array, $striptags = false) { $result = false; if (is_array($array)) { foreach ($array as $key => $value) { if (is_array($value)) { $result = $this->match_array($regex, $value, $striptags); } else { $v = $striptags ? strip_tags($value) : $value; $result = preg_match($regex, $v); } if ($result) break; } } elseif (is_string($array)) { $v = $striptags ? strip_tags($array) : $array; $result = preg_match($regex, $v); } return $result; } /** * The simplest anti-spam solution imagineable. Just blocks a request if a prohibited word is found. */ private function antiSpam() { $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select($db->qn('word')) ->from($db->qn('#__admintools_badwords')) ->group($db->qn('word')); $db->setQuery($sql); if (version_compare(JVERSION, '3.0', 'ge')) { $badwords = $db->loadColumn(); } else { $badwords = $db->loadResultArray(); } if (empty($badwords)) return; $hashes = array('get', 'post'); foreach ($hashes as $hash) { $allVars = JRequest::get($hash, 2); if (empty($allVars)) continue; foreach ($badwords as $word) { $regex = '#\b' . $word . '\b#i'; if ($this->match_array($regex, $allVars, true)) { $extraInfo = "Hash : $hash\n"; $extraInfo .= "Variables :\n"; $extraInfo .= print_r($allVars, true); $extraInfo .= "\n"; $this->blockRequest('antispam', null, $extraInfo); } } } } /** * Performs custom redirections defined in the back-end of the component. * It doesn't even require SEF to be turned on, he he! */ private function customRouter() { // Get the base path $basepath = ltrim(JURI::base(true), '/'); $myURL = JURI::getInstance(); $fullurl = ltrim($myURL->toString(array('path', 'query', 'fragment')), '/'); $path = ltrim($myURL->getPath(), '/'); $pathLength = strlen($path); $baseLength = strlen($basepath); if ($baseLength != 0) { if ($pathLength > $baseLength) { $path = ltrim(substr($path, $baseLength), '/'); } elseif ($pathLength = $baseLength) { $path = ''; } } $pathLength = strlen($fullurl); if ($baseLength != 0) { if ($pathLength > $baseLength) { $fullurl = ltrim(substr($fullurl, $baseLength), '/'); } elseif ($pathLength = $baseLength) { $fullurl = ''; } } $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select(array($db->qn('source'), $db->qn('keepurlparams'))) ->from($db->qn('#__admintools_redirects')) ->where( '(' . $db->qn('dest') . ' = ' . $db->q($path) . ')' . ' OR ' . '(' . $db->qn('dest') . ' = ' . $db->q($fullurl) . ')' )->where($db->qn('published') . ' = ' . $db->q('1')) ->order($db->qn('ordering') . ' DESC') ; $db->setQuery($sql, 0, 1); $newURLStruct = $db->loadRow(); if (!empty($newURLStruct)) { list ($newURL, $keepQueryParams) = $newURLStruct; $new = JURI::getInstance($newURL); $host = $new->getHost(); $fragment = $new->getFragment(); $query = $new->getQuery(); if (empty($host)) { $base = JURI::getInstance(JURI::base()); $new->setHost($base->getHost()); $new->setPort($base->getPort()); } if ($keepQueryParams) { if (empty($query)) { $new->setQuery($myURL->getQuery()); } if (empty($fragment)) { $new->setFragment($myURL->getFragment()); } } $path = $new->getPath(); if (!empty($path)) { if (substr($path, 0, 1) != '/') { $new->setPath('/' . $path); } } $new->setScheme($myURL->getScheme()); $targetURL = $new->toString(); $app = JFactory::getApplication(); $app->redirect($targetURL, '', 'message', true); } } private function sessionOptimizer() { $minutes = (int) $this->params->get('sesopt_freq', 0); if ($minutes <= 0) return; $lastJob = $this->getTimestamp('session_optimize'); $nextJob = $lastJob + $minutes * 60; JLoader::import('joomla.utilities.date'); $now = new JDate(); if ($now->toUnix() >= $nextJob) { $this->setTimestamp('session_optimize'); $this->sessionOptimize(); } } /** * Run the session cleaner (garbage collector) on a schedule */ private function sessionCleaner() { $minutes = (int) $this->params->get('ses_freq', 0); if ($minutes <= 0) return; $lastJob = $this->getTimestamp('session_clean'); $nextJob = $lastJob + $minutes * 60; JLoader::import('joomla.utilities.date'); $now = new JDate(); if ($now->toUnix() >= $nextJob) { $this->setTimestamp('session_clean'); $this->purgeSession(); } } private function cacheCleaner() { $minutes = (int) $this->params->get('cache_freq', 0); if ($minutes <= 0) return; $lastJob = $this->getTimestamp('cache_clean'); $nextJob = $lastJob + $minutes * 60; JLoader::import('joomla.utilities.date'); $now = new JDate(); if ($now->toUnix() >= $nextJob) { $this->setTimestamp('cache_clean'); $this->purgeCache(); } } private function cacheExpire() { $minutes = (int) $this->params->get('cacheexp_freq', 0); if ($minutes <= 0) return; $lastJob = $this->getTimestamp('cache_expire'); $nextJob = $lastJob + $minutes * 60; JLoader::import('joomla.utilities.date'); $now = new JDate(); if ($now->toUnix() >= $nextJob) { $this->setTimestamp('cache_expire'); $this->expireCache(); } } private function cleanTemp() { $minutes = (int) $this->params->get('cleantemp_freq', 0); if ($minutes <= 0) return; $lastJob = $this->getTimestamp('clean_temp'); $nextJob = $lastJob + $minutes * 60; JLoader::import('joomla.utilities.date'); $now = new JDate(); if ($now->toUnix() >= $nextJob) { $this->setTimestamp('clean_temp'); $this->tempDirectoryCleanup(); } } /** * Checks if a non logged in user is trying to access the administrator * application * * @param $onlySubmit bool Return true only if the form is submitted */ private function isAdminAccessAttempt($onlySubmit = false) { $app = JFactory::getApplication(); $user = JFactory::getUser(); if (in_array($app->getName(), array('administrator', 'admin'))) { if ($user->guest) { if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option', null); $task = $input->getCmd('task', null); } else { $option = JRequest::getCmd('option', null); $task = JRequest::getCmd('task', null); } if (($option == 'com_login') && ($task == 'login')) { // Check for malicious direct post without a valid token // In this case, we "cheat" by pretending that it is a // login attempt we need to filter. If it's a legitimate // login request (username & password posted) we stop // filtering so as to allow Joomla! to parse the login // request. JLoader::import('joomla.utiltiites.utility'); $token = null; if (class_exists('JUtility')) { if (method_exists('JUtility', 'getToken')) { $token = JUtility::getToken(); } } if (is_null($token)) { $token = JFactory::getSession()->getToken(); } $token = JRequest::getVar($token, false); if (!$onlySubmit) { if (($token === false) && method_exists('JSession', 'checkToken')) { return !JSession::checkToken('request'); } else { return $token === false; } } else { if (($token === false) && method_exists('JSession', 'checkToken')) { return JSession::checkToken('request'); } else { return $token !== false; } } } else { // Back-end login attempt if ($onlySubmit) { return false; } return true; } } else { // Logged in admin user return false; } } else { // The request doesn't belong to the Administrator application return false; } } /** * Checks if the user's IP is contained in a list of IPs or IP expressions * @param array $ipTable The list of IP expressions * @return null|bool True if it's in the list, null if the filtering can't proceed */ private function IPinList($ipTable = array()) { // Get our IP address $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; return AdmintoolsHelperIp::IPinList($ip, $ipTable); } /** * Blocks acess to com_install */ private function blockInstall() { if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option', ''); } else { $option = JRequest::getCmd('option', ''); } if (!in_array($option, array('com_installer', 'com_plugins'))) return; $blockSetting = $this->cparams->getValue('blockinstall', 0); if ($blockSetting == 0) return; $user = JFactory::getUser(); if (!$user->guest) { // Joomla! 1.6 -- Only Super Users have the core.admin global privilege $coreAdmin = $user->authorise('core.admin'); if (!empty($coreAdmin) && ($coreAdmin === true)) { $coreAdmin = true; } else { $coreAdmin = false; } if (($blockSetting == 1) && ($coreAdmin)) return; $jlang = JFactory::getLanguage(); $jlang->load('joomla', JPATH_ROOT, 'en-GB', true); $jlang->load('joomla', JPATH_ROOT, $jlang->getDefault(), true); $jlang->load('joomla', JPATH_ROOT, null, true); $this->loadLanguage('plg_system_admintools'); JError::raiseError(403, JText::_('JGLOBAL_AUTH_ACCESS_DENIED')); } } /** * Disabled creating new admins or updating new ones */ private function noNewAdmins() { if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option', ''); $task = $input->getCmd('task', ''); $gid = $input->getInt('gid', 0); } else { $option = JRequest::getCmd('option', ''); $task = JRequest::getCmd('task', ''); $gid = JRequest::getInt('gid', 0); } if ($option != 'com_users') return; $jform = JRequest::getVar('jform', array(), 'default', 'array'); if (($task == 'save') || ($task == 'apply') || ($task = 'user.apply')) { // Joomla! 1.6 if (empty($jform)) return; // Not editing, just core devs using the same task throughout the component, dammit $groups = $jform['groups']; $user = JFactory::getUser((int) $jform['id']); if (!empty($user->groups)) foreach ($user->groups as $title => $gid) { if (!in_array($gid, $groups)) $groups[] = $gid; } $isAdmin = false; if (!empty($groups)) foreach ($groups as $group) { // First try to see if the group has explicit backend login privileges $backend = JAccess::checkGroup($group, 'core.login.admin'); // If not, is it a Super Admin (ergo inherited privileges)? if (is_null($backend)) $backend = JAccess::checkGroup($group, 'core.admin'); $isAdmin |= $backend; } if ($isAdmin) { $jlang = JFactory::getLanguage(); $jlang->load('joomla', JPATH_ROOT, 'en-GB', true); $jlang->load('joomla', JPATH_ROOT, $jlang->getDefault(), true); $jlang->load('joomla', JPATH_ROOT, null, true); $this->loadLanguage('plg_system_admintools'); JError::raiseError(403, JText::_('JGLOBAL_AUTH_ACCESS_DENIED')); } } } /** * Redirects an administrator request back to the home page */ private function redirectAdminToHome() { // Get the current URI $myURI = JURI::getInstance(); $path = $myURI->getPath(); // Pop the administrator from the URI path $path_parts = explode('/', $path); $path_parts = array_slice($path_parts, 0, count($path_parts) - 2); $path = implode('/', $path_parts); $myURI->setPath($path); // Unset any query parameters $myURI->setQuery(''); // Redirect $app = JFactory::getApplication(); $app->redirect($myURI->toString()); } /** * Blocks the request in progress and, optionally, logs the details of the * blocked request for the admin to review later * * @param string $reason Block reason code * @param string $message The message to be shown to the user * @param string $extraLogInformation Extra information to be written to the text log file * @param string $extraLogTableInformation Extra information to be written to the extradata field of the log table (useful for JSON format) */ private function blockRequest($reason = 'other', $message = '', $extraLogInformation = '', $extraLogTableInformation = '') { if (empty($message)) { $customMessage = $this->cparams->getValue('custom403msg', ''); if (!empty($customMessage)) { $message = $customMessage; } else { $message = 'ADMINTOOLS_BLOCKED_MESSAGE'; } } $r = $this->logBreaches($reason, $extraLogInformation, $extraLogTableInformation); if (!$r) return; $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan($reason); // Merge the default translation with the current translation $jlang = JFactory::getLanguage(); // Front-end translation $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('plg_system_admintools', JPATH_ADMINISTRATOR, null, true); if ((JText::_('ADMINTOOLS_BLOCKED_MESSAGE') == 'ADMINTOOLS_BLOCKED_MESSAGE') && ($message == 'ADMINTOOLS_BLOCKED_MESSAGE')) { $message = "Access Denied"; } else { $message = JText::_($message); } // Show the 403 message if ($this->cparams->getValue('use403view', 0)) { // Using a view if (!JFactory::getSession()->get('block', false, 'com_admintools')) { // This is inside an if-block so that we don't end up in an infinite rediretion loop JFactory::getSession()->set('block', true, 'com_admintools'); JFactory::getSession()->set('message', $message, 'com_admintools'); JFactory::getSession()->close(); JFactory::getApplication()->redirect(JURI::base()); } } else { // Using Joomla!'s error page JError::raiseError('403', $message); } } private function logBreaches($reason, $extraLogInformation = '', $extraLogTableInformation = '') { $reasons_nolog = $this->cparams->getValue('reasons_nolog', 'geoblocking'); $reasons_noemail = $this->cparams->getValue('reasons_noemail', 'geoblocking'); $reasons_nolog = explode(',', $reasons_nolog); $reasons_noemail = explode(',', $reasons_noemail); // === SANITY CHECK - BEGIN === // Get our IP address $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; if ((strpos($ip, '::') === 0) && (strstr($ip, '.') !== false)) { $ip = substr($ip, strrpos($ip, ':') + 1); } // No point continuing if we can't get an address, right? if (empty($ip) || ($ip == '0.0.0.0')) { return false; } // Make sure it's not an IP in the safe list $safeIPs = $this->cparams->getValue('neverblockips', ''); if (!empty($safeIPs)) { $safeIPs = explode(',', $safeIPs); if (!empty($safeIPs)) { if ($this->IPinList($safeIPs)) { return false; } } } // Make sure we don't have a list in the administrator white list if ($this->cparams->getValue('ipwl', 0) == 1) { $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select($db->qn('ip')) ->from($db->qn('#__admintools_adminiplist')); $db->setQuery($sql); if (version_compare(JVERSION, '3.0', 'ge')) { $ipTable = $db->loadColumn(); } else { $ipTable = $db->loadResultArray(); } if (!empty($ipTable)) { if ($this->IPinList($ipTable)) { return false; } } } // === SANITY CHECK - END === if ($this->cparams->getValue('logbreaches', 0) && !in_array($reason, $reasons_nolog)) { // Logging requested. Fetch log information... $uri = JURI::getInstance(); $url = $uri->toString(array('scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment')); JLoader::import('joomla.utilities.date'); $date = new JDate(); $user = JFactory::getUser(); if ($user->guest) { $username = 'Guest'; } else { $username = $user->username . ' (' . $user->name . ' <' . $user->email . '>)'; } if (AdmintoolsHelperIp::isIPv6($ip)) { $country = 'N/A (IPv6)'; $continent = 'N/A (IPv6)'; } else { if (@file_exists(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat')) { if (!$this->hasGeoIPPECL) { require_once JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'geoip.php'; $gi = geoip_open(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat', GEOIP_STANDARD); $country = geoip_country_code_by_addr($gi, $ip); $continent = geoip_region_by_addr($gi, $ip); geoip_close($gi); } else { $country = geoip_country_code_by_name($ip); $continent = geoip_continent_code_by_name($ip); } } else { $country = '(unknown country)'; $continent = '(unknown continent)'; } } // Logging to file $config = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $logpath = $config->get('log_path'); } else { $logpath = $config->getValue('log_path'); } $fname = $logpath . DIRECTORY_SEPARATOR . 'admintools_breaches.log'; // -- Check the file size. If it's over 1Mb, archive and start a new log. if (@file_exists($fname)) { $fsize = filesize($fname); if ($fsize > 1048756) { if (@file_exists($fname . '.1')) { unlink($fname . '.1'); } @copy($fname, $fname . '.1'); @unlink($fname); } } // -- Log the exception $fp = @fopen($fname, 'at'); if ($fp !== false) { fwrite($fp, str_repeat('-', 79) . "\n"); fwrite($fp, "Blocking reason: " . $reason . "\n" . str_repeat('-', 79) . "\n"); fwrite($fp, 'Date/time : ' . gmdate('Y-m-d H:i:s') . " GMT\n"); fwrite($fp, 'URL : ' . $url . "\n"); fwrite($fp, 'User : ' . $username . "\n"); fwrite($fp, 'IP : ' . $ip . "\n"); fwrite($fp, 'Country : ' . $country . "\n"); fwrite($fp, 'Continent : ' . $continent . "\n"); fwrite($fp, 'UA : ' . $_SERVER['HTTP_USER_AGENT'] . "\n"); if (!empty($extraLogInformation)) fwrite($fp, $extraLogInformation . "\n"); fwrite($fp, "\n\n"); fclose($fp); } // ...and write a record to the log table $db = JFactory::getDBO(); $logEntry = (object) array( 'logdate' => $date->toSql(), 'ip' => $ip, 'url' => $url, 'reason' => $reason, 'extradata' => $extraLogTableInformation, ); $db->insertObject('#__admintools_log', $logEntry); } $emailbreaches = $this->cparams->getValue('emailbreaches', ''); if (!empty($emailbreaches) && !in_array($reason, $reasons_noemail)) { // Load the component's administrator translation files $jlang = JFactory::getLanguage(); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true); // Get the site name $config = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $sitename = $config->get('sitename'); } else { $sitename = $config->getValue('config.sitename'); } // Create a link to lookup the IP $ip_link = $this->cparams->getValue('iplookupscheme', 'http') . '://' . $this->cparams->getValue('iplookup', 'ip-lookup.net/index.php?ip={ip}'); $ip_link = str_replace('{ip}', $ip, $ip_link); // Get the reason in human readable format $txtReason = JText::_('ATOOLS_LBL_REASON_' . strtoupper($reason)); // Get extra information if ($extraLogTableInformation) { list($logReason, $techURL) = explode('|', $extraLogTableInformation); $txtReason .= " ($logReason)"; } // Send the email $mailer = JFactory::getMailer(); if (version_compare(JVERSION, '3.0', 'ge')) { $mailfrom = $config->get('mailfrom'); $fromname = $config->get('fromname'); } else { $mailfrom = $config->getValue('config.mailfrom'); $fromname = $config->getValue('config.fromname'); } $mailer->setSender(array($mailfrom, $fromname)); $mailer->addRecipient($this->cparams->getValue('emailbreaches', '')); $mailer->setSubject(JText::sprintf('ATOOLS_LBL_WAF_EMAILBREACHES_SUBJECT', $sitename)); $mailer->setBody(JText::sprintf('ATOOLS_LBL_WAF_EMAILBREACHES_BODY', $sitename, $ip, $ip_link, $txtReason, $sitename)); $mailer->Send(); } return true; } /** * Optimizes the session table. The idea is that as users log in and out, * vast amounts of records are created and deleted, slowly fragmenting the * underlying database file and slowing down user session operations. At * some point, your site might even crash. By doing a periodic optimization * of the sessions table this is prevented. An optimization per hour should * be adequate, even for huge sites. * * Note: this is not necessary if you're not using the database to save * session data. Using disk files, memcache, APC or other alternative caches * has no impact on your database performance. In this case you should not * enable this option, as you have nothing to gain. */ private function sessionOptimize() { $db = JFactory::getDBO(); // First, make sure this is MySQL! $dbClass = get_class($db); if (substr($dbClass, 0, 15) == 'JDatabaseDriver') { $dbClass = substr($dbClass, 15); } else { $dbClass = str_replace('JDatabase', '', $dbClass); } if (!in_array(strtolower($dbClass), array('mysql', 'mysqli'))) { return; } $db->setQuery('CHECK TABLE ' . $db->quoteName('#__session')); $result = $db->loadObjectList(); $isOK = false; if (!empty($result)) foreach ($result as $row) { if (($row->Msg_type == 'status') && ( ($row->Msg_text == 'OK') || ($row->Msg_text == 'Table is already up to date') )) $isOK = true; } // Run a repair only if it is required if (!$isOK) { // The table needs repair $db->setQuery('REPAIR TABLE ' . $db->quoteName('#__session')); $db->execute(); } // Finally, optimize $db->setQuery('OPTIMIZE TABLE ' . $db->quoteName('#__session')); $db->execute(); } /** * Purges expired sessions */ private function purgeSession() { JLoader::import('joomla.session.session'); $options = array(); $conf = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $handler = $conf->get('session_handler', 'none'); } else { $handler = $conf->getValue('config.session_handler', 'none'); } // config time is in minutes if (version_compare(JVERSION, '3.0', 'ge')) { $options['expire'] = ($conf->get('lifetime')) ? $conf->get('lifetime') * 60 : 900; } else { $options['expire'] = ($conf->getValue('config.lifetime')) ? $conf->getValue('config.lifetime') * 60 : 900; } $storage = JSessionStorage::getInstance($handler, $options); $storage->gc($options['expire']); } /** * Completely purges the cache */ private function purgeCache() { // Site client $client = JApplicationHelper::getClientInfo(0); $er = @error_reporting(0); $cache = JFactory::getCache(''); $cache->clean('sillylongnamewhichcantexistunlessyouareacompletelyparanoiddeveloperinwhichcaseyoushouldnotbewritingsoftwareokay', 'notgroup'); @error_reporting($er); } /** * Expires cache items */ private function expireCache() { $er = @error_reporting(0); $cache = JFactory::getCache(''); $cache->gc(); @error_reporting($er); } /** * Cleans up the temporary director */ private function tempDirectoryCleanup() { $file = JPATH_ADMINISTRATOR . '/components/com_admintools/models/cleantmp.php'; if (@file_exists($file)) { include_once($file); $model = new AdmintoolsModelCleantmp(); $model->startScanning(); // This also runs the first batch of deletions $model->run(); // and this runs more deletions until the time is up } } /** * Sets the timestamp for a specific scheduling task * @param $key string The scheduling task key to set the timestamp parameter for */ private function setTimestamp($key) { JLoader::import('joomla.utilities.date'); $date = new JDate(); $pk = 'timestamp_' . $key; $timestamp = $date->toUnix(); $oldTimestamp = $this->getTimestamp($key); // Make sure the array is populated $db = JFactory::getDbo(); // This is necessary because using an UPDATE query results in Joomla! // throwing a JLIB_APPLICATION_ERROR_COMPONENT_NOT_LOADING or blank // page. HUH!!!!!! $query = $db->getQuery(true) ->delete($db->qn('#__admintools_storage')) ->where($db->qn('key') . ' = ' . $db->q($pk)); $db->setQuery($query); $db->execute(); $query = $db->getQuery(true) ->insert($db->qn('#__admintools_storage')) ->columns(array( $db->qn('key'), $db->qn('value'), ))->values( $db->q($pk) . ', ' . $db->q($timestamp) ); $db->setQuery($query); $db->execute(); $this->timestamps[$pk] = $timestamp; } /** * Gets the last recorded timestamp for a specific scheduling task * @param $key string The scheduling task key to retrieve the timestamp parameter * @return int UNIX timestamp */ private function getTimestamp($key) { if (empty($this->timestamps)) { $this->loadTimestamps(); } JLoader::import('joomla.utilities.date'); $pk = 'timestamp_' . $key; if (!array_key_exists($pk, $this->timestamps)) { return 0; } return $this->timestamps[$pk]; } /** * Sends an email upon accessing an administrator page other than the login screen */ private function emailOnAdminLogin() { // Make sure we don't fire when someone is still in the login page if ($this->isAdminAccessAttempt()) return; // Double check $user = JFactory::getUser(); if ($user->guest) return; // Check if the session flag is set (avoid sending thousands of emails!) $session = JFactory::getSession(); $flag = $session->get('waf.loggedin', 0, 'plg_admintools'); if ($flag == 1) return; // Load the component's administrator translation files $jlang = JFactory::getLanguage(); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true); // Get the username $username = $user->username; // Get the site name $config = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $sitename = $config->get('sitename'); } else { $sitename = $config->getValue('config.sitename'); } // Get the IP address $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; if ((strpos($ip, '::') === 0) && (strstr($ip, '.') !== false)) { $ip = substr($ip, strrpos($ip, ':') + 1); } if (@file_exists(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat')) { if (!$this->hasGeoIPPECL) { require_once JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'geoip.php'; $gi = geoip_open(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat', GEOIP_STANDARD); $country = geoip_country_code_by_addr($gi, $ip); $continent = geoip_region_by_addr($gi, $ip); geoip_close($gi); } else { $country = geoip_country_code_by_name($ip); $continent = geoip_continent_code_by_name($ip); } } else { $country = '(unknown country)'; $continent = '(unknown country)'; } // Construct the replacement table $substitutions = array( '[SITENAME]' => $sitename, '[USERNAME]' => $username, '[IP]' => $ip, '[UASTRING]' => $_SERVER['HTTP_USER_AGENT'], '[COUNTRY]' => $country, '[CONTINENT]' => $continent ); $subject = JText::_('ATOOLS_LBL_WAF_EMAILADMINLOGIN_SUBJECT_21'); $body = JText::_('ATOOLS_LBL_WAF_EMAILADMINLOGIN_BODY_21'); foreach ($substitutions as $k => $v) { $subject = str_replace($k, $v, $subject); $body = str_replace($k, $v, $body); } // Send the email $mailer = JFactory::getMailer(); if (version_compare(JVERSION, '3.0', 'ge')) { $mailfrom = $config->get('mailfrom'); $fromname = $config->get('fromname'); } else { $mailfrom = $config->getValue('config.mailfrom'); $fromname = $config->getValue('config.fromname'); } $mailer->setSender(array($mailfrom, $fromname)); $mailer->addRecipient($this->cparams->getValue('emailonadminlogin', '')); $mailer->setSubject($subject); $mailer->setBody($body); $mailer->Send(); // Set the flag to prevent sending more emails $session->set('waf.loggedin', 1, 'plg_admintools'); } /** * Sends an email upon a failed administrator login * * @param $forcedFailure bool Wt to true to force a login failure trigger */ private function emailOnFailedAdminLogin($forcedFailure = false) { // Make sure we don't fire unless someone is still in the login page $user = JFactory::getUser(); if (!$user->guest) return; if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option'); $task = $input->getCmd('task'); } else { $option = JRequest::getCmd('option'); $task = JRequest::getCmd('task'); } if (($option != 'com_login') && !$forcedFailure) return; if (($task == 'login') || $forcedFailure) { // If we are STILL in the login task WITHOUT a valid user, we had a login failure. // Load the component's administrator translation files $jlang = JFactory::getLanguage(); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true); // Fetch the username $username = JRequest::getString('username'); // Get the site name $config = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $sitename = $config->get('sitename'); } else { $sitename = $config->getValue('config.sitename'); } // Get the IP address $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; if ((strpos($ip, '::') === 0) && (strstr($ip, '.') !== false)) { $ip = substr($ip, strrpos($ip, ':') + 1); } // Send the email $mailer = JFactory::getMailer(); if (version_compare(JVERSION, '3.0', 'ge')) { $mailfrom = $config->get('mailfrom'); $fromname = $config->get('fromname'); } else { $mailfrom = $config->getValue('config.mailfrom'); $fromname = $config->getValue('config.fromname'); } $mailer->setSender(array($mailfrom, $fromname)); $mailer->addRecipient($this->cparams->getValue('emailonfailedadminlogin', '')); $mailer->setSubject(JText::sprintf('ATOOLS_LBL_WAF_EMAILADMINFAILEDLOGIN_SUBJECT', $username, $sitename)); $mailer->setBody(JText::sprintf('ATOOLS_LBL_WAF_EMAILADMINFAILEDLOGIN_BODY', $username, $sitename, $ip, $sitename)); $mailer->Send(); } } private function trackFailedLogin() { $user = JRequest::getCmd('username', null); $pass = JRequest::getCmd('password', null); if (empty($pass)) $pass = JRequest::getCmd('passwd', null); $extraInfo = null; if (!empty($user)) { if ($this->cparams->getValue('showpwonloginfailure', 1)) { $extraInfo = 'Username: ' . $user . ' -- Password: ' . $pass; } else { $extraInfo = 'Username: ' . $user; } } $this->logBreaches('loginfailure', $user, $extraInfo); $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan('loginfailure'); } /** * Protects against a malicious User Agent string */ private function MUAShield() { // Some PHP binaries don't set the $_SERVER array under all platforms if (!isset($_SERVER)) return; if (!is_array($_SERVER)) return; // Some user agents don't set a UA string at all if (!array_key_exists('HTTP_USER_AGENT', $_SERVER)) return; $mua = $_SERVER['HTTP_USER_AGENT']; if (strstr($mua, '<?')) { $this->blockRequest('muashield'); } } private function CSRFShield_BASIC() { // Do not activate on GET, HEAD and TRACE requests $method = strtoupper($_SERVER['REQUEST_METHOD']); if (in_array($method, array('GET', 'HEAD', 'TRACE'))) return; // Check the referer, if available $valid = true; $referer = array_key_exists('HTTP_REFERER', $_SERVER) ? $_SERVER['HTTP_REFERER'] : ''; if (!empty($referer)) { $jRefURI = JURI::getInstance($referer); $refererURI = $jRefURI->toString(array('host', 'port')); $jSiteURI = JURI::getInstance(); $siteURI = $jSiteURI->toString(array('host', 'port')); $valid = ($siteURI == $refererURI); } if (!$valid) { $this->blockRequest('csrfshield'); } } /** * Applies basic HTTP referer filtering to POST, PUT, DELETE etc HTTP requests, * usually associated with form submission. */ private function CSRFShield_GetFieldName() { static $fieldName = null; if (empty($fieldName)) { $config = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $sitename = $config->get('sitename'); $secret = $config->get('secret'); } else { $sitename = $config->getValue('config.sitename'); $secret = $config->getValue('config.secret'); } $fieldName = md5($sitename . $secret); } return $fieldName; } /** * Applies advanced reverse CAPTCHA checks to POST, PUT, DELETE etc HTTP * requests, usually associated with form submission. */ private function CSRFShield_ADVANCED() { // Do not activate on GET, HEAD and TRACE requests $method = strtoupper($_SERVER['REQUEST_METHOD']); if (in_array($method, array('GET', 'HEAD', 'TRACE'))) return; // Check for the existence of a hidden field $valid = true; $hashes = array('get', 'post'); $hiddenFieldName = $this->CSRFShield_GetFieldName(); foreach ($hashes as $hash) { $allVars = JRequest::get('default', 2); if (!array_key_exists($hiddenFieldName, $allVars)) continue; if (!empty($allVars[$hiddenFieldName])) { $this->blockRequest('csrfshield'); } } } /** * Processes all forms on the page, adding a reverse CAPTCHA field * for advanced filtering */ private function CSRFShield_PROCESS() { $hiddenFieldName = $this->CSRFShield_GetFieldName(); $buffer = JResponse::getBody(); $buffer = preg_replace('#<[\s]*/[\s]*form[\s]*>#iU', '<input type="text" name="' . $hiddenFieldName . '" value="" style="float: left; position: absolute; z-index: 1000000; left: -10000px; top: -10000px;" /></form>', $buffer); JResponse::setBody($buffer); } /** * Processes all forms on the page, adding a reverse CAPTCHA field * for advanced filtering */ private function twoFactorAuthentication_process() { // Load the component's administrator translation files $jlang = JFactory::getLanguage(); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true); $label = JText::_('COM_ADMINTOOLS_LOGIN_TWOFACTOR_LABEL'); $title = JText::_('COM_ADMINTOOLS_LOGIN_TWOFACTOR_TITLE'); // The are the "plain vanilla", catch-all settings $regex = '#<[\s]*form.*>#iU'; $input = <<<ENDINPUT <div class="admintools-security-code"> <label for="admintools-securitycode" title="$title">$label</label> <input name="securitycode" id="admintools-securitycode" type="password" size="6" autocomplete="off" autofocus="autofocus" title="$title" /> <div class="clear"></div> </div> ENDINPUT; if (version_compare(JVERSION, '3.0', 'lt')) { $template = JFactory::getApplication()->getTemplate(); if ($template == 'hathor') { // Joomla! 2.5, Hathor $regex = '#<[\s]*fieldset[\s]*class[\s]*=[\s]*"loginform">#'; } elseif ($template == 'rt_missioncontrol') { // Joomla! 2.5 Mission Control by RocketTheme $regex = '#<[\s]*input[\s]*name[\s]*=[\s]*"passwd".*>#'; $input = <<<ENDINPUT <input name="securitycode" id="admintools-securitycode" type="password" size="6" autocomplete="off" title="$title" placeholder="$label" class="inputbox" /> ENDINPUT; } } else { // Joomla! 3.0 template conventions (Bootstrap FTW!) $regex = '#<[\s]*fieldset[\s]*class[\s]*=[\s]*"loginform">#'; $input = <<<ENDINPUT <div class="control-group"> <div class="controls"> <div class="input-prepend input-append"> <span class="add-on"> <i class="icon-puzzle" rel="tooltip" data-placement="left" data-original-title="$title"></i> <label for="admintools-securitycode" class="element-invisible">$label</label> </span><input tabindex="1" name="securitycode" id="admintools-securitycode" type="password" class="input-medium" size="6" autocomplete="off" title="$title" placeholder="$label" autofocus="autofocus" /> </div> </div> </div> ENDINPUT; } $buffer = JResponse::getBody(); $buffer = preg_replace($regex, '\\0 ' . $input, $buffer); JResponse::setBody($buffer); } private function twoFactorAuthentication_verify() { // Get the secret key $secret = $this->cparams->getValue('twofactorauth_secret', ''); if (empty($secret)) return; // Include Google Authenticator library include_once dirname(__FILE__) . '/gaphp/googleauthenticator.php'; if (!class_exists('GoogleAuthenticator')) return; if (!class_exists('FixedBitNotation')) return; $code = JRequest::getVar('securitycode', ''); // Check for the panic code $panic = $this->cparams->getValue('twofactorauth_panic', ''); $panic = preg_replace('#[^0-9]#', '', $panic); $code = preg_replace('#[^0-9]#', '', $code); if ($code == $panic) return; $googleAuth = new GoogleAuthenticator(); if (!$googleAuth->checkCode($secret, $code)) { // Uh oh... Unauthorized access! if (!$this->logBreaches('securitycode')) return; $autoban = $this->cparams->getValue('tsrenable', 0); if ($autoban) $this->autoBan('securitycode'); $this->redirectAdminToHome(); } } /** * Simple Remote Files Inclusion block. If any query string parameter contains a reference to an http[s]:// or ftp[s]:// * address it will be scanned. If the remote file looks like a PHP script, we block access. */ private function RFIShield() { $hashes = array('get', 'post'); $regex = '#(http|ftp){1,1}(s){0,1}://.*#i'; foreach ($hashes as $hash) { $allVars = JRequest::get($hash, 2); if (empty($allVars)) continue; if ($this->match_array_and_scan($regex, $allVars)) { $extraInfo = "Hash : $hash\n"; $extraInfo .= "Variables :\n"; $extraInfo .= print_r($allVars, true); $extraInfo .= "\n"; $this->blockRequest('rfishield', null, $extraInfo); } } } private function match_array_and_scan($regex, $array) { $result = false; if (is_array($array)) { foreach ($array as $key => $value) { if (in_array($key, $this->exceptions)) continue; if (is_array($value)) { $result = $this->match_array_and_scan($regex, $value); } else { $result = preg_match($regex, $value); } if ($result) { // Can we fetch the file directly? $fContents = @file_get_contents($value); if (!empty($fContents)) { $result = (strstr($fContents, '<?php') !== false); if ($result) break; } else { $result = false; } } } } elseif (is_string($array)) { $result = preg_match($regex, $array); if ($result) { // Can we fetch the file directly? $fContents = @file_get_contents($value); if (!empty($fContents)) { $result = (strstr($fContents, '<?php') !== false); if ($result) break; } else { $result = false; } } } return $result; } /** * Runs the Project Honeypot HTTP:BL integration */ private function ProjectHoneypotHTTPBL() { // Load parameters $httpbl_key = $this->cparams->getValue('bbhttpblkey', ''); $minthreat = $this->cparams->getValue('httpblthreshold', 25); $maxage = $this->cparams->getValue('httpblmaxage', 30); $suspicious = $this->cparams->getValue('httpblblocksuspicious', 0); // Make sure we have an HTTP:BL key set if (empty($httpbl_key)) return; // Get the IP address $reqip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; if ($reqip == '0.0.0.0') return false; if (strpos($reqip, '::') === 0) { $reqip = substr($reqip, strrpos($reqip, ':') + 1); } // No point continuing if we can't get an address, right? if (empty($reqip)) return false; // IPv6 addresses are not supported by HTTP:BL yet if (strpos($reqip, ":")) return false; $find = implode('.', array_reverse(explode('.', $reqip))); $result = gethostbynamel($httpbl_key . ".${find}.dnsbl.httpbl.org."); if (!empty($result)) { $ip = explode('.', $result[0]); if ($ip[0] != 127) return; // Make sure it's a valid response if ($ip[3] == 0) return; // Do not block search engines $block = ($ip[3] & 2) || ($ip[3] & 4); // Block harvesters and comment spammers if (!$suspicious && ($ip[3] & 1)) $block = false; // Do not block "suspicious" (not confirmed) IPs unless asked so $block = $block && ($ip[1] <= $maxage); $block = $block && ($ip[2] >= $minthreat); if ($block) { $classes = array(); if ($ip[3] & 1) $classes[] = 'Suspicious'; if ($ip[3] & 2) $classes[] = 'Email Harvester'; if ($ip[3] & 4) $classes[] = 'Comment Spammer'; $class = implode(', ', $classes); $extraInfo = <<<ENDINFO HTTP:BL analysis for blocked spammer's IP address $reqip Attacker class : $class Last activity : $ip[1] days ago Threat level : $ip[2] --> see http://is.gd/mAwMTo for more info ENDINFO; $this->blockRequest('httpbl', '', $extraInfo); } } } private function geoBlocking() { if (!isset($_SERVER['REMOTE_ADDR'])) return; $ip = $_SERVER['REMOTE_ADDR']; $continents = $this->cparams->getValue('geoblockcontinents', ''); $continents = empty($continents) ? array() : explode(',', $continents); $countries = $this->cparams->getValue('geoblockcountries', ''); $countries = empty($countries) ? array() : explode(',', $countries); if (@file_exists(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat')) { if (!$this->hasGeoIPPECL) { require_once JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'geoip.php'; $gi = geoip_open(JPATH_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_admintools' . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'geoip' . DIRECTORY_SEPARATOR . 'GeoIP.dat', GEOIP_STANDARD); $country = geoip_country_code_by_addr($gi, $ip); $continent = geoip_region_by_addr($gi, $ip); geoip_close($gi); } else { $country = geoip_country_code_by_name($ip); $continent = geoip_continent_code_by_name($ip); } } else { $country = '(unknown country)'; $continent = '(unknown continent)'; } if (($continent) && !empty($continents)) { if (in_array($continent, $continents)) { $extraInfo = 'Continent : ' . $continent; $this->blockRequest('geoblocking', null, $extraInfo); } } if (($country) && !empty($countries)) { if (in_array($country, $countries)) { $extraInfo = 'Country : ' . $country; $this->blockRequest('geoblocking', null, $extraInfo); } } } /** * Blocks visitors coming from an automatically banned IP. These suckers are repeat * offenders. No courtesy from our part. */ private function AutoIPFiltering() { // We need to be able to get our own IP, right? if (!function_exists('inet_pton')) { return; } // Get our IP address $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; if (empty($ip) || ($ip == '0.0.0.0')) { return; } $myIP = @inet_pton($ip); if ($myIP === false) { return; } $myIP = inet_ntop($myIP); // Let's get a list of blocked IP ranges $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select('*') ->from($db->qn('#__admintools_ipautoban')) ->where($db->qn('ip') . ' = ' . $db->q($myIP)); $db->setQuery($sql); $record = $db->loadObject(); if (empty($record)) { return; } // Is this record expired? JLoader::import('joomla.utilities.date'); $jNow = new JDate(); $jUntil = new JDate($record->until); $now = $jNow->toUnix(); $until = $jUntil->toUnix(); if ($now > $until) { // Ban expired. Clear the entry and allow the request to proceed. $sql = $db->getQuery(true) ->delete($db->qn('#__admintools_ipautoban')) ->where($db->qn('ip') . ' = ' . $db->q($myIPv4)); $db->setQuery($sql); $db->execute(); return; } // Clean up old entries $sql = $db->getQuery(true) ->delete($db->qn('#__admintools_ipautoban')) ->where($db->qn('until') . ' < ' . $db->q($jNow->toSql())); $db->setQuery($sql); $db->execute(); @ob_end_clean(); header("HTTP/1.0 403 Forbidden"); $spammerMessage = $this->cparams->getValue('spammermessage', ''); echo $spammerMessage; JFactory::getApplication()->close(); } private function autoBan($reason = 'other') { // We need to be able to get our own IP, right? if (!function_exists('inet_pton')) { return; } // Get the IP $ip = array_key_exists('REMOTE_ADDR', $_SERVER) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '0.0.0.0'; // No point continuing if we can't get an address, right? if (empty($ip) || ($ip == '0.0.0.0')) { return; } // Check for repeat offenses $db = JFactory::getDBO(); $strikes = $this->cparams->getValue('tsrstrikes', 3); $numfreq = $this->cparams->getValue('tsrnumfreq', 1); $frequency = $this->cparams->getValue('tsrfrequency', 'hour'); $mindatestamp = 0; switch ($frequency) { case 'second': break; case 'minute': $numfreq *= 60; break; case 'hour': $numfreq *= 3600; break; case 'day': $numfreq *= 86400; break; case 'ever': $mindatestamp = 946706400; // January 1st, 2000 break; } JLoader::import('joomla.utilities.date'); $jNow = new JDate(); if ($mindatestamp == 0) { $mindatestamp = $jNow->toUnix() - $numfreq; } $jMinDate = new JDate($mindatestamp); $minDate = $jMinDate->toSql(); $sql = $db->getQuery(true) ->select('COUNT(*)') ->from($db->qn('#__admintools_log')) ->where($db->qn('logdate') . ' >= ' . $db->q($minDate)) ->where($db->qn('ip') . ' = ' . $db->q($ip)); $db->setQuery($sql); $numOffenses = $db->loadResult(); if ($numOffenses < $strikes) { return; } // Block the IP $myIP = @inet_pton($ip); if ($myIP === false) { return; } $myIP = inet_ntop($myIP); $until = $jNow->toUnix(); $numfreq = $this->cparams->getValue('tsrbannum', 1); $frequency = $this->cparams->getValue('tsrbanfrequency', 'hour'); switch ($frequency) { case 'second': $until += $numfreq; break; case 'minute': $numfreq *= 60; $until += $numfreq; break; case 'hour': $numfreq *= 3600; $until += $numfreq; break; case 'day': $numfreq *= 86400; $until += $numfreq; break; case 'ever': $until = 2145938400; // January 1st, 2038 (mind you, UNIX epoch runs out on January 19, 2038!) break; } JLoader::import('joomla.utilities.date'); $jMinDate = new JDate($until); $minDate = $jMinDate->toSql(); $record = (object) array( 'ip' => $myIP, 'reason' => $reason, 'until' => $minDate ); $db->insertObject('#__admintools_ipautoban', $record); // Send an optional email if ($this->cparams->getValue('emailafteripautoban', '')) { // Get the site name $config = JFactory::getConfig(); if (version_compare(JVERSION, '3.0', 'ge')) { $sitename = $config->get('sitename'); } else { $sitename = $config->getValue('config.sitename'); } $substitutions = array( '[SITENAME]' => $sitename, '[IP]' => $myIP, '[UNTIL]' => $minDate ); // Load the component's administrator translation files $jlang = JFactory::getLanguage(); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true); $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true); $subject = JText::_('ATOOLS_LBL_WAF_AUTOIPBLOCKEMAIL_SUBJECT'); $body = JText::_('ATOOLS_LBL_WAF_AUTOIPBLOCKEMAIL_BODY'); foreach ($substitutions as $k => $v) { $subject = str_replace($k, $v, $subject); $body = str_replace($k, $v, $body); } // Send the email $mailer = JFactory::getMailer(); if (version_compare(JVERSION, '3.0', 'ge')) { $mailfrom = $config->get('mailfrom'); $fromname = $config->get('fromname'); } else { $mailfrom = $config->getValue('config.mailfrom'); $fromname = $config->getValue('config.fromname'); } $mailer->setSender(array($mailfrom, $fromname)); $mailer->addRecipient($this->cparams->getValue('emailafteripautoban', '')); $mailer->setSubject($subject); $mailer->setBody($body); $mailer->Send(); } } /** * Simple Direct Files Inclusion block. */ private function DFIShield() { if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option', ''); $view = $input->getCmd('view', ''); $layout = $input->getCmd('layout', ''); } else { $option = JRequest::getCmd('option', ''); $view = JRequest::getCmd('view', ''); $layout = JRequest::getCmd('layout', ''); } // Special case: JCE if (($option == 'com_jce') && ($view == 'editor') && ($layout == 'plugin')) return; $hashes = array('get', 'post'); foreach ($hashes as $hash) { $allVars = JRequest::get($hash, 2); if (empty($allVars)) continue; if ($this->match_array_dfi($allVars)) { $extraInfo = "Hash : $hash\n"; $extraInfo .= "Variables :\n"; $extraInfo .= print_r($allVars, true); $extraInfo .= "\n"; $this->blockRequest('dfishield', null, $extraInfo); } } } private function match_array_dfi($array) { $result = false; if (is_array($array)) { foreach ($array as $key => $value) { if (in_array($key, $this->exceptions)) continue; // If there's a null byte in the key, break if (strstr($key, "\u0000")) { $result = true; break; } // If there's no value, treat the key as a value if (empty($value)) $value = $key; // Scan the value if (is_array($value)) { $result = $this->match_array_dfi($value); } else { // If there's a null byte, break if (strstr($value, "\u0000")) { $result = true; break; } // If the value starts with a /, ../ or [a-z]{1,2}:, block if (preg_match('#^(/|\.\.|[a-z]{1,2}:\\\)#i', $value)) { // Fix 2.0.1: Check that the file exists $result = @file_exists($value); if (!$result) { $sillyParts = explode('../', $value); $realParts = array(); foreach ($sillyParts as $p) if (!empty($p)) $realParts[] = $p; $path = implode('/', $realParts); $result = @file_exists($path); } break; } if ($result) break; } } } return $result; } /** * Scans all uploaded files for PHP tags. This prevents uploading PHP files or crafted * images with raw PHP code in them which may lead to arbitrary code execution under * several common circumstances. It will also block files with null bytes in their * filenames or with double extensions which include PHP in them (e.g. .php.jpg). */ private function UploadShield() { // Do we have uploaded files? $filesHash = JRequest::get('FILES', 2); if (empty($filesHash)) return; $extraInfo = ''; foreach ($filesHash as $key => $descriptor) { if (is_array($descriptor) && !array_key_exists('tmp_name', $descriptor)) { $descriptors = $descriptor; } else { $descriptors[] = $descriptor; } unset($descriptor); foreach ($descriptors as $descriptor) { $files = array(); if (is_array($descriptor['tmp_name'])) { foreach ($descriptor['tmp_name'] as $key => $value) { $files[] = array( 'name' => $descriptor['name'][$key], 'type' => $descriptor['type'][$key], 'tmp_name' => $descriptor['tmp_name'][$key], 'error' => $descriptor['error'][$key], 'size' => $descriptor['size'][$key], ); } } else { $files[] = $descriptor; } foreach ($files as $fileDescriptor) { $tempNames = $fileDescriptor['tmp_name']; $intendedNames = $fileDescriptor['name']; if (!is_array($tempNames)) { $tempNames = array($tempNames); } if (!is_array($intendedNames)) { $intendedNames = array($intendedNames); } $len = count($tempNames); for ($i = 0; $i < $len; $i++) { $tempName = array_shift($tempNames); $intendedName = array_shift($intendedNames); $extraInfo = "File descriptor :\n"; $extraInfo .= print_r($fileDescriptor, true); $extraInfo .= "\n"; // 1. Null byte check if (strstr($intendedName, "\u0000")) { $this->blockRequest('uploadshield', null, $extraInfo); return; } // 2. PHP-in-extension check $explodedName = explode('.', $intendedName); array_reverse($explodedName); // 2a. File extension is .php if ((count($explodedName) > 1) && (strtolower($explodedName[0]) == 'php')) { $this->blockRequest('uploadshield', null, $extraInfo); return; } // 2a. File extension is php.xxx if ((count($explodedName) > 2) && (strtolower($explodedName[1]) == 'php')) { $this->blockRequest('uploadshield', null, $extraInfo); return; } // 2b. File extensions is php.xxx.yyy if ((count($explodedName) > 3) && (strtolower($explodedName[2]) == 'php')) { $this->blockRequest('uploadshield', null, $extraInfo); return; } // 3. Contents scanner $fp = @fopen($tempName, 'r'); if ($fp !== false) { $data = ''; $extension = strtolower($explodedName[0]); while (!feof($fp)) { $buffer = @fread($fp, 131072); $data .= $buffer; if (strstr($buffer, '<?php')) { $this->blockRequest('uploadshield', null, $extraInfo); return; } if (in_array($extension, array('inc', 'phps', 'class', 'php3', 'php4', 'txt', 'dat', 'tpl', 'tmpl'))) { // These are suspicious text files which may have the short tag (<?) in them if (strstr($buffer, '<?')) { $this->blockRequest('uploadshield', null, $extraInfo); return; } } $data = substr($data, -4); } fclose($fp); } } // end for } // end foreach } } } /** * Tries to figure out if the given query string looks like an XSS attack. It's not watertight, * but it's better than nothing. * * Based largely on CodeIgniter's XSS cleanup code by EllisLab * * @param string $str The string to filter */ private function looksLikeXSS($str) { // 1. Non-displayable character filtering static $non_displayables = null; if (is_null($non_displayables)) { // All control characters except newline, carriage return, and horizontal tab (dec 09) $non_displayables = array( '/%0[0-8bcef]/', // url encoded 00-08, 11, 12, 14, 15 '/%1[0-9a-f]/', // url encoded 16-31 '/[\x00-\x08]/', // 00-08 '/\x0b/', '/\x0c/', // 11, 12 '/[\x0e-\x1f]/' // 14-31 ); } foreach ($non_displayables as $pattern) { $result = preg_match($pattern, $str); if ($result) return true; } // 2. Partial standard character entities $test = preg_replace('#(&\#?[0-9a-z]{2,})([\x00-\x20])*;?#i', "\\1;\\2", $str); if ($test != $str) return true; // 3. Partial UTF16 two byte encoding $test = preg_replace('#(&\#x?)([0-9A-F]+);?#i', "\\1\\2;", $str); if ($test != $str) return true; // 4. Conditioning // In this step we try to unwrap commonly encoded payloads for the next steps to work // 4a. URL decoding, in case an attacker tries to use URL-encoded payloads // Note: rawurldecode() is used to avoid decoding plus signs $str = rawurldecode($str); // 4b. Convert character entities to ASCII, as they are used a lot in XSS attacks $str = preg_replace_callback("/[a-z]+=([\'\"]).*?\\1/si", array($this, 'attribute_callback'), $str); $str = preg_replace_callback("/<\w+.*?(?=>|<|$)/si", array($this, 'html_entity_decode_callback'), $str); // 5. Non-displayable character filtering (second pass, now that we decoded some more entities!) foreach ($non_displayables as $pattern) { $result = preg_match($pattern, $str); if ($result) return true; } // 6. Convert tab to spaces. Attackers may use ja vascript to pass malicious code to us. if (strpos($str, "\t") !== FALSE) { $str = str_replace("\t", ' ', $str); } // Store the converted string for later comparison $converted_string = $str; // 7. Filter out unsafe strings from list static $never_allowed_str = null; if (is_null($never_allowed_str)) { $never_allowed_str = array( 'document.cookie', 'document.write', '.parentNode', '.innerHTML', 'window.location', '-moz-binding', '<!--', '-->', '<![CDATA[' ); } foreach ($never_allowed_str as $never) { if (strstr($str, $never) !== false) return true; } // 8. Filter out unsafe strings from list of regular expressions static $never_allowed_regex = null; if (empty($never_allowed_regex)) { $never_allowed_regex = array( "javascript\s*:", "expression\s*(\(|&\#40;)", "vbscript\s*:", "Redirect\s+302", ); } foreach ($never_allowed_regex as $pattern) { if (preg_match('#' . $pattern . '#i', $str)) return true; } // 9. PHP filtering // Let's make sure that PHP tags (<? or <?php) are not present, while ensuring that // XML tags (<?xml) are not touched if ($this->cparams->getValue('xssshield_allowphp', 0) != 1) { $safe = str_replace('<?xml', '--xml', $str); if (strstr($safe, '<?')) return true; } // 10. Compact exploded words like j a v a s c r i p t => javascript static $words = null; if (is_null($words)) { $words = array('javascript', 'expression', 'vbscript', 'script', 'applet', 'alert', 'document', 'write', 'cookie', 'window'); } foreach ($words as $word) { $temp = ''; for ($i = 0, $wordlen = strlen($word); $i < $wordlen; $i++) { $temp .= substr($word, $i, 1) . "\s*"; } // We only want to do this when it is followed by a non-word character $str = preg_replace_callback('#(' . substr($temp, 0, -3) . ')(\W)#is', array($this, 'compact_exploded_words_callback'), $str); } // 11. Check for disallowed Javascript in links or img tags $original = $str; if (preg_match("/<a/i", $str)) { $str = preg_replace_callback("#<a\s+([^>]*?)(>|$)#si", array($this, 'js_link_removal'), $str); } if ($str != $original) return true; if (preg_match("/<img/i", $str)) { $str = preg_replace_callback("#<img\s+([^>]*?)(\s?/?>|$)#si", array($this, 'js_img_removal'), $str); } if ($str != $original) return true; if (preg_match("/script/i", $str) OR preg_match("/xss/i", $str)) { $str = preg_replace("#<(/*)(script|xss)(.*?)\>#si", '[removed]', $str); } if ($str != $original) return true; // 11. Detect Javascript event handlers $event_handlers = array('[^a-z_\-]on\w*', 'xmlns'); $str = preg_replace("#<([^><]+?)(" . implode('|', $event_handlers) . ")(\s*=\s*[^><]*)([><]*)#i", "<\\1\\4", $str); if ($str != $original) return true; // 12. Detect naughty PHP and Javascript code commonly used in exploits $result = preg_match('#(alert|cmd|passthru|eval|exec|expression|system|fopen|fsockopen|file|file_get_contents|readfile|unlink)(\s*)\((.*?)\)#si', $str); if ($result) return true; // -- At this point, the string has passed all XSS filters. We hope it contains nothing malicious // -- so we will report it as non-XSS. return false; } private function attribute_callback($match) { return str_replace(array('>', '<', '\\'), array('>', '<', '\\\\'), $match[0]); } private function html_entity_decode_callback($match) { $str = $match[0]; if (stristr($str, '&') === FALSE) return $str; $str = html_entity_decode($str, ENT_COMPAT, 'UTF-8'); $str = preg_replace('~&#x(0*[0-9a-f]{2,5})~ei', 'chr(hexdec("\\1"))', $str); return preg_replace('~&#([0-9]{2,4})~e', 'chr(\\1)', $str); } private function compact_exploded_words_callback($matches) { return preg_replace('/\s+/s', '', $matches[1]) . $matches[2]; } private function js_link_removal($match) { $attributes = $this->filter_attributes(str_replace(array('<', '>'), '', $match[1])); return str_replace($match[1], preg_replace("#href=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]); } function js_img_removal($match) { $attributes = $this->filter_attributes(str_replace(array('<', '>'), '', $match[1])); return str_replace($match[1], preg_replace("#src=.*?(alert\(|alert&\#40;|javascript\:|charset\=|window\.|document\.|\.cookie|<script|<xss|base64\s*,)#si", "", $attributes), $match[0]); } function filter_attributes($str) { $out = ''; if (preg_match_all('#\s*[a-z\-]+\s*=\s*(\042|\047)([^\\1]*?)\\1#is', $str, $matches)) { foreach ($matches[0] as $match) { $out .= preg_replace("#/\*.*?\*/#s", '', $match); } } return $out; } private function match_array_xss($array) { // Safe keys, i.e. keys which may contain stuff which looks like an XSS attack // TODO Move them to WAF Configuration static $safe_keys = array('password', 'passwd', 'token', '_token', 'password1', 'password2', 'text'); $result = false; if (is_array($array)) { foreach ($array as $key => $value) { if (in_array($key, $safe_keys)) continue; if (!in_array($key, $this->exceptions)) continue; // If there's no value, treat the key as a value if (empty($value)) $value = $key; // Make sure the key is not an XSS attack // if($this->looksLikeXSS($key)) return true; // Scan the value if (is_array($value)) { $result = $this->match_array_xss($value); } else { $result = $this->looksLikeXSS($value); if ($result) break; } } } return $result; } /** * Simple XSS attack block. */ private function XSSShield() { $hashes = array('get', 'post'); foreach ($hashes as $hash) { $allVars = JRequest::get($hash, 2); if (empty($allVars)) continue; if ($this->match_array_xss($allVars)) { $extraInfo = "Hash : $hash\n"; $extraInfo .= "Variables :\n"; $extraInfo .= print_r($allVars, true); $extraInfo .= "\n"; $this->blockRequest('xssshield', null, $extraInfo); } } } /** * Purges old log entries */ private function purgeLog() { $minutes = (int) $this->params->get('purgelog_freq', 0); if ($minutes <= 0) return; $lastJob = $this->getTimestamp('purge_log'); $nextJob = $lastJob + $minutes * 60; JLoader::import('joomla.utilities.date'); $now = new JDate(); if ($now->toUnix() >= $nextJob) { $this->setTimestamp('purge_log'); $maxage = (int) $this->params->get('purgelog_age', 0); $maxage = 24 * 3600 * $maxage; if ($maxage > 0) { $now = time(); $oldest = $now - $maxage; $jOldest = new JDate($oldest); $mOldest = $jOldest->toSql(); $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->delete($db->qn('#__admintools_log')) ->where($db->qn('logdate') . ' < ' . $db->q($mOldest)); $db->setQuery($sql); $db->execute(); } } } function &_getUser($user, $options = array()) { JLoader::import('joomla.user.helper'); $instance = new JUser(); if ($id = intval(JUserHelper::getUserId($user['username']))) { $instance->load($id); return $instance; } JLoader::import('joomla.application.component.helper'); $config = JComponentHelper::getParams('com_users'); $defaultUserGroup = $config->get('new_usertype', 2); $acl = JFactory::getACL(); $instance->set('id', 0); $instance->set('name', $user['fullname']); $instance->set('username', $user['username']); $instance->set('password_clear', $user['password_clear']); $instance->set('email', $user['email']); // Result should contain an email (check) $instance->set('usertype', 'deprecated'); $instance->set('groups', array($defaultUserGroup)); return $instance; } private function loadExceptions() { // REMOVED - This doesn't work if this plugin is published BEFORE the // SEF router plugin (default) /* $app = JFactory::getApplication(); if(!in_array($app->getName(),array('administrator','admin'))) { // We have to run the SQLiShield once, before parsing the URL through the router if($this->cparams->getValue('sqlishield',0) == 1) $this->SQLiShield(); } // Break down the route $uri = clone JURI::getInstance(); $app = JFactory::getApplication(); $router = $app->getRouter(); $result = $router->parse($uri); JRequest::set($result, 'get', false); */ // Now, proceed if (version_compare(JVERSION, '3.0', 'ge')) { $input = new JInput(); $option = $input->getCmd('option', ''); $view = $input->getCmd('view', ''); } else { $option = JRequest::getCmd('option', ''); $view = JRequest::getCmd('view', ''); } /* if(empty($option) && array_key_exists('option', $result)) $option = $result['option']; if(empty($view) && array_key_exists('view', $result)) $view = $result['view']; */ $db = JFactory::getDBO(); $sql = $db->getQuery(true) ->select($db->qn('query')) ->from($db->qn('#__admintools_wafexceptions')); if (empty($option)) { $sql->where( '(' . $db->qn('option') . ' IS NULL OR ' . $db->qn('option') . ' = ' . $db->q('') . ')' ); } else { $sql->where( '(' . $db->qn('option') . ' IS NULL OR ' . $db->qn('option') . ' = ' . $db->q('') . ' OR ' . $db->qn('option') . ' = ' . $db->q($option) . ')' ); } if (empty($view)) { $sql->where( '(' . $db->qn('view') . ' IS NULL OR ' . $db->qn('view') . ' = ' . $db->q('') . ')' ); } else { $sql->where( '(' . $db->qn('view') . ' IS NULL OR ' . $db->qn('view') . ' = ' . $db->q('') . ' OR ' . $db->qn('view') . ' = ' . $db->q($view) . ')' ); } $sql->group($db->qn('query')) ->order($db->qn('query') . ' ASC'); $db->setQuery($sql); if (version_compare(JVERSION, '3.0', 'ge')) { $this->exceptions = $db->loadColumn(); } else { $this->exceptions = $db->loadResultArray(); } } private function removeInactiveUsers() { // If the days are not at least 1, bail out $filtertype = (int) $this->params->get('deleteinactive', 1); $days = (int) $this->params->get('deleteinactive_days', 0); if ($days <= 0) return; // Get up to 5 ids of users to remove $db = JFactory::getDbo(); $sql = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__users')) ->where($db->qn('lastvisitDate') . ' = ' . $db->q($db->getNullDate())) ->where($db->qn('registerDate') . ' <= ' . "DATE_SUB(NOW(), INTERVAL $days DAY)") ; switch ($filtertype) { case 1: // Only users not yet activated $sql->where($db->qn('activation') . ' != ' . $db->quote('')); break; case 2: // Only users already activated $sql->where($db->qn('activation') . ' = ' . $db->quote('')); break; case 3: // All users who haven't logged in break; } $db->setQuery($sql, 0, 5); if (version_compare(JVERSION, '3.0', 'ge')) { $ids = $db->loadColumn(); } else { $ids = $db->loadResultArray(); } // Remove those inactive users if (!empty($ids)) { foreach ($ids as $id) { $userToKill = JFactory::getUser($id); $userToKill->delete(); } } } private function loadTimestamps() { $db = JFactory::getDbo(); $query = $db->getQuery(true) ->select('*') ->from($db->quoteName('#__admintools_storage')) ->where($db->quoteName('key') . ' LIKE ' . $db->quote('timestamp_%')); $db->setQuery($query); $temp = $db->loadAssocList(); $this->timestamps = array(); if (!empty($temp)) foreach ($temp as $item) { $this->timestamps[$item['key']] = $item['value']; } } private function removeOldLogEntries() { // Delete up to 100 old entries $maxEntries = $this->params->get('maxlogentries', 0); $db = JFactory::getDbo(); $query = $db->getQuery(true) ->select($db->qn('id')) ->from($db->qn('#__admintools_log')) ->order($db->qn('id') . ' DESC'); $db->setQuery($query, $maxEntries, 100); $ids = $db->loadColumn(0); if (!count($ids)) return; $temp = array(); foreach ($ids as $id) { $temp = $db->q($id); } $ids = implode(',', $ids); $query = $db->getQuery(true) ->delete($db->qn('#__admintools_log')) ->where($db->qn('id') . ' IN(' . $ids . ')'); $db->setQuery($query); try { $db->execute(); } catch (Exception $exc) { // Do nothing on DB exception } } }