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\System\AdminTools\Feature;
defined('_JEXEC') || die;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Event\ErrorEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\DatabaseInterface;
use Joomla\Event\Event;
use Throwable;
class Shield404 extends Base
{
private static $blockedUrls;
/**
* Is this feature enabled?
*
* @return bool
*/
public function isEnabled()
{
// Assign those values to our static variables, so we can reference to them in the static context
static::$blockedUrls = $this->wafParams->getValue('404shield', [
"wp-admin.php", "wp-login.php", "wp-content/*", "wp-admin/*",
]);
// Double check we truly have an array
static::$blockedUrls = is_array(static::$blockedUrls) ? static::$blockedUrls : explode("\n", static::$blockedUrls);
// Remove descriptions
static::$blockedUrls = array_map(function ($x) {
return is_array($x) ? $x[0] : $x;
}, static::$blockedUrls);
return ($this->wafParams->getValue('404shield_enable', 1));
}
public function onError(Event $event)
{
// Make sure we are handling the correct event here
if (!($event instanceof ErrorEvent))
{
return;
}
// Get the event arguments
$error = $event->getError();
$app = $event->getApplication();
if (!($error instanceof Throwable))
{
return;
}
if ($app->isClient('administrator') || ((int) $error->getCode() !== 404))
{
return;
}
$blockedURLs = array_map('trim', static::$blockedUrls);
$root = Uri::root();
$currentURL = Uri::getInstance();
$currentPath = $currentURL->toString(['scheme', 'host', 'port', 'path']);
// Remove the root from the current path so we can work with relative URLs
$currentPath = str_replace($root, '', $currentPath);
$currentPath = $this->removeLanguageTag($currentPath);
$currentPath = trim($currentPath, '/');
$block = false;
foreach ($blockedURLs as $blockPattern)
{
$shouldNegate = false;
// If the pattern starts with a !, we're going to negate the assumption
if (substr($blockPattern, 0, 1) == '!')
{
$blockPattern = substr($blockPattern, 1);
$shouldNegate = true;
}
$blockPattern = trim($blockPattern, '/');
$match = fnmatch($blockPattern, $currentPath);
// Should I invert the result?
if ($shouldNegate)
{
$match = !$match;
}
$block = $match;
// No need to continue if we have to block the request
if ($block)
{
break;
}
}
if ($block)
{
$this->exceptionsHandler->logRequest('404shield');
}
}
/**
* Removes the language tag from the URLs generated by multilanguage sites.
*
* @param $pathURL
*
* @return string
*/
private function removeLanguageTag($pathURL)
{
/** @var SiteApplication $app */
$app = $this->app;
$hasLanguageFilter = false;
if (method_exists($app, 'getLanguageFilter'))
{
$hasLanguageFilter = $app->getLanguageFilter();
}
if (!$hasLanguageFilter)
{
return $pathURL;
}
/** @var DatabaseDriver $db */
$db = $this->db;
// Let's get the list of SEF code used in the URLs of the published languages
$query = (method_exists($db, 'createQuery') ? $db->createQuery() : $db->getQuery(true))
->select($db->qn('sef'))
->from($db->qn('#__languages'))
->where($db->qn('published') . ' = ' . $db->q('1'));
$languages = $db->setQuery($query)->loadColumn();
foreach ($languages as $lang)
{
$lang .= '/';
// Replace only the starting string
if (strpos($pathURL, $lang) !== 0)
{
continue;
}
$pathURL = substr($pathURL, strlen($lang));
// There can be only one language tag in the URL, so if we get here it means that there's nothing left to do
break;
}
return $pathURL;
}
}