shell bypass 403
<?php /** * @package admintools * @copyright Copyright (c)2010-2023 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Uri\Uri; use Joomla\Registry\Registry; defined('_JEXEC') || die; /** * Admin Tools installer helper * * Adds the Download ID query string parameter to the download URL if for any reason your Joomla Update Sites had the * extra_query column wiped (e.g. by rebuilding the update sites list). This plugin is not necessary for most of our * users since our software uses the original method of changing the extra_query column of the #__update_sites entry * for the extension. * * @since 5.3.4 */ class plgInstallerAdmintools extends CMSPlugin { /** * The name of the package in the download URL we should match in this plugin * * @var string * @since 5.3.4 */ private $packageMatch = 'pkg_admintools'; /** * List of URL prefixes we are allowed to work with * * @var array * @since 5.3.4 */ private $allowedURLPrefixes = [ 'https://www.akeeba.com', ]; /** * List of Akeeba components which may have a Download ID setting in their Options page. The first extension listed * here takes precedence in case multiple, different download IDs are recovered from these extensions' settings. * * @var array * @since 5.3.4 */ private $akeebaExtensions = [ 'com_admintools', 'com_akeeba', 'com_ats', ]; /** * List of the Download ID setting key in Akeeba components. In case there are multiple keys found the first one * listed here wins. * * @var array * @since 5.3.4 */ private $possibleDownloadIDKeys = [ 'downloadid', 'update_dlid', 'dlid', ]; /** * Handles Joomla's event fired before downloading an update package. * * @param string $url The URL of the package Joomla is trying to install * @param array $headers The HTTP headers used for downloading the package * * @return void * * @since 5.3.4 */ public function onInstallerBeforePackageDownload(&$url, &$headers) { // This plugin only applies to Joomla! 3 if (version_compare(JVERSION, '3.999.999', 'gt')) { return; } // Make sure the URL belongs to one of our domain names if (!$this->hasAllowedPrefix($url)) { return; } // Make sure the URL seems to be for a package are meant to handle $uri = Uri::getInstance($url); $path = $uri->getPath(); $baseName = basename($path); $pattern = $this->packageMatch . '-*-pro*.zip'; if (!fnmatch($pattern, $baseName)) { return; } // Make sure this URL does not already have a download ID if ($this->hasDownloadID($uri)) { return; } // Get the Download ID and make sure it's not empty try { $dlid = $this->getDownloadID(); } catch (Exception $e) { $dlid = ''; } if (empty($dlid)) { return; } // Apply the download ID to the download URL $uri->setVar('dlid', $dlid); $url = $uri->toString(); } /** * Checks if the download URL is one we're supposed to handle. We have a whitelist of allowed URL prefixes set up * at the top of this plugin. * * @param string $url The download URL to check * * @return bool * @since 5.3.4 */ private function hasAllowedPrefix($url) { $hasAllowedPrefix = false; foreach ($this->allowedURLPrefixes as $prefix) { $hasAllowedPrefix = $hasAllowedPrefix || (strpos($url, $prefix) === 0); } return $hasAllowedPrefix; } /** * Does the download URL already have a non-empty Download ID query parameter? * * @param Uri $uri The download URL to check * * @return bool * @since 5.3.4 */ private function hasDownloadID($uri) { $dlid = $uri->getVar('dlid', null); return !empty($dlid); } /** * Get the applicable Download ID. We prioritize the Download ID of the component listed first in the * $this->akeebaExtensions array. If that component does not have a non-empty Download ID or is not currently * installed we will return the first non-empty Download ID set up in any other extension. This protects the user * against the Download ID being unset in one of our Pro extensions they are trying to update but set in another * one. In this case we are essentially silently correcting their mistake and allow them to update their extension * anyway. * * The reason we have prioritization by extension is that it's possible that the same site has Download IDs from * multiple Akeeba clients. For example, the site integrator uses their own Download ID for Akeeba Backup * Professional, the site security auditor uses their Download ID for Admin Tools Professional and the site owner * uses their own Download ID for Akeeba Ticket System Professional. In this case each package needs a different * Download ID to be downloaded since each one of these Download IDs is valid for a specific extension only. * * @return string * @since 5.3.4 */ private function getDownloadID() { $downloadIDs = []; foreach ($this->akeebaExtensions as $extension) { // Make sure the component is installed and enabled if (!ComponentHelper::isInstalled($extension) || !ComponentHelper::isEnabled($extension)) { return null; } // Get the component's parameters $params = ComponentHelper::getParams($extension); // Make sure the parameters object is valid if (is_null($params) || !is_object($params) || !($params instanceof Registry)) { continue; } foreach ($this->possibleDownloadIDKeys as $key) { $value = $params->get($key, null); if (empty($value)) { continue; } $downloadIDs[] = $value; break; } } if (empty($downloadIDs)) { return ''; } $downloadIDs = array_unique($downloadIDs); return array_shift($downloadIDs); } }