shell bypass 403
<?php /** * @package admintools * @copyright Copyright (c)2010-2024 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace Akeeba\Component\AdminTools\Administrator\Model; defined('_JEXEC') or die; use DateTimeZone; use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\File; use Joomla\CMS\MVC\Model\BaseModel; use Joomla\CMS\User\UserHelper; #[\AllowDynamicProperties] class AdminpasswordModel extends BaseModel { use ApacheVersionTrait; /** * Applies the back-end protection. * * Creates the necessary .htaccess and .htpasswd files in the administrator directory. * * @return bool */ public function protect(): bool { $cryptpw = $this->apacheEncryptPassword(); $htpasswd = $this->getState('username') . ':' . $cryptpw . "\n"; $htpasswdPath = JPATH_ADMINISTRATOR . '/.htpasswd'; $htaccessPath = $this->getPublicAdminFolder() . '/.htaccess'; if (!File::write($htpasswdPath, $htpasswd)) { return false; } switch ($this->getState('mode', 'everything')) { default: case 'everything': $mode = "Everything"; $comment = "Enable password protection for all resources in this directory and its subdirectories"; $wrapBefore = ''; $wrapAfter = ''; break; case 'joomla': $mode = "Joomla"; $comment = "Enable password protection only for Joomla's index.php file in this directory"; $wrapBefore = '<FilesMatch "^index\.php$">'; $wrapAfter = '</FilesMatch>'; break; case 'php': $mode = "All PHP Files"; $comment = "Enable password protection for all .php files in this directory and its subdirectories"; $wrapBefore = '<FilesMatch "\.php$">'; $wrapAfter = '</FilesMatch>'; break; } $app = Factory::getApplication(); $user = $app->getIdentity(); $path = rtrim(JPATH_ADMINISTRATOR, '/\\') . '/'; $date = clone Factory::getDate(); $tz = new DateTimeZone($user->getParam('timezone', $app->get('offset', 'UTC'))); $timestamp = $date->setTimezone($tz)->format('Y-m-d H:i:s T', true); $version = ADMINTOOLS_VERSION; $htaccess = <<<HTACCESS ######################################################################################################################## ## Administrator Password Protection ## ## This file was generated by Admin Tools $version on $timestamp ## ## Password protection mode selected: $mode ## ## If you are unable to access your site's administrator OR see a browser login prompt in the frontend of your site ## please delete this file and the .htpasswd file in the same folder. ######################################################################################################################## ## $comment $wrapBefore <IfModule mod_auth_basic.c> AuthUserFile "{$path}.htpasswd" AuthName "Restricted Area" AuthType Basic Require valid-user </IfModule> $wrapAfter ## Forbid access to the .htpasswd file containing your (hashed) password <FilesMatch "^\.ht"> Require all denied </FilesMatch> HTACCESS; if ($this->getState('resetErrorPages', 1)) { $htaccess .= <<< HTACCESS ## Reset custom error pages to default # # Prevents a 404 error when trying to access your site's administrator directory # # See https://www.akeeba.com/documentation/admin-tools-joomla/admin-pw-protection.html#id604127 # ErrorDocument 401 "Unauthorized" ErrorDocument 403 "Forbidden" HTACCESS; } $status = @file_put_contents($htaccessPath, $htaccess); if (!$status) { $status = File::write($htaccessPath, $htaccess); } if (!$status || !is_file($path . '/.htpasswd')) { File::delete($htpasswdPath); return false; } return true; } /** * Removes the administrator protection. * * Removes both the .htaccess and .htpasswd files from the administrator directory * * @return bool */ public function unprotect(): bool { $htaccessPath = $this->getPublicAdminFolder() . '/.htaccess'; $htpasswdPath = JPATH_ADMINISTRATOR . '/.htpasswd'; return File::delete($htaccessPath) && File::delete($htpasswdPath); } /** * Is the administrator directory password protected? * * Returns true if both a .htpasswd and .htaccess file exist in the back-end * * @return bool */ public function isLocked(): bool { $htaccessPath = $this->getPublicAdminFolder() . '/.htaccess'; $htpasswdPath = JPATH_ADMINISTRATOR . '/.htpasswd'; return @file_exists($htpasswdPath) && @file_exists($htaccessPath); } protected function populateState() { /** @var CMSApplication $app */ $app = Factory::getApplication(); $username = $app->getUserStateFromRequest('com_admintools.adminpassword.username', 'username', '', 'raw'); $this->setState('username', $username); $password = $app->getUserStateFromRequest('com_admintools.adminpassword.password', 'password', '', 'raw'); $this->setState('password', $password); $resetErrorPages = $app->getUserStateFromRequest( 'com_admintools.adminpassword.resetErrorPages', 'resetErrorPages', 1, 'int' ); $this->setState('resetErrorPages', $resetErrorPages); $mode = $app->getUserStateFromRequest('com_admintools.adminpassword.mode', 'mode', 'everything', 'cmd'); $this->setState('mode', $mode); } protected function apacheEncryptPassword() { $os = strtoupper(PHP_OS); $isWindows = substr($os, 0, 3) == 'WIN'; // If this looks like Apache 2.4 we'll use bCrypt instead of legacy password protection $isApache24 = version_compare($this->apacheVersion(), '2.4', 'ge'); if ($isApache24 && function_exists('password_hash') && defined('PASSWORD_BCRYPT')) { return password_hash($this->getState('password'), PASSWORD_BCRYPT); } // Iterated and salted MD5 (APR1) $salt = UserHelper::genRandomPassword(4); $encryptedPassword = $this->apr1_hash($this->getState('password'), $salt, 1000); // SHA-1 encrypted – should never run if (empty($encryptedPassword) && function_exists('base64_encode') && function_exists('sha1')) { $encryptedPassword = '{SHA}' . base64_encode(sha1($this->getState('password'), true)); } // Traditional crypt(3) – should never run if (empty($encryptedPassword) && function_exists('crypt') && !$isWindows) { $salt = UserHelper::genRandomPassword(2); $encryptedPassword = crypt($this->getState('password'), $salt); } // Plain text fallback (should only happen on REALLY old PHP versions incompatible with Joomla) if (empty($encryptedPassword)) { $encryptedPassword = $this->getState('password'); } return $encryptedPassword; } /** * Perform the hashing of the password * * @param string $password The plain text password to hash * @param string $salt The 8 byte salt to use * @param int $iterations The number of iterations to use * * @return string The hashed password */ protected function apr1_hash($password, $salt, $iterations) { $len = strlen($password); $text = $password . '$apr1$' . $salt; $bin = md5($password . $salt . $password, true); for ($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); } for ($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $password[0]; } $bin = $this->apr1_iterate($text, $iterations, $salt, $password); return $this->apr1_convertToHash($bin, $salt); } protected function apr1_iterate($text, $iterations, $salt, $password) { $bin = md5($text, true); for ($i = 0; $i < $iterations; $i++) { $new = ($i & 1) ? $password : $bin; if ($i % 3) { $new .= $salt; } if ($i % 7) { $new .= $password; } $new .= ($i & 1) ? $bin : $password; $bin = md5($new, true); } return $bin; } protected function apr1_convertToHash($bin, $salt) { $tmp = '$apr1$' . $salt . '$'; $tmp .= $this->apr1_to64( (ord($bin[0]) << 16) | (ord($bin[6]) << 8) | ord($bin[12]), 4 ); $tmp .= $this->apr1_to64( (ord($bin[1]) << 16) | (ord($bin[7]) << 8) | ord($bin[13]), 4 ); $tmp .= $this->apr1_to64( (ord($bin[2]) << 16) | (ord($bin[8]) << 8) | ord($bin[14]), 4 ); $tmp .= $this->apr1_to64( (ord($bin[3]) << 16) | (ord($bin[9]) << 8) | ord($bin[15]), 4 ); $tmp .= $this->apr1_to64( (ord($bin[4]) << 16) | (ord($bin[10]) << 8) | ord($bin[5]), 4 ); $tmp .= $this->apr1_to64( ord($bin[11]), 2 ); return $tmp; } /** * Convert the input number to a base64 number of the specified size * * @param int $num The number to convert * @param int $size The size of the result string * * @return string The converted representation */ protected function apr1_to64($num, $size) { static $seed = ''; if (empty($seed)) { $seed = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; } $result = ''; while (--$size >= 0) { $result .= $seed[$num & 0x3f]; $num >>= 6; } return $result; } /** * Returns the absolute filesystem folder to the administrator directory. * * Joomla! 5 can be installed with a custom public folder. In this case the .htaccess file needs to be written in * the public directory, whereas the .htpasswd file needs to be written in the regular administrator folder which is * outside the public folder (web root). * * This method is here to get the public folder where the .htaccess file goes into. * * @return string * * @since 7.4.3 */ private function getPublicAdminFolder(): string { return !defined('JPATH_PUBLIC') ? JPATH_ADMINISTRATOR : (JPATH_PUBLIC . '/administrator'); } }