| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 | <?phpdeclare(strict_types=1);namespace PhpMyAdmin;use PhpMyAdmin\ConfigStorage\Relation;use PhpMyAdmin\Dbal\DatabaseName;use PhpMyAdmin\Dbal\TableName;use PhpMyAdmin\Http\Factory\ServerRequestFactory;use PhpMyAdmin\Http\ServerRequest;use PhpMyAdmin\Plugins\AuthenticationPlugin;use PhpMyAdmin\SqlParser\Lexer;use Symfony\Component\DependencyInjection\ContainerInterface;use Webmozart\Assert\Assert;use Webmozart\Assert\InvalidArgumentException;use function __;use function array_pop;use function count;use function date_default_timezone_get;use function date_default_timezone_set;use function define;use function defined;use function explode;use function extension_loaded;use function function_exists;use function hash_equals;use function htmlspecialchars;use function implode;use function ini_get;use function ini_set;use function is_array;use function is_scalar;use function mb_internal_encoding;use function mb_strlen;use function mb_strpos;use function mb_strrpos;use function mb_substr;use function register_shutdown_function;use function session_id;use function str_replace;use function strlen;use function trigger_error;use function urldecode;use const E_USER_ERROR;final class Common{    /**     * Misc stuff and REQUIRED by ALL the scripts.     * MUST be included by every script     *     * Among other things, it contains the advanced authentication work.     *     * Order of sections:     *     * the authentication libraries must be before the connection to db     *     * ... so the required order is:     *     * LABEL_variables_init     *  - initialize some variables always needed     * LABEL_parsing_config_file     *  - parsing of the configuration file     * LABEL_loading_language_file     *  - loading language file     * LABEL_setup_servers     *  - check and setup configured servers     * LABEL_theme_setup     *  - setting up themes     *     * - load of MySQL extension (if necessary)     * - loading of an authentication library     * - db connection     * - authentication work     */    public static function run(): void    {        global $containerBuilder, $errorHandler, $config, $server, $dbi, $request;        global $lang, $cfg, $isConfigLoading, $auth_plugin, $route, $theme;        global $urlParams, $isMinimumCommon, $sql_query, $token_mismatch;        $request = ServerRequestFactory::createFromGlobals();        $route = Routing::getCurrentRoute();        if ($route === '/import-status') {            $isMinimumCommon = true;        }        $containerBuilder = Core::getContainerBuilder();        /** @var ErrorHandler $errorHandler */        $errorHandler = $containerBuilder->get('error_handler');        self::checkRequiredPhpExtensions();        self::configurePhpSettings();        self::cleanupPathInfo();        /* parsing configuration file                  LABEL_parsing_config_file      */        /** @var bool $isConfigLoading Indication for the error handler */        $isConfigLoading = false;        register_shutdown_function([Config::class, 'fatalErrorHandler']);        /**         * Force reading of config file, because we removed sensitive values         * in the previous iteration.         *         * @var Config $config         */        $config = $containerBuilder->get('config');        /**         * include session handling after the globals, to prevent overwriting         */        if (! defined('PMA_NO_SESSION')) {            Session::setUp($config, $errorHandler);        }        $request = Core::populateRequestWithEncryptedQueryParams($request);        /**         * init some variables LABEL_variables_init         */        /**         * holds parameters to be passed to next page         *         * @global array $urlParams         */        $urlParams = [];        $containerBuilder->setParameter('url_params', $urlParams);        self::setGotoAndBackGlobals($containerBuilder, $config);        self::checkTokenRequestParam();        self::setDatabaseAndTableFromRequest($containerBuilder, $request);        /**         * SQL query to be executed         *         * @global string $sql_query         */        $sql_query = '';        if ($request->isPost()) {            $sql_query = $request->getParsedBodyParam('sql_query', '');        }        $containerBuilder->setParameter('sql_query', $sql_query);        //$_REQUEST['set_theme'] // checked later in this file LABEL_theme_setup        //$_REQUEST['server']; // checked later in this file        //$_REQUEST['lang'];   // checked by LABEL_loading_language_file        /* loading language file                       LABEL_loading_language_file    */        /**         * lang detection is done here         */        $language = LanguageManager::getInstance()->selectLanguage();        $language->activate();        /**         * check for errors occurred while loading configuration         * this check is done here after loading language files to present errors in locale         */        $config->checkPermissions();        $config->checkErrors();        self::checkServerConfiguration();        self::checkRequest();        /* setup servers                                       LABEL_setup_servers    */        $config->checkServers();        /**         * current server         *         * @global integer $server         */        $server = $config->selectServer();        $urlParams['server'] = $server;        $containerBuilder->setParameter('server', $server);        $containerBuilder->setParameter('url_params', $urlParams);        $cfg = $config->settings;        /* setup themes                                          LABEL_theme_setup    */        $theme = ThemeManager::initializeTheme();        /** @var DatabaseInterface $dbi */        $dbi = null;        if (isset($isMinimumCommon)) {            $config->loadUserPreferences();            $containerBuilder->set('theme_manager', ThemeManager::getInstance());            Tracker::enable();            return;        }        /**         * save some settings in cookies         *         * @todo should be done in PhpMyAdmin\Config         */        $config->setCookie('pma_lang', (string) $lang);        ThemeManager::getInstance()->setThemeCookie();        $dbi = DatabaseInterface::load();        $containerBuilder->set(DatabaseInterface::class, $dbi);        $containerBuilder->setAlias('dbi', DatabaseInterface::class);        if (! empty($cfg['Server'])) {            $config->getLoginCookieValidityFromCache($server);            $auth_plugin = Plugins::getAuthPlugin();            $auth_plugin->authenticate();            /* Enable LOAD DATA LOCAL INFILE for LDI plugin */            if ($route === '/import' && ($_POST['format'] ?? '') === 'ldi') {                // Switch this before the DB connection is done                // phpcs:disable PSR1.Files.SideEffects                define('PMA_ENABLE_LDI', 1);                // phpcs:enable            }            self::connectToDatabaseServer($dbi, $auth_plugin);            $auth_plugin->rememberCredentials();            $auth_plugin->checkTwoFactor();            /* Log success */            Logging::logUser($cfg['Server']['user']);            if ($dbi->getVersion() < $cfg['MysqlMinVersion']['internal']) {                Core::fatalError(                    __('You should upgrade to %s %s or later.'),                    [                        'MySQL',                        $cfg['MysqlMinVersion']['human'],                    ]                );            }            // Sets the default delimiter (if specified).            $sqlDelimiter = $request->getParam('sql_delimiter', '');            if (strlen($sqlDelimiter) > 0) {                // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps                Lexer::$DEFAULT_DELIMITER = $sqlDelimiter;            }            // TODO: Set SQL modes too.        } else { // end server connecting            $response = ResponseRenderer::getInstance();            $response->getHeader()->disableMenuAndConsole();            $response->getFooter()->setMinimal();        }        $response = ResponseRenderer::getInstance();        /**         * There is no point in even attempting to process         * an ajax request if there is a token mismatch         */        if ($response->isAjax() && $request->isPost() && $token_mismatch) {            $response->setRequestStatus(false);            $response->addJSON(                'message',                Message::error(__('Error: Token mismatch'))            );            exit;        }        Profiling::check($dbi, $response);        $containerBuilder->set('response', ResponseRenderer::getInstance());        // load user preferences        $config->loadUserPreferences();        $containerBuilder->set('theme_manager', ThemeManager::getInstance());        /* Tell tracker that it can actually work */        Tracker::enable();        if (empty($server) || ! isset($cfg['ZeroConf']) || $cfg['ZeroConf'] !== true) {            return;        }        /** @var Relation $relation */        $relation = $containerBuilder->get('relation');        $dbi->postConnectControl($relation);    }    /**     * Checks that required PHP extensions are there.     */    private static function checkRequiredPhpExtensions(): void    {        /**         * Warning about mbstring.         */        if (! function_exists('mb_detect_encoding')) {            Core::warnMissingExtension('mbstring');        }        /**         * Warning about mysqlnd. This does not apply to PMA >= 6.0         */        if (! function_exists('mysqli_stmt_get_result')) {            Core::warnMissingExtension('mysqlnd');        }        /**         * We really need this one!         */        if (! function_exists('preg_replace')) {            Core::warnMissingExtension('pcre', true);        }        /**         * JSON is required in several places.         */        if (! function_exists('json_encode')) {            Core::warnMissingExtension('json', true);        }        /**         * ctype is required for Twig.         */        if (! function_exists('ctype_alpha')) {            Core::warnMissingExtension('ctype', true);        }        /**         * hash is required for cookie authentication.         */        if (function_exists('hash_hmac')) {            return;        }        Core::warnMissingExtension('hash', true);    }    /**     * Applies changes to PHP configuration.     */    private static function configurePhpSettings(): void    {        /**         * Set utf-8 encoding for PHP         */        ini_set('default_charset', 'utf-8');        mb_internal_encoding('utf-8');        /**         * Set precision to sane value, with higher values         * things behave slightly unexpectedly, for example         * round(1.2, 2) returns 1.199999999999999956.         */        ini_set('precision', '14');        /**         * check timezone setting         * this could produce an E_WARNING - but only once,         * if not done here it will produce E_WARNING on every date/time function         */        date_default_timezone_set(@date_default_timezone_get());    }    /**     * PATH_INFO could be compromised if set, so remove it from PHP_SELF     * and provide a clean PHP_SELF here     */    public static function cleanupPathInfo(): void    {        global $PMA_PHP_SELF;        $PMA_PHP_SELF = Core::getenv('PHP_SELF');        if (empty($PMA_PHP_SELF)) {            $PMA_PHP_SELF = urldecode(Core::getenv('REQUEST_URI'));        }        $_PATH_INFO = Core::getenv('PATH_INFO');        if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) {            $question_pos = mb_strpos($PMA_PHP_SELF, '?');            if ($question_pos != false) {                $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos);            }            $path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO);            if ($path_info_pos !== false) {                $path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO));                if ($path_info_part == $_PATH_INFO) {                    $PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos);                }            }        }        $path = [];        foreach (explode('/', $PMA_PHP_SELF) as $part) {            // ignore parts that have no value            if (empty($part) || $part === '.') {                continue;            }            if ($part !== '..') {                // cool, we found a new part                $path[] = $part;            } elseif (count($path) > 0) {                // going back up? sure                array_pop($path);            }            // Here we intentionall ignore case where we go too up            // as there is nothing sane to do        }        $PMA_PHP_SELF = htmlspecialchars('/' . implode('/', $path));    }    private static function setGotoAndBackGlobals(ContainerInterface $container, Config $config): void    {        global $goto, $back, $urlParams;        // Holds page that should be displayed.        $goto = '';        $container->setParameter('goto', $goto);        if (isset($_REQUEST['goto']) && Core::checkPageValidity($_REQUEST['goto'])) {            $goto = $_REQUEST['goto'];            $urlParams['goto'] = $goto;            $container->setParameter('goto', $goto);            $container->setParameter('url_params', $urlParams);        } else {            if ($config->issetCookie('goto')) {                $config->removeCookie('goto');            }            unset($_REQUEST['goto'], $_GET['goto'], $_POST['goto']);        }        if (isset($_REQUEST['back']) && Core::checkPageValidity($_REQUEST['back'])) {            // Returning page.            $back = $_REQUEST['back'];            $container->setParameter('back', $back);            return;        }        if ($config->issetCookie('back')) {            $config->removeCookie('back');        }        unset($_REQUEST['back'], $_GET['back'], $_POST['back']);    }    /**     * Check whether user supplied token is valid, if not remove any possibly     * dangerous stuff from request.     *     * Check for token mismatch only if the Request method is POST.     * GET Requests would never have token and therefore checking     * mis-match does not make sense.     */    public static function checkTokenRequestParam(): void    {        global $token_mismatch, $token_provided;        $token_mismatch = true;        $token_provided = false;        if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {            return;        }        if (isset($_POST['token']) && is_scalar($_POST['token']) && strlen((string) $_POST['token']) > 0) {            $token_provided = true;            $token_mismatch = ! @hash_equals($_SESSION[' PMA_token '], (string) $_POST['token']);        }        if (! $token_mismatch) {            return;        }        // Warn in case the mismatch is result of failed setting of session cookie        if (isset($_POST['set_session']) && $_POST['set_session'] !== session_id()) {            trigger_error(                __(                    'Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.'                ),                E_USER_ERROR            );        }        /**         * We don't allow any POST operation parameters if the token is mismatched         * or is not provided.         */        $allowList = ['ajax_request'];        Sanitize::removeRequestVars($allowList);    }    private static function setDatabaseAndTableFromRequest(        ContainerInterface $containerBuilder,        ServerRequest $request    ): void {        global $db, $table, $urlParams;        try {            $db = DatabaseName::fromValue($request->getParam('db'))->getName();        } catch (InvalidArgumentException $exception) {            $db = '';        }        try {            Assert::stringNotEmpty($db);            $table = TableName::fromValue($request->getParam('table'))->getName();        } catch (InvalidArgumentException $exception) {            $table = '';        }        if (! is_array($urlParams)) {            $urlParams = [];        }        $urlParams['db'] = $db;        $urlParams['table'] = $table;        // If some parameter value includes the % character, you need to escape it by adding        // another % so Symfony doesn't consider it a reference to a parameter name.        $containerBuilder->setParameter('db', str_replace('%', '%%', $db));        $containerBuilder->setParameter('table', str_replace('%', '%%', $table));        $containerBuilder->setParameter('url_params', $urlParams);    }    /**     * Check whether PHP configuration matches our needs.     */    private static function checkServerConfiguration(): void    {        /**         * As we try to handle charsets by ourself, mbstring overloads just         * break it, see bug 1063821.         *         * We specifically use empty here as we are looking for anything else than         * empty value or 0.         */        if (extension_loaded('mbstring') && ! empty(ini_get('mbstring.func_overload'))) {            Core::fatalError(                __(                    'You have enabled mbstring.func_overload in your PHP '                    . 'configuration. This option is incompatible with phpMyAdmin '                    . 'and might cause some data to be corrupted!'                )            );        }        /**         * The ini_set and ini_get functions can be disabled using         * disable_functions but we're relying quite a lot of them.         */        if (function_exists('ini_get') && function_exists('ini_set')) {            return;        }        Core::fatalError(            __(                'The ini_get and/or ini_set functions are disabled in php.ini. phpMyAdmin requires these functions!'            )        );    }    /**     * Checks request and fails with fatal error if something problematic is found     */    private static function checkRequest(): void    {        if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) {            Core::fatalError(__('GLOBALS overwrite attempt'));        }        /**         * protect against possible exploits - there is no need to have so much variables         */        if (count($_REQUEST) <= 1000) {            return;        }        Core::fatalError(__('possible exploit'));    }    private static function connectToDatabaseServer(DatabaseInterface $dbi, AuthenticationPlugin $auth): void    {        global $cfg;        /**         * Try to connect MySQL with the control user profile (will be used to get the privileges list for the current         * user but the true user link must be open after this one so it would be default one for all the scripts).         */        $controlLink = false;        if ($cfg['Server']['controluser'] !== '') {            $controlLink = $dbi->connect(DatabaseInterface::CONNECT_CONTROL);        }        // Connects to the server (validates user's login)        $userLink = $dbi->connect(DatabaseInterface::CONNECT_USER);        if ($userLink === false) {            $auth->showFailure('mysql-denied');        }        if ($controlLink) {            return;        }        /**         * Open separate connection for control queries, this is needed to avoid problems with table locking used in         * main connection and phpMyAdmin issuing queries to configuration storage, which is not locked by that time.         */        $dbi->connect(DatabaseInterface::CONNECT_USER, null, DatabaseInterface::CONNECT_CONTROL);    }}
 |