0, 'path' => '/', 'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'httponly' => true, 'samesite' => 'Strict', ]); session_start(); } // ─── CSRF PROTECTION ────────────────────────────────────────────────────────── // Token init + csrf_token()/csrf_field()/csrf_verify() helpers, shared with the // admin Mods (which are POSTed to directly). See GameEngine/Admin/csrf.php. include_once("../GameEngine/Admin/csrf.php"); // ─── CORE INCLUDES ─────────────────────────────────────────────────────────── include_once("../GameEngine/config.php"); include_once("../GameEngine/Database.php"); include_once("../GameEngine/Lang/" . LANG . ".php"); include_once("../GameEngine/Admin/database.php"); include_once("../GameEngine/Data/buidata.php"); include_once("../GameEngine/Artifacts.php"); include_once("../GameEngine/MultiAccount.php"); include_once("../GameEngine/PushProtection.php"); include_once("../GameEngine/RegBlock.php"); include_once("../GameEngine/Heatmap.php"); include_once("../GameEngine/GoldShop.php"); include_once("../GameEngine/QuestConfig.php"); // ─── SECURITY HELPERS ──────────────────────────────────────────────────────── /** * Return a sanitised integer from a superglobal key, or null if missing/invalid. * Replaces direct (int) casts on $_GET inside switch — ensures 0 is treated as * absent (IDs are always >= 1 in TravianZ). */ function admin_input_id(array $source, string $key): ?int { if (!isset($source[$key]) || !ctype_digit((string)$source[$key])) { return null; } $v = (int)$source[$key]; return $v > 0 ? $v : null; } /** * HTML-escape a value for safe output inside HTML attributes or text nodes. */ function e(string $value): string { // decode first prevents ' / ' double encoding return htmlspecialchars(html_entity_decode($value, ENT_QUOTES, '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';"); } ?>