From 34fd581ea2b3e77eb06b71ea462d0272cd7265b5 Mon Sep 17 00:00:00 2001 From: Catalin Novgorodschi Date: Fri, 14 Aug 2026 13:27:22 +0300 Subject: [PATCH] Load instance bootstrap before Admin session --- Admin/admin.php | 912 +----------------------------------------------- 1 file changed, 7 insertions(+), 905 deletions(-) diff --git a/Admin/admin.php b/Admin/admin.php index f7ce709b..1874f540 100644 --- a/Admin/admin.php +++ b/Admin/admin.php @@ -17,6 +17,10 @@ ## ## ################################################################################# +// Multi-instance bootstrap MUST run before session_start() so each world gets +// its own session name and its own configuration before Admin loads the DB. +require_once __DIR__ . "/../GameEngine/Instance/Bootstrap.php"; + // ─── SESSION ───────────────────────────────────────────────────────────────── // Harden session cookie before session_start() — has no effect after. if (session_status() === PHP_SESSION_NONE) { @@ -36,6 +40,8 @@ if (session_status() === PHP_SESSION_NONE) { include_once("../GameEngine/Admin/csrf.php"); // ─── CORE INCLUDES ─────────────────────────────────────────────────────────── +// Bootstrap already selected the authoritative instance config. Keep this +// include for backwards compatibility with the existing Admin bootstrap. include_once("../GameEngine/config.php"); include_once("../GameEngine/Database.php"); require_once __DIR__ . "/../GameEngine/Lang/loader.php"; @@ -78,909 +84,5 @@ function e(string $value): string { // decode first prevents &#39; / ' double encoding return htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), - ENT_QUOTES | ENT_SUBSTITUTE, - 'UTF-8' - ); + ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } -/** - * Whitelist-validate the ?p= parameter. - * Returns the validated page string, or '' if not in the whitelist. - * - * SECURITY: This is the primary defence against path-traversal in the - * include('Templates/'.$p.'.tpl') call below. Only values present in this - * array are ever passed to include(). - */ -function admin_validated_page(string $raw): string -{ - static $whitelist = [ - 'server_info', 'online', 'notregistered', 'inactive', 'report', - 'message', 'massmessage', 'sysmessage', 'map', 'map_tile', 'natars', - 'search', 'ban', 'maintenance', 'cleanban', 'gold', 'usergold', - 'maintenenceResetGold', 'delmedal', 'delallymedal', 'givePlus', - 'maintenenceResetPlus', 'givePlusRes', 'maintenenceResetPlusBonus', - 'addUsers', 'users', 'admin_log', 'config', 'debug_log', - 'editServerSet', 'editPlusSet', 'editLogSet', 'editNewsboxSet', - 'editCronSet', - 'editExtraSet', 'editAdminInfo', 'resetServer', 'player', 'editUser', - 'deletion', 'Newmessage', 'editPlus', 'editSitter', 'editPassword', - 'editProtection', 'editOverall', - 'editWeek', 'userlogin', 'userillegallog', 'editHero', 'editHeroT4', 'editAdditional', - 'village', 'editResources', 'addTroops', 'addABTroops', 'editVillage', - 'villagelog', 'techlog', 'msg', - 'alliance', 'editAli', 'delAli','editNewFunctions', - 'multiacc', - 'pushprot', - 'blockReg', - 'heatmap', - 'goldShop', - 'questEditor', - ]; - - return in_array($raw, $whitelist, true) ? $raw : ''; -} - -// CSRF helpers — csrf_token() / csrf_field() / csrf_verify() — are defined in -// GameEngine/Admin/csrf.php (included above), shared with the admin Mods. - -/** - * Look up a user row by ID using a prepared statement. - * Replaces the two raw mysqli_query() calls for userlogin / userillegallog. - * - * Returns the associative row, or null on failure / not found. - */ -function admin_get_user_by_id(int $uid): ?array -{ - $link = $GLOBALS['link']; - $stmt = mysqli_prepare($link, "SELECT * FROM `" . TB_PREFIX . "users` WHERE `id` = ?"); - if (!$stmt) { - return null; - } - mysqli_stmt_bind_param($stmt, 'i', $uid); - mysqli_stmt_execute($stmt); - $result = mysqli_stmt_get_result($stmt); - $row = $result ? mysqli_fetch_assoc($result) : null; - mysqli_stmt_close($stmt); - return $row ?: null; -} - -// ─── PAGE ROUTING ───────────────────────────────────────────────────────────── -// Read and whitelist the ?p= parameter once; all branching below uses $page. -$rawPage = isset($_GET['p']) ? trim((string)$_GET['p']) : ''; -$page = admin_validated_page($rawPage); - -$subpage = 'Login'; -$not_include_mootools_js = false; - -if ($page !== '') { - switch ($page) { - - // ── Simple label-only pages ────────────────────────────────────────── - case 'server_info': - $subpage = ADMIN_SERVER_INFO; - break; - - case 'online': - $subpage = ADMIN_ONLINE_USERS; - break; - - case 'notregistered': - $subpage = ADMIN_PLAYERS_NOT_ACTIVATED; - break; - - case 'inactive': - $subpage = ADMIN_PLAYERS_INACTIVATE; - break; - - case 'report': - $subpage = ADMIN_PLAYERS_REPORT; - break; - - case 'message': - // NOTE: original code had this case duplicated (second occurrence - // overrode with 'Search IGMs/Reports'). The first definition - // ('Players Message') is intentional for the ?p=message route. - // The 'Search IGMs/Reports' label belongs to ?p=search sub-section - // which is already covered by the search template include logic. - $subpage = ADMIN_PLAYERS_MESSAGE; - break; - - case 'msg': - $subpage = ADMIN_SEARCH_IGMS_REPORTS; - break; - - case 'multiacc': - $subpage = ADMIN_MULTI_ACCOUNT_DETECTION; - break; - - case 'pushprot': - $subpage = ADMIN_PUSH_PROTECTION; - break; - - case 'blockReg': - $subpage = ADMIN_REGISTRATION_BLOCKLIST; - break; - - case 'heatmap': - $subpage = ADMIN_WORLD_MAP_HEATMAP; - break; - - case 'goldShop': - $subpage = ADMIN_GOLD_SHOP_PROMO_CODES; - break; - - case 'questEditor': - $subpage = ADMIN_QUEST_EDITOR; - break; - - case 'massmessage': - $subpage = ADMIN_MASS_MESSAGE; - break; - - case 'sysmessage': - $subpage = ADMIN_SYSTEM_MESSAGE; - break; - - case 'map': - $subpage = ADMIN_MAP; - break; - - case 'map_tile': - $subpage = 'Map Tile'; - $not_include_mootools_js = true; - break; - - case 'natars': - $subpage = ADMIN_NATARS_MANAGEMENT; - break; - - case 'search': - $subpage = ADMIN_GENERAL_SEARCH; - break; - - case 'ban': - $subpage = ADMIN_BAN_UNBAN_PLAYERS; - break; - - case 'maintenance': - $subpage = ADMIN_SERVER_MAINTENANCE; - break; - - case 'cleanban': - $subpage = ADMIN_CLEAN_BANLIST_DATA; - break; - - case 'gold': - $subpage = ADMIN_GIVE_ALL_FREE_GOLD; - break; - - case 'usergold': - $subpage = ADMIN_GIVE_FREE_GOLD_TO_SPECIFIC_USER; - break; - - case 'maintenenceResetGold': - $subpage = ADMIN_RESET_GOLD; - break; - - case 'delmedal': - $subpage = ADMIN_DELETE_PLAYER_MEDALS; - break; - - case 'delallymedal': - $subpage = ADMIN_DELETE_ALLY_MEDALS; - break; - - case 'givePlus': - $subpage = ADMIN_GIVE_ALL_PLUS; - break; - - case 'maintenenceResetPlus': - $subpage = ADMIN_RESET_PLUS; - break; - - case 'givePlusRes': - $subpage = ADMIN_GIVE_ALL_RES_BONUS; - break; - - case 'maintenenceResetPlusBonus': - $subpage = ADMIN_RESET_RES_BONUS; - break; - - case 'addUsers': - $subpage = ADMIN_CREATE_USERS; - break; - - case 'users': - $subpage = ADMIN_USERS_LIST; - break; - - case 'admin_log': - $subpage = ADMIN_ADMIN_LOG; - break; - - case 'config': - $subpage = ADMIN_SERVER_SETTINGS; - break; - - case 'debug_log': - $subpage = ADMIN_DEBUG_ERROR_LOG; - break; - - case 'editServerSet': - $subpage = ADMIN_SERVER_CONFIGURATION; - break; - - case 'editCronSet': - $subpage = ADMIN_CRON_AUTOMATION; - break; - - case 'editPlusSet': - $subpage = ADMIN_PLUS_SETTINGS; - break; - - case 'editLogSet': - $subpage = ADMIN_LOG_SETTINGS; - break; - - case 'editNewsboxSet': - $subpage = ADMIN_NEWSBOX_SETTINGS; - break; - - case 'editNewFunctions': - $subpage = ADMIN_NEW_FUNCTIONS_SETTINGS; - break; - - case 'editExtraSet': - $subpage = ADMIN_EXTRA_SETTINGS; - break; - - case 'editAdminInfo': - $subpage = ADMIN_EDIT_ADMIN_INFORMATION; - break; - - case 'resetServer': - $subpage = ADMIN_SERVER_RESETTING; - break; - - // ── User-context pages (require a valid ?uid=) ─────────────────────── - case 'player': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $displayarray = $database->getUserArray($uid, 1); - $user = $displayarray; - $subpage = 'Player Details (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_PLAYER_DETAILS . ' (' . ADMIN_NO_PLAYER . ')'; - } - break; - - case 'editUser': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_PLAYER . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_PLAYER . ' (' . ADMIN_NO_PLAYER . ')'; - } - break; - - case 'deletion': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_DELETE_PLAYER . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_DELETE_PLAYER . ' (' . ADMIN_NO_PLAYER . ')'; - } - break; - - case 'Newmessage': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_COMPOSE_MESSAGE . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_COMPOSE_MESSAGE; - } - break; - - case 'editPlus': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_PLUS_RESOURCES . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_PLUS_RESOURCES; - } - break; - - case 'editSitter': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_SITTERS . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_SITTERS; - } - break; - - case 'editPassword': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_PASSWORD . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_PASSWORD; - } - break; - - case 'editProtection': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_PROTECTION . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_PROTECTION; - } - break; - - case 'editOverall': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_OFF_DEF . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_OFF_DEF; - } - break; - - case 'editWeek': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_WEEKLY_OFF_DEF . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_WEEKLY_OFF_DEF; - } - break; - - case 'userlogin': - // SECURITY FIX: was raw mysqli_query with direct $_GET interpolation. - // Now uses admin_get_user_by_id() which internally uses a prepared statement. - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $player = admin_get_user_by_id($uid); - $subpage = $player - ? 'User Logins (' . e($player['username']) . ')' - : 'User Logins (player not found)'; - } else { - $subpage = ADMIN_USER_LOGINS . ' (' . ADMIN_NO_PLAYER . ')'; - } - break; - - case 'userillegallog': - // SECURITY FIX: same as userlogin above. - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $player = admin_get_user_by_id($uid); - $subpage = $player - ? 'User Illegals Log (' . e($player['username']) . ')' - : 'User Illegals Log (player not found)'; - } else { - $subpage = ADMIN_USER_ILLEGALS_LOG . ' (' . ADMIN_NO_PLAYER . ')'; - } - break; - - case 'editHero': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_HERO . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_HERO; - } - break; - - case 'editHeroT4': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_T4_HERO_CONTROLS . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_T4_HERO_CONTROLS; - } - break; - - case 'editAdditional': - $uid = admin_input_id($_GET, 'uid'); - if ($uid !== null) { - $user = $database->getUserArray($uid, 1); - $subpage = ADMIN_EDIT_ADDITIONAL_INFO . ' (' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_ADDITIONAL_INFO; - } - break; - - // ── Village-context pages (require a valid ?did=) ──────────────────── - case 'village': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - if ($village) { - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_EDIT_VILLAGE . ' (' . e($village['name']) . ' » ' . e($user['username'] ?? '?') . ')'; - } else { - $subpage = ADMIN_EDIT_VILLAGE . $did . ' not found)'; - $village = null; - } - } else { - $subpage = ADMIN_EDIT_VILLAGE . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - - case 'editResources': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - if ($village) { - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_EDIT_RESOURCES . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; - } else { - // BUGFIX: original used $did which was only set in 'village' case, - // causing an undefined variable notice here. Now always defined above. - $subpage = ADMIN_EDIT_RESOURCES . $did . ' not found)'; - $village = null; - } - } else { - $subpage = ADMIN_EDIT_RESOURCES . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - - case 'addTroops': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_EDIT_TROOPS . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_TROOPS . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - - case 'addABTroops': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_UPGRADE_TROOPS . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_UPGRADE_TROOPS . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - - case 'editVillage': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_EDIT_VILLAGE . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_EDIT_VILLAGE . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - - // ── Alliance-context pages (require a valid ?aid=) ─────────────────── - case 'alliance': - $aid = admin_input_id($_GET, 'aid'); - if ($aid !== null) { - $alidata = $database->getAlliance($aid); - $subpage = $alidata ? 'Alliance (' . e($alidata['tag']) . ')' : 'Alliance (ID ' . $aid . ' not found)'; - } else { - $subpage = ADMIN_ALLIANCE; - } - break; - - case 'editAli': - $aid = admin_input_id($_GET, 'aid'); - if ($aid !== null) { - $alidata = $database->getAlliance($aid); - $subpage = $alidata ? 'Edit Alliance (' . e($alidata['tag']) . ')' : 'Edit Alliance'; - } else { - $subpage = ADMIN_EDIT_ALLIANCE; - } - break; - - case 'delAli': - $aid = admin_input_id($_GET, 'aid'); - if ($aid !== null) { - $alidata = $database->getAlliance($aid); - $subpage = $alidata ? 'Delete Alliance (' . e($alidata['tag']) . ')' : 'Delete Alliance'; - } else { - $subpage = ADMIN_DELETE_ALLIANCE; - } - break; - - case 'villagelog': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_BUILD_LOG . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_BUILD_LOG . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - - case 'techlog': - $did = admin_input_id($_GET, 'did'); - if ($did !== null) { - $village = $database->getVillage($did); - $user = $database->getUserArray($village['owner'], 1); - $subpage = ADMIN_RESEARCH_LOG . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; - } else { - $subpage = ADMIN_RESEARCH_LOG . ' (' . ADMIN_NO_VILLAGE . ')'; - } - break; - } -} - -// ─── SECURITY HEADERS ───────────────────────────────────────────────────────── -// Send headers before ANY output. These protect against common web attacks. -// Intentionally NOT using header_remove() to avoid stripping headers set by -// other TravianZ bootstrap code — we only add, never remove. -if (!headers_sent()) { - header('X-Frame-Options: DENY'); - header('X-Content-Type-Options: nosniff'); - header('Referrer-Policy: strict-origin-when-cross-origin'); - header("Content-Security-Policy: default-src 'self'; " - . "script-src 'self' 'unsafe-inline' https://ajax.googleapis.com; " - . "style-src 'self' 'unsafe-inline'; " - . "img-src 'self' data:; " - . "font-src 'self'; " - . "connect-src 'self'; " - . "frame-ancestors 'none';"); -} - -?> - - - - Admin Panel - <?php echo e($subpage); ?> - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-

