diff --git a/Admin/admin.php b/Admin/admin.php index 1874f540..e239f2f7 100644 --- a/Admin/admin.php +++ b/Admin/admin.php @@ -40,13 +40,11 @@ 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"; tz_load_language(LANG); -include_once("../GameEngine/Admin/database.php"); +include_once __DIR__ . "/../GameEngine/Admin/database.php"; // Helperul care descopera pachetele grafice din gpack/ (tz_available_gpacks). // Sabloanele panoului il folosesc pentru selectorul de pachet; fara acest @@ -84,5 +82,909 @@ 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'); + 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';"); +} + +?> + +
+ +
+
+
+
+