From e016d78206bf67d0742ce94b0d57e55354bfb5ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 20:39:47 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20address=20code=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20escaping,=20CSRF=20token,=20gmdate,=20regex=20confi?= =?UTF-8?q?g=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/d2560591-832a-44dc-bd98-baf5c3e26cd5 Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com> --- INSTALL_README.md | 6 +++--- check.php | 39 ++++++++++++++++++++++++--------------- install.php | 25 +++++++++++++++++++++---- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/INSTALL_README.md b/INSTALL_README.md index ad262848..6e1c17cb 100644 --- a/INSTALL_README.md +++ b/INSTALL_README.md @@ -114,8 +114,8 @@ If you need to reinstall the panel (e.g. after a migration or reset): When the installer detects **existing tables** in the target database, it: 1. Displays a warning: _"Existing database detected. A backup will be created before reinstall."_ -2. Creates a backup database named `panel_BAK`. - - If `panel_BAK` already exists, a timestamped name is used: `panel_BAK_YYYYMMDD_HHMMSS`. +2. Creates a backup database named `{database_name}_BAK` (e.g. `panel_BAK` if your DB is `panel`). + - If `{database_name}_BAK` already exists, a timestamped name is used: `{database_name}_BAK_YYYYMMDD_HHMMSS`. 3. Copies schema + data for every table into the backup database. 4. Drops all tables from the target database. 5. Proceeds with a fresh install. @@ -126,7 +126,7 @@ When the installer detects **existing tables** in the target database, it: -- Example restore of a single table INSERT INTO panel.gsp_users SELECT * FROM panel_BAK.gsp_users; --- Or restore the full backup DB +-- Or restore the full backup DB (replace panel_BAK with the actual backup name) mysqldump panel_BAK | mysql panel ``` diff --git a/check.php b/check.php index 61da8ec2..c869f00f 100644 --- a/check.php +++ b/check.php @@ -125,7 +125,7 @@ $rows[] = [ 'section' => 'PHP Libraries', 'name' => 'PEAR', 'status' => $pear_path !== false ? 'ok' : 'warning', - 'current' => $pear_path !== false ? h($pear_path) : 'Not found', + 'current' => $pear_path !== false ? $pear_path : 'Not found', 'fix' => $pear_path !== false ? '' : 'sudo apt install php-pear -y', 'notes' => 'Used by some legacy OGP/GSP modules.', ]; @@ -233,24 +233,32 @@ if (function_exists('apache_get_modules')) { // ── Optional DB connectivity test ──────────────────────────────────────────── $config_path = CHECK_BASE . '/includes/config.inc.php'; if (is_readable($config_path)) { - // Extract only the variables we need without executing arbitrary code. - // We use a limited include inside an isolated function scope. - $db_host = $db_port = $db_user = $db_pass = $db_name = null; - (static function () use ($config_path, &$db_host, &$db_port, &$db_user, &$db_pass, &$db_name): void { - @include $config_path; - })(); + // Extract credentials using regex instead of executing the config file, + // to avoid running arbitrary PHP code from the config. + $raw = file_get_contents($config_path); + $cfg = []; + foreach (['db_host', 'db_port', 'db_user', 'db_pass', 'db_name'] as $var) { + if ($raw !== false && preg_match('/\$' . $var . '\s*=\s*"([^"]*)"/', $raw, $m)) { + $cfg[$var] = $m[1]; + } + } - if ($db_host !== null && $db_user !== null && $db_name !== null) { - $db_port_int = (int)($db_port ?? 3306); - $conn = @mysqli_connect($db_host, $db_user, $db_pass ?? '', $db_name, $db_port_int); + $db_host_cfg = $cfg['db_host'] ?? null; + $db_user_cfg = $cfg['db_user'] ?? null; + $db_pass_cfg = $cfg['db_pass'] ?? null; + $db_name_cfg = $cfg['db_name'] ?? null; + $db_port_cfg = isset($cfg['db_port']) ? (int)$cfg['db_port'] : 3306; + + if ($db_host_cfg !== null && $db_user_cfg !== null && $db_name_cfg !== null) { + $conn = @mysqli_connect($db_host_cfg, $db_user_cfg, $db_pass_cfg ?? '', $db_name_cfg, $db_port_cfg); if ($conn) { $db_status = 'ok'; - $db_current = 'Connected to ' . h($db_host) . ':' . $db_port_int . ' / ' . h($db_name); + $db_current = 'Connected to ' . $db_host_cfg . ':' . $db_port_cfg . ' / ' . $db_name_cfg; $db_fix = ''; mysqli_close($conn); } else { $db_status = 'warning'; - $db_current = 'Connection failed — ' . h(mysqli_connect_error()); + $db_current = 'Connection failed — ' . (mysqli_connect_error() ?? 'unknown error'); $db_fix = 'Check credentials in includes/config.inc.php'; } @@ -260,7 +268,7 @@ if (is_readable($config_path)) { 'status' => $db_status, 'current' => $db_current, 'fix' => $db_fix, - 'notes' => 'Host: ' . h($db_host) . ' | Port: ' . $db_port_int . ' | DB: ' . h($db_name) . ' | User: ' . h($db_user ?? ''), + 'notes' => 'Host: ' . $db_host_cfg . ' | Port: ' . $db_port_cfg . ' | DB: ' . $db_name_cfg . ' | User: ' . $db_user_cfg, ]; } else { $rows[] = [ @@ -361,6 +369,7 @@ foreach ($rows as $r) { .note-box { background: #1a2a1a; border: 1px solid #2e5a2e; border-radius: 8px; padding: 14px 18px; margin-bottom: 20px; color: #9ddd9d; font-size: 13px; } .note-box strong { color: #7ddb7d; } footer { text-align: center; color: #555; font-size: 11px; margin-top: 30px; } + @@ -435,7 +444,7 @@ foreach ($rows as $r) { - + @@ -469,7 +478,7 @@ sudo systemctl restart apache2' - + diff --git a/install.php b/install.php index 018b24c4..293dfdc4 100644 --- a/install.php +++ b/install.php @@ -428,7 +428,7 @@ function gsp_backup_existing_db($db, $db_host, $db_user, $db_pass, $db_name, $db } $table_count = count($tables_result); - echo "

⚠ Existing database detected ({$table_count} table(s)). Creating backup before reinstall…

"; + echo "

⚠ Existing database detected (" . htmlspecialchars((string)$table_count, ENT_QUOTES, 'UTF-8') . " table(s)). Creating backup before reinstall…

"; // Determine backup DB name $backup_name = $db_name . '_BAK'; @@ -513,22 +513,38 @@ function gsp_disable_installer() { * To restore the installer: * cp install.php.bak install.php * - * Or via the button below (admin action — currently unprotected; remove this - * file when you no longer need reinstall capability). + * Or via the button below (admin action — requires a one-time restore token). */ +session_start(); + +// Generate a one-time CSRF token if not already set +if (empty($_SESSION['gsp_restore_token'])) { + $_SESSION['gsp_restore_token'] = bin2hex(random_bytes(16)); +} if (isset($_POST['restore_installer'])) { + // Validate CSRF token + $submitted = $_POST['gsp_restore_token'] ?? ''; + if (!hash_equals($_SESSION['gsp_restore_token'], $submitted)) { + http_response_code(403); + die('

Invalid or expired restore token. Please reload the page and try again.

'); + } + $bak = __DIR__ . '/install.php.bak'; if (!is_readable($bak)) { die('

install.php.bak not found. Restore manually.

'); } $content = file_get_contents($bak); - if (file_put_contents(__FILE__, $content) === false) { + if ($content === false || file_put_contents(__FILE__, $content) === false) { die('

Could not overwrite install.php. Check file permissions.

'); } + // Invalidate the token after use + unset($_SESSION['gsp_restore_token']); header('Location: install.php'); exit; } + +$token = htmlspecialchars($_SESSION['gsp_restore_token'], ENT_QUOTES, 'UTF-8'); ?> @@ -552,6 +568,7 @@ code { background:#0d0d22; color:#aaf; padding:2px 8px; border-radius:4px; font-

The GSP installer has been disabled after a successful installation to prevent accidental re-runs.

The original installer is preserved at install.php.bak.

+