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 Akeeba\Component\AdminTools\Administrator\Helper\Storage;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
#[\AllowDynamicProperties]
class FixpermissionsModel extends BaseDatabaseModel
{
/** @var int Total numbers of folders in this site */
public $totalFolders = 0;
/** @var int Numbers of folders already processed */
public $doneFolders = 0;
/** @var float The time the process started */
private $startTime = null;
/** @var array The folders to process */
private $folderStack = [];
/** @var array The files to process */
private $filesStack = [];
/** @var int Default directory permissions */
private $dirperms = 0755;
/** @var int Default file permissions */
private $fileperms = 0644;
/** @var array Custom permissions */
private $customperms = [];
/** @var array Skip subdirectories and files of these directories */
private $skipDirs = [];
/**
* Scans $root for directories and updates $folderStack
*
* @param string|null $root The full path of the directory to scan
*/
public function getDirectories(?string $root = null): void
{
$root = $root ?: JPATH_ROOT;
// For some REALLY BROKEN servers...
$root = $root ?: realpath('..');
if (in_array(rtrim($root, '/'), $this->skipDirs))
{
return;
}
$folders = Folder::folders($root, '.', false, true) ?: [];
$this->folderStack = array_merge($this->folderStack, $folders);
$this->totalFolders += count($folders);
}
/**
* Scans $root for files and updates $filesStack
*
* @param string|null $root The full path of the directory to scan
*/
public function getFiles(?string $root = null): void
{
$root = $root ?: JPATH_ROOT;
// For some REALLY BROKEN servers...
$root = $root ?: realpath('..');
if (in_array(rtrim($root, '/'), $this->skipDirs))
{
return;
}
$root = rtrim($root, '/') . '/';
// Should I include dot files, too?
$params = Storage::getInstance();
$excludeFilter = $params->getValue('perms_show_hidden', 0) ? ['.*~'] : ['^\..*', '.*~'];
$folders = Folder::files($root, '.', false, true, [
'.svn', 'CVS', '.DS_Store', '__MACOSX',
], $excludeFilter) ?: [];
$this->filesStack = array_merge($this->filesStack, $folders);
$this->totalFolders += count($folders);
}
public function startScanning(): bool
{
$this->initialize();
$this->resetStack();
$this->resetTimer();
$this->getDirectories();
$this->getFiles();
$this->saveStack();
if (!$this->haveEnoughTime())
{
return true;
}
return $this->run(false);
}
/**
* Change the permissions of a file / folder.
*
* @param string $path Path to file/folder to change permissions on
* @param string|int $mode File mode, either as an octal string (e.g. '0644') or as an integer.
*
* @return bool
*/
public function chmod(string $path, $mode): bool
{
if (is_string($mode))
{
$mode = octdec($mode);
if (($mode <= 0) || ($mode > 0777))
{
$mode = 0755;
}
}
// Check to make sure the path valid and clean
$path = Path::clean($path);
$effectivePath = rtrim(JPATH_SITE, DIRECTORY_SEPARATOR . '/') . DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR . '/');
return @chmod($effectivePath, $mode);
}
public function run(bool $resetTimer = true): bool
{
if ($resetTimer)
{
$this->resetTimer();
}
$this->loadStack();
$result = true;
while ($result && $this->haveEnoughTime())
{
$result = $this->RealRun();
}
$this->saveStack();
return $result;
}
public function getRelativePath(string $somePath): string
{
$path = Path::clean($somePath, '/');
$root = Path::clean(JPATH_ROOT, '/');
return ltrim(substr($path, strlen($root)), '/');
}
private function initialize(): void
{
$params = Storage::getInstance();
$dirperms = '0' . ltrim(trim($params->getValue('dirperms', '0755')), '0');
$fileperms = '0' . ltrim(trim($params->getValue('fileperms', '0644')), '0');
$dirperms = octdec($dirperms);
if (($dirperms < 0400) || ($dirperms > 0777))
{
$dirperms = 0755;
}
$this->dirperms = $dirperms;
$fileperms = octdec($fileperms);
if (($fileperms < 0400) || ($fileperms > 0777))
{
$fileperms = 0644;
}
$this->fileperms = $fileperms;
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([
$db->qn('path'),
$db->qn('perms'),
])->from($db->qn('#__admintools_customperms'))
->order($db->qn('path') . ' ASC');
$this->customperms = $db->setQuery($query)->loadAssocList('path');
// Add cache, tmp and log to the exceptions
$app = Factory::getApplication();
$this->skipDirs[] = rtrim(JPATH_CACHE, '/');
$this->skipDirs[] = rtrim(JPATH_ROOT . '/cache', '/');
$this->skipDirs[] = rtrim($app->get('tmp_path', JPATH_ROOT . '/tmp'), '/');
$this->skipDirs[] = rtrim($app->get('log_path', JPATH_ROOT . '/logs'), '/');
$this->skipDirs[] = JPATH_ADMINISTRATOR . '/logs';
$this->skipDirs[] = JPATH_ADMINISTRATOR . '/log';
$this->skipDirs[] = JPATH_ROOT . '/logs';
$this->skipDirs[] = JPATH_ROOT . '/log';
}
/**
* Starts or resets the internal timer
*/
private function resetTimer(): void
{
$this->startTime = microtime(true);
}
/**
* Makes sure that no more than 3 seconds since the start of the timer have elapsed
*
* @return bool
*/
private function haveEnoughTime(): bool
{
$now = microtime(true);
$elapsed = abs($now - $this->startTime);
return $elapsed < 2;
}
/**
* Saves the file/folder stack in the session
*/
private function saveStack(): void
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->delete($db->quoteName('#__admintools_storage'))
->where($db->quoteName('key') . ' = ' . $db->quote('fixperms_stack'));
$db->setQuery($query)->execute();
$object = (object) [
'key' => 'fixperms_stack',
'value' => json_encode([
'folders' => $this->folderStack,
'files' => $this->filesStack,
'total' => $this->totalFolders,
'done' => $this->doneFolders,
]),
];
$db->insertObject('#__admintools_storage', $object);
}
/**
* Resets the file/folder stack saved in the session
*/
private function resetStack(): void
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->delete($db->quoteName('#__admintools_storage'))
->where($db->quoteName('key') . ' = ' . $db->quote('fixperms_stack'));
$db->setQuery($query)->execute();
$this->folderStack = [];
$this->filesStack = [];
$this->totalFolders = 0;
$this->doneFolders = 0;
}
/**
* Loads the file/folder stack from the session
*/
private function loadStack(): void
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([$db->quoteName('value')])
->from($db->quoteName('#__admintools_storage'))
->where($db->quoteName('key') . ' = ' . $db->quote('fixperms_stack'));
$stack = $db->setQuery($query)->loadResult();
if (empty($stack))
{
$this->folderStack = [];
$this->filesStack = [];
$this->totalFolders = 0;
$this->doneFolders = 0;
return;
}
$stack = json_decode($stack, true);
$this->folderStack = $stack['folders'];
$this->filesStack = $stack['files'];
$this->totalFolders = $stack['total'];
$this->doneFolders = $stack['done'];
}
private function RealRun(): bool
{
while (empty($this->filesStack) && !empty($this->folderStack))
{
// Get a directory
$dir = null;
while (empty($dir) && !empty($this->folderStack))
{
// Get the next directory
$dir = array_shift($this->folderStack);
// Skip over non-directories and symlinks
if (!@is_dir($dir) || @is_link($dir))
{
$dir = null;
continue;
}
// Skip over . and ..
$checkDir = str_replace('\\', '/', $dir);
if (in_array(basename($checkDir), [
'.', '..',
]) || (substr($checkDir, -2) == '/.') || (substr($checkDir, -3) == '/..'))
{
$dir = null;
continue;
}
// Check for custom permissions
$reldir = $this->getRelativePath($dir);
$perms = $this->dirperms;
if (array_key_exists($reldir, $this->customperms))
{
$perms = $this->customperms[$reldir]['perms'];
}
// Apply new permissions
$this->chmod($dir, $perms ?: $this->dirperms);
$this->doneFolders++;
$this->getDirectories($dir);
$this->getFiles($dir);
if (!$this->haveEnoughTime())
{
// Gotta continue in the next step
return true;
}
}
}
if (empty($this->filesStack) && empty($this->folderStack))
{
// Just finished
$this->resetStack();
return false;
}
if (!empty($this->filesStack) && $this->haveEnoughTime())
{
while (!empty($this->filesStack))
{
$file = array_shift($this->filesStack);
// Skip over symlinks and non-files
if (@is_link($file) || !@is_file($file))
{
continue;
}
$reldir = $this->getRelativePath($file);
$perms = $this->fileperms;
if (array_key_exists($reldir, $this->customperms))
{
$perms = $this->customperms[$reldir]['perms'];
}
$this->chmod($file, $perms ?: $this->fileperms);
}
}
if (empty($this->filesStack) && empty($this->folderStack))
{
// Just finished
$this->resetStack();
return false;
}
return true;
}
}