TravianZ Admin Panel

- • v14.06.2026 -
-
-
- CheckLogin()) { ?> - getUserField($_SESSION['id'], 'username', 0); - $adminAccess = $database->getUserField($_SESSION['id'], 'access', 0); - $rank = $adminAccess == 9 ? 'Admin' : ($adminAccess == 8 ? 'MH' : 'User'); - ?> - Logged: - () - - - Not Logged in - -
-
-
- -
- -
-
-
- -

- - TravianZ Admin Panel - -

- - CheckLogin()) { ?> - - - - - - - - - - - -
- -
-
- CheckLogin()) { - // CSRF: verifică token-ul pe ORICE request POST înainte de a - // include orice template. GET-urile nu modifică starea serverului - // (sunt doar citiri), deci nu necesită verificare CSRF. - if ($_POST) { - csrf_verify(); - } - - if ($_POST || $_GET) { - // SECURITY: $page is already whitelist-validated above. - // Direct string concat with include() is now safe. - if ($page !== '' && $page !== 'search') { - $filename = 'Templates/' . $page . '.tpl'; - if (file_exists($filename)) { - include($filename); - } else { - include('Templates/404.tpl'); - } - } else { - include('Templates/search.tpl'); - } - - // Handle POST-based results template. - $postPage = isset($_POST['p']) ? trim((string)$_POST['p']) : ''; - $postSub = isset($_POST['s']) ? trim((string)$_POST['s']) : ''; - $postPage = admin_validated_page($postPage); // whitelist POST too - if ($postPage !== '' && $postSub !== '') { - $filename = 'Templates/results_' . $postPage . '.tpl'; - if (file_exists($filename)) { - include($filename); - } else { - include('Templates/404.tpl'); - } - } - } else { - include('Templates/home.tpl'); - } - } else { - include('Templates/login.tpl'); - } - ?> -
-
- -
-
-
-
- - -