<?php /* * @package bfNetwork * @copyright Copyright (C) 2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025 Blue Flame Digital Solutions Ltd. All rights reserved. * @license GNU General Public License version 3 or later * * @see https://mySites.guru/ * @see https://www.phil-taylor.com/ * * @author Phil Taylor / Blue Flame Digital Solutions Limited. * * bfNetwork 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. * * bfNetwork 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 package. If not, see http://www.gnu.org/licenses/ * * If you have any questions regarding this code, please contact [email protected] */ use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Schema\ChangeSet; use Joomla\CMS\Version; use Joomla\Component\Installer\Administrator\Model\DatabaseModel; require 'bfEncrypt.php'; /* * If we have got here then we have already passed through decrypting * the encrypted header and so we are sure we are now secure and no one * else cannot run the code below. */ // require all we need to access Joomla API if (! defined('BF_JOOMLA_INIT_DONE')) { require_once 'bfInitJoomla.php'; } require_once 'bfActivitylog.php'; final class bfSnapshot { public $_data; private $db; private $version; private $container; private $config; public function __construct() { $this->cleanOurCrap(); // Ask Joomla to report config through its API $this->container = Factory::getContainer(); $this->config = $this->container->get('config'); // Connect to the database $this->initDb(); $this->logSnapshot(); $session_save_path = ini_get('session.save_path'); $this->_data = [ 'version' => $this->getJoomlaVersion(), 'connectorversion' => file_get_contents('./VERSION'), 'php_version' => \PHP_VERSION, 'php_disabled_functions' => ini_get('disable_functions'), 'display_errors' => ini_get('display_errors'), 'register_globals' => (int) ini_get('register_globals'), 'safe_mode' => (int) ini_get('safe_mode'), 'file_uploads' => (int) ini_get('file_uploads'), 'magic_quotes_gpc' => (int) ini_get('magic_quotes_gpc'), 'magic_quotes_runtime' => (int) ini_get('magic_quotes_runtime'), 'session_autostart' => (int) ini_get('session_autostart'), 'gc_probability' => (int) ini_get('session.gc_probability'), 'mysql_version' => $this->initDb(), 'session_save_path' => @ini_get('session.save_path') ? ini_get('session.save_path') : '/tmp', 'is_windows_host' => (int) ('WIN' == substr(\PHP_OS, 0, 3)) ? 1 : 0, 'session_save_path_writable' => (int) @is_writable($session_save_path), 'db_prefix' => $this->config->get('dbprefix', ''), 'dbs_visible' => $this->getVisibleDbsCount(), 'db_user_is_root' => (int) ('root' == $this->config->get('user', '') ? 1 : 0), 'db_bak_tables' => (int) $this->hasBakTables(), 'memory_limit' => ini_get('memory_limit'), 'has_installation_folders' => (int) $this->hasInstallationFolders(), 'site_debug_enabled' => (int) $this->config->get('debug') ? 1 : 0, 'has_ftp_configured' => (int) $this->checkFTPLayer(), 'numberofsuperadmins' => $this->getNumberOfSuperAdmins(), 'adminusernames' => $this->getAdminUserNameCount(), 'neverloggedinusers' => $this->getNeverLoggedInUsersCount(), 'hasjce' => $this->hasExtensionWithNameInstalled('com_jce'), 'hasakeebabackup' => $this->hasExtensionWithNameInstalled('com_akeebabackup'), 'site_offline' => $this->config->get('offline', ''), 'cache_enabled' => $this->config->get('caching', ''), 'sef_enabled' => $this->config->get('sef', ''), 'tmplogfolderswritable' => (int) $this->hastmplogfolderswritable(), 'extensionupdatesavailable' => null, // Now called in separate job was $this->hasUpdatesAvailable(), 'defaulttemplateused' => (int) $this->hasUsedDefaultTemplate(), 'tpequalsone' => $this->hastpequalsone(), 'configsymlinked' => (is_link(JPATH_BASE . '/configuration.php') ? 1 : 0), 'kickstartseen' => (int) $this->kickstartexists(), 'fpaseen' => (int) $this->fpaexists(), 'userregistrationenabled' => (int) ComponentHelper::getParams('com_users')->get('allowUserRegistration'), 'has_root_htaccess' => (int) (file_exists(JPATH_BASE . '/.htaccess') ? 1 : 0), 'adminhtaccess' => (int) (file_exists(JPATH_BASE . '/administrator/.htaccess') ? 1 : 0), 'gzipenabled' => (int) $this->config->get('gzip', ''), 'gcerrorreportingnone' => (int) $this->getErrorReportingLevel(), 'livesitevarset' => strlen($this->config->get('live_site', '')) > 1 ? 1 : 0, 'cookiedomainpath' => ($this->config->get('cookie_path') || $this->config->get('cookie_domain')) ? 1 : 0, 'sessionlifetime' => (int) $this->config->get('lifetime'), 'akeebabackupscount' => (int) $this->getNumberOfAkeebaBackups(), 'md5passwords' => (int) $this->hasmd5passwords(), 'tmplogfoldersdefaultpaths' => (int) $this->hastmplogfoldersdefaultpaths(), 'max_allowed_packet' => (int) $this->getMaxAllowedPacket(), 'jceversion' => $this->checkJCEVersion(), 'fluff' => (int) $this->checkfluff(), 'db_schema' => $this->checkdbschema(), 'robots_blocks_media' => (int) $this->checkRobotsBlocksMedia(), 'server_hostname' => function_exists('gethostname') ? gethostname() : php_uname('n'), 'akeeba_dir_problems' => 0, // @deprecated 'diskspace' => $this->getDiskSpace(), 'hacked' => $this->checkIf100percentHackedOrNot(), 'new_usertype' => $this->getNewUserType(), 'non2faadmins' => (int) $this->getNon2FaAdmins(), 'users_hacked' => $this->checkJoomlaUserHelperHack2016(), 'sessiongcpublished' => $this->getSessionGCStatus(), 'twofactorenabled' => $this->getTwoFactorPluginsEnabled(), 'adminfilterfixed' => $this->getAdminFilterFixed(), 'userfilterfixed' => $this->getUserFilterFixed(), 'plaintextpasswordsfixed' => $this->getPlaintextpasswordsFixed(), 'uploadsettingsfixed' => (int) $this->getUploadsettingsfixed(), 'captchaenabled' => (int) $this->getCaptchaDetails(), 'useractionlogenabled' => (int) $this->getUseractionlogenabled(), 'plgprivacyconsentenabled' => (int) $this->getPrivacyConsentPluginEnabled(), 'useractionlogiplogenabled' => (int) $this->getActionLogsIPLoggingEnabled(), 'systemlogrotationenabled' => (int) $this->getSystemLogRotationEnabled(), 'hasprivacypolicy' => (int) $this->hasprivacypolicy(), 'privacypendingremove' => (int) $this->getPrivacypendingremove(), 'privacycompletedexport' => (int) $this->getPrivacycompletedexport(), 'privacypendingexport' => (int) $this->getPrivacypendingexport(), 'privacycompletedremove' => (int) $this->getPrivacycompletedremove(), 'privacyoverdue' => (int) $this->getPrivacyoverdue(), 'privacyconfirmedremove' => (int) $this->getPrivacyconfirmedremove(), 'privacyconfirmedexport' => (int) $this->getPrivacyconfirmedexport(), 'enablepurge30days' => (int) $this->getPurge30Days(), 'extensionsjson' => $this->getExtensions(), 'hasrootuser' => $this->config->get('root_user') ? 1 : 0, 'debuglanguage' => $this->config->get('debug_lang') ? 1 : 0, 'apilogenabled' => (int) ComponentHelper::getParams('com_actionlogs')->get('loggable_api'), 'postinstallmsgcount' => count($this->getPostInstallMessages()), 'sendcopytosubmitter' => (int) ComponentHelper::getParams('com_contact')->get('show_email_copy', 0), 'fileinfo' => \extension_loaded('fileinfo'), 'guidedtour' => (int) $this->getGuidedToursEnabled(), 'thumbnails' => (int) $this->getThumbnailsEnabled(), 'updatesource' => (string) ComponentHelper::getParams('com_joomlaupdate')->get('updatesource'), 'activetheme' => (string) $this->getThemeName(), 'updateemails' => (int) $this->getUpdateEmailsState(), 'logeverything' => (int) $this->config->get('log_everything', '0'), 'logdeprecated' => (int) $this->config->get('log_deprecated', '0'), ]; } public function getUpdateEmailsState() { $sql = 'select count(*) from #__extensions where type = "plugin" and element = "updatenotification" and enabled = 1'; $this->db->setQuery($sql); return $this->db->LoadResult(); } public function getThemeName() { try { $sql = 'select template from #__template_styles where client_id = 0 and home = 1 LIMIT 1'; $this->db->setQuery($sql); $row = $this->db->LoadObject(); $data = []; $data['Name']=$row->template; return json_encode($data); } catch (\Exception $exception) { } return ''; } public function getThumbnailsEnabled() { $this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'plg_filesystem_local'"); $params = $this->db->LoadResult(); if ('{}' == $params || null == $params || '' == $params) { return; } $params = json_decode($params, JSON_OBJECT_AS_ARRAY); if (! array_key_exists('directories', $params)) { return; } $thumbsCount = 0; foreach ($params['directories'] as $directory) { if (! array_key_exists('thumbs', $directory)) { continue; } if ($directory['thumbs'] == 1) { $thumbsCount++; } } return (int) $thumbsCount === \count($params['directories']); } /** * Get the post install messages from a Joomla 3+ site. */ public function getPostInstallMessages() { require JPATH_BASE . '/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php'; require JPATH_BASE . '/administrator/components/com_postinstall/src/Model/MessagesModel.php'; $model = Factory::getApplication() ->bootComponent('com_postinstall') ->getMVCFactory() ->createModel('Messages', 'Administrator', [ 'ignore_request' => true, ]); $messages = $model->getItems(); foreach ($messages as $k => $item) { $item->title_key = Text::_($item->title_key); $item->description_key = Text::_($item->description_key); } return $messages; } private function logSnapshot() { bfActivitylog::getInstance()->log( 'bfNetwork', '', 'Snapshot Taken', 'onSnapshotTaken', '', null, null, '', bfEvents::onSnapshotTaken, 'onSnapshotTaken', bfEvents::onSnapshotTaken ); } public function getExtensions() { require 'bfExtensions.php'; $ext = new bfExtensions(); return $ext->getExtensions(); } /** * Get the number of days to delete logs after from the System - User Actions Log. * * @return int */ public function getPurge30Days() { $this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'"); $params = $this->db->LoadResult(); if ('{}' == $params || $params === null) { return; } $params = json_decode($params); if (! $params) { return; } return $params->logDeletePeriod; } public function getPrivacypendingremove() { $this->db->setQuery("select count(*) from #__privacy_requests where status = 0 and request_type = 'remove'"); return $this->db->LoadResult(); } public function getPrivacyconfirmedremove() { $this->db->setQuery("select count(*) from #__privacy_requests where status = 1 and request_type = 'remove'"); return $this->db->LoadResult(); } public function getPrivacyconfirmedexport() { $this->db->setQuery("select count(*) from #__privacy_requests where status = 1 and request_type = 'export'"); return $this->db->LoadResult(); } public function getPrivacycompletedexport() { $this->db->setQuery("select count(*) from #__privacy_requests where status = 2 and request_type = 'export'"); return $this->db->LoadResult(); } public function getPrivacypendingexport() { $this->db->setQuery("select count(*) from #__privacy_requests where status = 0 and request_type = 'export'"); return $this->db->LoadResult(); } public function getPrivacycompletedremove() { $this->db->setQuery("select count(*) from #__privacy_requests where status = 2 and request_type = 'remove'"); return $this->db->LoadResult(); } /** * Get the overdue requests. * * @return bool */ public function getPrivacyoverdue() { // Load the parameters. $params = ComponentHelper::getComponent('com_privacy')->getParams(); $notify = (int) $params->get('notify', 14); $now = Factory::getDate()->toSql(); $period = '-' . $notify; $query = $this->db->getQuery(true) ->select('COUNT(*)'); $query->from($this->db->quoteName('#__privacy_requests')); $query->where($this->db->quoteName('status') . ' = 1 '); $query->where($query->dateAdd($this->db->quote($now), $period, 'DAY') . ' > ' . $this->db->quoteName('requested_at')); $this->db->setQuery($query); return $this->db->LoadResult(); } /** * Joomla 3.9.0+ Check for system log rotation plugin. */ public function getSystemLogRotationEnabled() { $this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_logrotation' and enabled = 1"); return $this->db->LoadResult(); } public function getGuidedToursEnabled() { $this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_guidedtours' and enabled = 1"); return $this->db->LoadResult(); } /** * Joomla 3.9.0+ Check for action log ip logging enabled. */ public function hasprivacypolicy() { $this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'plg_system_privacyconsent'"); $res = $this->db->LoadResult(); if (! $res) { return 0; } $params = json_decode($res); if ($params && property_exists($params, 'privacy_article')) { return $params->privacy_article > 0 ? 1 : 0; } return 0; } /** * Joomla 3.9.0+ Check for action log ip logging enabled. */ public function getActionLogsIPLoggingEnabled() { $this->db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'com_actionlogs'"); $params = json_decode($this->db->LoadResult()); if ($params && property_exists($params, 'ip_logging')) { return $params->ip_logging; } return 0; } /** * Joomla 3.9.0+ Check for action log ip logging enabled. */ public function getUseractionlogenabled() { $this->db->setQuery( "SELECT count(*) FROM `#__extensions` WHERE (`name` = 'PLG_ACTIONLOG_JOOMLA' or `name` = 'PLG_SYSTEM_ACTIONLOGS') and enabled = 1" ); return 2 == $this->db->LoadResult() ? 1 : 0; } /** * Joomla 3.9.0+ Check for plg_privacy_actionlogs enabled. */ public function getPrivacyConsentPluginEnabled() { $this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_privacyconsent' and enabled = 1"); return $this->db->LoadResult(); } /** * Checks if the FTP Layer is in anyway configured. * * @return bool */ private function checkFTPLayer() { $ftp_pass = $this->config->get('ftp_pass', ''); $ftp_user = $this->config->get('ftp_user', ''); $ftp_enable = $this->config->get('ftp_enable', ''); $ftp_host = $this->config->get('ftp_host', ''); $ftp_root = $this->config->get('ftp_root', ''); if ($ftp_pass || $ftp_user || '1' == $ftp_enable || $ftp_host || $ftp_root) { return true; } return false; } /** * Clean up old mysites.guru files and features. */ private function cleanOurCrap() { // cleanup old files $oldFiles = [ 'upgrade.zip', './bfViewLog.php', './bfDev.php', './bfMysql.php', './j25_30_bfnetwork.xml', // dont get confused with the one in the folder above this. './install.bfnetwork.php', './bfnetwork.xml', './bfJson.php', './tmp/log.tmp', './tmp/tmp.ob', ]; foreach ($oldFiles as $file) { if (file_exists($file)) { @unlink($file); } } // cleanup if (file_exists('../j25_30_bfnetwork.xml')) { @copy('../j25_30_bfnetwork.xml', '../bfnetwork.xml'); @unlink('../j25_30_bfnetwork.xml'); } $fileContent = file_get_contents('../bfnetwork.php'); if (! preg_match('/bfPlugin/', $fileContent)) { $fileContent = str_replace([ "\n\n", '// For more details please contact Phil Taylor <[email protected]>', '// This is NOT a Joomla Extension or Plugin and is NOT designed for consumption within Joomla - yet :)', ], '', $fileContent); $fileContent = $fileContent . " /** * All our code is in the sub folder, as that is what is auto-upgraded * and fully maintained by the automated processes at mysites.guru */ require 'bfnetwork/bfPlugin.php';"; file_put_contents('../bfnetwork.php', $fileContent); } bfActivitylog::getInstance(); } /** * Init the Joomla db connection. */ private function initDb() { try { $this->db = $this->container->get('DatabaseDriver'); if (! $conn = $this->db->getConnection()) { $this->db->connect(); $conn = $this->db->getConnection(); } if (! $conn) { return ''; } if (get_class($conn) === \PDO::class) { $dbVerString = $conn->getAttribute(\PDO::ATTR_SERVER_VERSION); } else { $dbVerString = $conn->server_info; } } catch (\Exception $exception) { $dbVerString = ''; } return $dbVerString; } private function getJoomlaVersion() { $VERSION = new Version(); // Store in our object for switching configs $this->version = $VERSION->getShortVersion(); return $VERSION->getShortVersion(); } /** * How many databases can I see? * * We need to reconnect again to the db so we are ot going through the Joomla DB Layer because it just crashes too * far up the stack for us to catch the exception * * @return int */ private function getVisibleDbsCount() { if (! function_exists('mysqli_connect')) { return 1; } $count = 0; try { // Create correct commands based on how old and crap the server is! switch ($this->config->get('dbtype')) { default: case \mysqli::class: $port = '3306'; // Handle localhost:3306 type hosts $parts = explode(':', $this->config->get('host')); if (\count($parts) > 1) { $port = $parts[1]; } else { $port = null; } $link = mysqli_connect($parts[0], $this->config->get('user'), $this->config->get('password'), null, $port); if (! $link) { return 0; } $res = mysqli_query( $link, 'SHOW DATABASES where `Database` NOT IN ("test","performance_schema", "information_schema", "mysql")' ); if (! $res) { return 0; } $count = $res->num_rows; // tidy up mysqli_close($link); break; } // return number seen return $count; } catch (Exception $e) { return $count; } } /** * Do we have any backup tables. * * @return string */ private function hasBakTables() { $this->db->setQuery("SHOW TABLES WHERE `Tables_in_{$this->config->get('db', '')}` like 'bak_%'"); return $this->db->loadResult() ? true : false; } /** * See if we have any installation folders. * * @return string "TRUE|FALSE" if we do */ private function hasInstallationFolders() { $folders = $this->getFolders(JPATH_BASE); foreach ($folders as $folder) { if (preg_match( '/installation|installation.old|docs\/installation|install|installation.bak|installation.old|installation.backup|installation.delete/i', $folder, $matches )) { return true; } } return false; } /** * Function taken from Akeeba filesystem.php. * * Akeeba Engine The modular PHP5 site backup engine * * @copyright Copyright (c)2009 Nicholas K. Dionysopoulos * @license GNU GPL version 3 or, at your option, any later version * * @version Id: scanner.php 158 2010-06-10 08:46:49Z nikosdion */ private function getFolders($folder) { // Initialize variables $arr = []; $false = false; $folder = trim($folder); if (! is_dir($folder) && ! is_dir($folder . \DIRECTORY_SEPARATOR) || is_link($folder . \DIRECTORY_SEPARATOR) || is_link( $folder ) || ! $folder) { return $false; } if (@file_exists($folder . \DIRECTORY_SEPARATOR . '.myjoomla.ignore.folder')) { return []; } $handle = @opendir($folder); if (false === $handle) { $handle = @opendir($folder . \DIRECTORY_SEPARATOR); } // If directory is not accessible, just return FALSE if (false === $handle) { return $false; } while ((false !== ($file = @readdir($handle)))) { if (('.' != $file) && ('..' != $file) && (null != trim($file))) { $ds = ('' == $folder) || (\DIRECTORY_SEPARATOR == $folder) || (\DIRECTORY_SEPARATOR == @substr( $folder, -1 )) || (\DIRECTORY_SEPARATOR == @substr($folder, -1)) ? '' : \DIRECTORY_SEPARATOR; $dir = trim($folder . $ds . $file); $isDir = @is_dir($dir); if ($isDir) { $arr[] = $this->cleanupFileFolderName(str_replace(JPATH_BASE, '', $folder . \DIRECTORY_SEPARATOR . $file)); } } } @closedir($handle); return $arr; } /** * Clean up a string, a path name. * * @param string $str * * @return string */ private function cleanupFileFolderName($str) { $str = str_replace('////', '/', $str); $str = str_replace('///', '/', $str); $str = str_replace('//', '/', $str); $str = str_replace('\\/', '/', $str); $str = str_replace('\\t', '/t', $str); $str = str_replace("\/", '/', $str); return addslashes($str); } /** * The the number of super admins. * * @return int The number of super admins * @todo remove hard coded 8 and look for the correct group_id if people have messed with ACL */ private function getNumberOfSuperAdmins() { $this->db->setQuery('SELECT count(*) FROM #__user_usergroup_map WHERE group_id = 8'); return (int) $this->db->LoadResult(); } /** * Report if any users have a username of 'admin'. * * @return int */ private function getAdminUserNameCount() { $this->db->setQuery('SELECT COUNT(*) FROM #__users WHERE username = "admin"'); return (int) $this->db->LoadResult(); } private function getNeverLoggedInUsersCount() { $this->db->setQuery('SELECT COUNT(*) FROM #__users WHERE lastvisitDate IS NULL'); return (int) $this->db->LoadResult(); } /** * See if we have extension installed. */ private function hasExtensionWithNameInstalled($name) { $count = 0; $folders = $this->getFolders(JPATH_BASE . '/administrator/components/'); foreach ($folders as $folder) { if (preg_match('/' . $name . '/i', $folder, $matches)) { ++$count; } } return $count; } private function hastmplogfolderswritable() { return is_writable($this->config->get('tmp_path')) && is_writable($this->config->get('log_path')); } /** * @return bool */ private function hasUsedDefaultTemplate() { $core_templates = [ 'atomic', 'beez_20', 'beez_5', 'beez3', 'ja_purity', 'protostar', 'rhuk_milkyway', 'rhuk_milkyway_2', 'cassiopeia', ]; $this->db->setQuery('SELECT template FROM #__template_styles WHERE client_id=0 AND home=1'); return (bool) in_array($this->db->loadResult(), $core_templates); } private function hastpequalsone() { if (1 == ComponentHelper::getParams('com_templates') ->get('template_positions_display') ) { // allowed - which is bad $tpequalsone = 1; } else { // not allowed - which is good $tpequalsone = 0; } return $tpequalsone; } private function kickstartexists() { $files = scandir(JPATH_BASE); foreach ($files as $file) { if (preg_match('/.*kickstart.*\.php/i', $file)) { return true; } } return false; } private function fpaexists() { $files = scandir(JPATH_BASE); foreach ($files as $file) { if (preg_match('/.*fpa.*\.php/i', $file)) { return true; } } return false; } private function getErrorReportingLevel() { $er = $this->config->get('error_reporting'); if (! is_int($er)) { switch ($er) { case 'none': $er = 0; break; case 'simple': $er = 7; break; case 'maximum': $er = 2047; break; case 'development': $er = -1; break; default: $er = $er; // yeah yeah I know! break; } } return $er; } private function getNumberOfAkeebaBackups() { $count = 0; $folder = JPATH_BASE . '/administrator/components/com_akeeba/backup'; if (file_exists($folder)) { $folderContents = scandir($folder); foreach ($folderContents as $file) { if (preg_match('/\.jpa$/i', $file)) { ++$count; } } } return $count; } private function hasmd5passwords() { $this->db->setQuery('SELECT count(*) FROM #__users WHERE CHAR_LENGTH(password) = 32'); return (int) $this->db->LoadResult(); } private function hastmplogfoldersdefaultpaths() { $logPath = $this->config->get('log_path'); $tmpPath = $this->config->get('tmp_path'); $expectedLogPath1 = JPATH_BASE . '/logs'; $expectedLogPath2 = JPATH_BASE . '/administrator/logs'; // Introduced in Joomla 3.6.0 $expectedTmpPath = JPATH_BASE . '/tmp'; return (int) (($expectedLogPath1 == $logPath || $expectedLogPath2 == $logPath) && $expectedTmpPath == $tmpPath); } private function getMaxAllowedPacket() { $this->db->setQuery('SHOW VARIABLES LIKE "max_allowed_packet"'); $res = $this->db->loadObjectList(); return $res[0]->Value; } /** * @return string */ private function checkJCEVersion() { $versionFile = JPATH_BASE . '/administrator/components/com_jce/jce.xml'; if (file_exists($versionFile)) { $xml = file_get_contents($versionFile); preg_match('/\<version\>(.*)\<\/version\>/', $xml, $matches); if (count($matches)) { return $matches[1]; } else { return false; } } else { return false; } } private function checkfluff() { $fluffFiles = [ '/.drone.yml', '/robots.txt.dist', '/web.config.txt', '/joomla.xml', '/build.xml', '/LICENSE.txt', '/README.txt', '/htaccess.txt', '/LICENSES.php', '/configuration.php-dist', '/CHANGELOG.php', '/COPYRIGHT.php', '/CREDITS.php', '/INSTALL.php', '/LICENSE.php', '/CONTRIBUTING.md', '/phpunit.xml.dist', '/README.md', '/.travis.yml', '/travisci-phpunit.xml', '/images/banners/osmbanner1.png', '/images/banners/osmbanner2.png', '/images/banners/shop-ad-books.jpg', '/images/banners/shop-ad.jpg', '/images/banners/white.png', '/images/headers/blue-flower.jpg', '/images/headers/maple.jpg', '/images/headers/raindrops.jpg', '/images/headers/walden-pond.jpg', '/images/headers/windows.jpg', '/images/joomla_black.gif', '/images/joomla_black.png', '/images/joomla_green.gif', '/images/joomla_logo_black.jpg', '/images/powered_by.png', '/images/sampledata/fruitshop/apple.jpg', '/images/sampledata/fruitshop/bananas_2.jpg', '/images/sampledata/fruitshop/fruits.gif', '/images/sampledata/fruitshop/tamarind.jpg', '/images/sampledata/parks/animals/180px_koala_ag1.jpg', '/images/sampledata/parks/animals/180px_wobbegong.jpg', '/images/sampledata/parks/animals/200px_phyllopteryx_taeniolatus1.jpg', '/images/sampledata/parks/animals/220px_spottedquoll_2005_seanmcclean.jpg', '/images/sampledata/parks/animals/789px_spottedquoll_2005_seanmcclean.jpg', '/images/sampledata/parks/animals/800px_koala_ag1.jpg', '/images/sampledata/parks/animals/800px_phyllopteryx_taeniolatus1.jpg', '/images/sampledata/parks/animals/800px_wobbegong.jpg', '/images/sampledata/parks/banner_cradle.jpg', '/images/sampledata/parks/landscape/120px_pinnacles_western_australia.jpg', '/images/sampledata/parks/landscape/120px_rainforest_bluemountainsnsw.jpg', '/images/sampledata/parks/landscape/180px_ormiston_pound.jpg', '/images/sampledata/parks/landscape/250px_cradle_mountain_seen_from_barn_bluff.jpg', '/images/sampledata/parks/landscape/727px_rainforest_bluemountainsnsw.jpg', '/images/sampledata/parks/landscape/800px_cradle_mountain_seen_from_barn_bluff.jpg', '/images/sampledata/parks/landscape/800px_ormiston_pound.jpg', '/images/sampledata/parks/landscape/800px_pinnacles_western_australia.jpg', '/images/sampledata/parks/parks.gif', ]; $fluffCount = 0; foreach ($fluffFiles as $file) { $fileWithPath = JPATH_BASE . $file; if (file_exists($fileWithPath)) { ++$fluffCount; } } return (int) $fluffCount; } private function checkdbschema() { /** @var DatabaseModel $model */ $model = Factory::getApplication() ->bootComponent('com_installer') ->getMVCFactory() ->createModel('Database', 'Administrator', [ 'ignore_request' => true, ]); $schemaData = new stdClass(); // Force sensible defaults to stop Joomla, with PHP 8+, and Error Reporting Maximum - from showing Deprecated warnings $model->setState('list.limit', 1000); $model->setState('list.start', 0); $model->setState('filter.search', ''); foreach ($model->getItems() as $item) { if ('com_admin' !== $item['extension']->element) { continue; } $schemaData->version_latest = $item['extension']->version; $schemaData->version_current = JVERSION; } $changeSet = new ChangeSet($this->db, null); $this->db->setQuery('select extension_id from #__extensions where name = "files_joomla"'); $filesJoomlaId = $this->db->loadResult(); $schemaData->latest = $changeSet->getSchema(); $schemaData->current = $model->getSchemaVersion($filesJoomlaId); return json_encode($schemaData); } private function checkRobotsBlocksMedia() { $robots_blocks_media = 0; if (file_exists(JPATH_BASE . '/robots.txt')) { $robotsTxTContent = file_get_contents(JPATH_BASE . '/robots.txt'); if (preg_match('/Disallow:\s\/(templates|media)\//', $robotsTxTContent)) { $robots_blocks_media = 1; } } return $robots_blocks_media; } private function getDiskSpace() { if (! function_exists('disk_free_space') || ! function_exists('disk_total_space')) { return json_encode([]); } $data = [ 'free' => disk_free_space(JPATH_BASE), 'total' => disk_total_space(JPATH_BASE), ]; $data['used'] = $data['total'] - $data['free']; $data['percentUsed'] = sprintf('%.2f', ($data['used'] / $data['total']) * 100); $data['free'] = $this->formatSize($data['free']); $data['total'] = $this->formatSize($data['total']); $data['used'] = $this->formatSize($data['used']); return json_encode($data); } private function formatSize($bytes) { $types = ['B', 'KB', 'MB', 'GB', 'TB']; for ($i = 0; $bytes >= 1024 && $i < (count($types) - 1); $bytes /= 1024, $i++); return round($bytes, 2) . ' ' . $types[$i]; } /** * Run some very specific checks to see if this site is hacked or not. */ private function checkIf100percentHackedOrNot() { // oh, not dont this yet :) doing it service site instead :) } private function getNewUserType() { $this->db->setQuery("SELECT params FROM #__extensions WHERE name ='com_users'"); $paramsJsonString = $this->db->loadResult(); preg_match('/new_usertype\":\"([0-9]*)\"/', $paramsJsonString, $matches); return count($matches) ? $matches[1] : null; } private function getNon2FaAdmins() { if (version_compare($this->getJoomlaVersion(), '4.2.0') >= 0) { $this->db->setQuery(" select * from #__users as u left join #__user_usergroup_map as ugm on ugm.user_id = u.id WHERE ugm.group_id IN ( select id from #__usergroups where title = 'Super Users' or title = 'Super Utilisateur' ) "); $superUsers = $this->db->loadObjectList(); $Non2FaAdmins = \count($superUsers); foreach ($superUsers as $user) { if (\count(\Joomla\Component\Users\Administrator\Helper\Mfa::getUserMfaRecords($user->id))) { $Non2FaAdmins--; } } return $Non2FaAdmins; } else { try { $this->db->setQuery("select count(*) from #__users as u left join #__user_usergroup_map as ugm on ugm.user_id = u.id where (otpKey = \"\" or otpKey IS NULL) and (ugm.group_id IN (select id from #__usergroups where (title = 'Super Users' or title = 'Super Utilisateur')))"); return $this->db->loadResult(); } catch (\Exception $e) { return 0; } } } /** * Check for joomla.user.helper.XXXXX usernames - hack seen in Q4 2016. */ private function checkJoomlaUserHelperHack2016() { $this->db->setQuery("select count(*) from #__users where username LIKE 'joomla.user.helper.%'"); return $this->db->loadResult(); } /** * Check the session gc plugin in Joomla 3. * * @return int */ public function getSessionGCStatus() { $res = 2; // Session GC $this->db->setQuery("select count(*) from #__extensions where name = 'plg_system_sessiongc'"); $hasSessionGcPlugin = $this->db->LoadResult(); if ($hasSessionGcPlugin) { $this->db->setQuery("select enabled from #__extensions where name = 'plg_system_sessiongc'"); $res = $this->db->LoadResult(); } return $res; } /** * Check how many Two Factor Plugins are enabled. */ public function getTwoFactorPluginsEnabled() { // Session GC $this->db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `folder` = 'twofactorauth' and enabled = 1"); return $this->db->LoadResult(); } /** * Load filters from com_config without using a helper. */ public function getAdminFilterFixed() { $this->db->setQuery("select params from #__extensions where element = 'com_config'"); $params = json_decode($this->db->LoadResult()); if ('NONE' == $params->filters->{7}->filter_type) { return 0; } if ('BL' == $params->filters->{7}->filter_type) { return 1; } else { return 2; } } /** * Load filters from com_config without using a helper. */ public function getUserFilterFixed() { $this->db->setQuery("select params from #__extensions where element = 'com_config'"); $params = json_decode($this->db->LoadResult()); if ('NH' != @$params->filters->{1}->filter_type) { return 0; } if ('NH' != @$params->filters->{2}->filter_type) { return 0; } if ('NH' != @$params->filters->{9}->filter_type) { return 0; } return 1; } /** * Load sendpassword from params from com_users without using a helper. */ public function getPlaintextpasswordsFixed() { $this->db->setQuery("select params from #__extensions where element = 'com_users'"); $params = json_decode($this->db->LoadResult()); return 1 - $params->sendpassword; } /** * Load Flash Upload Settings from params from com_media without using a helper. */ public function getUploadsettingsfixed() { $this->db->setQuery("select params from #__extensions where element = 'com_media'"); $res = $this->db->LoadResult(); if (! $res) { return 0; } $params = json_decode($res); if (! $params || ! property_exists($params, 'upload_extensions')) { return 0; } if ( ! preg_match('/swf/ism', $params->upload_extensions) && ! preg_match('/application\/x-shockwave-flash/ism', $params->upload_mime) ) { return 1; } else { return 0; } } /** * Get the configuration of the google recaptcha plugin and global config. */ private function getCaptchaDetails() { $this->db->setQuery( "SELECT count(*) FROM #__extensions WHERE (name ='plg_captcha_recaptcha' or name = 'plg_captcha_recaptcha_invisible') and enabled = 1" ); return 0 != $this->db->loadResult() ? 1 : 0; } public function getData() { return $this->_data; } private function hasUpdatesAvailable() { set_time_limit(60); ob_start(); require 'bfUpdates.php'; $upCheck = new bfUpdates($this->db); $extensionupdatesavailable = $upCheck->getUpdates(true); // remove any stray output echo ' '; // must have something to clean else warning occurs @ob_clean(); return $extensionupdatesavailable; } } $data = new bfSnapshot(); bfEncrypt::reply(bfReply::SUCCESS, $data->getData());