fix(admin): wire CSRF token into admin.php-routed forms [#139] (#244)

This commit is contained in:
Ferywir
2026-06-20 05:44:12 +02:00
committed by GitHub
parent 1ce1003153
commit b7e943ea90
11 changed files with 79 additions and 43 deletions
+1
View File
@@ -57,6 +57,7 @@
</style>
<form method="post" action="admin.php" style="margin:0">
<?php echo csrf_field(); ?>
<input name="action" type="hidden" value="addVillage">
<input name="uid" type="hidden" value="<?php echo $user['id'];?>">
<table class="punish-box">
+2
View File
@@ -121,6 +121,7 @@ $banHistory = mysqli_query($database->dblink,"SELECT * FROM ".TB_PREFIX."banlist
Add New Ban
</h3>
<form method="post" class="ban-form">
<?php echo csrf_field(); ?>
<input type="hidden" name="action" value="addBan">
<div class="row">
<input type="number" name="uid" placeholder="User ID" required>
@@ -174,6 +175,7 @@ $banHistory = mysqli_query($database->dblink,"SELECT * FROM ".TB_PREFIX."banlist
Ban IP Address
</h3>
<form method="post" class="ban-form">
<?php echo csrf_field(); ?>
<input type="hidden" name="action" value="addIpBan">
<div class="row">
<input type="text" name="ip" placeholder="IPv4 or IPv6" required>
+1
View File
@@ -60,6 +60,7 @@ if($maint['started_by'] > 0){
<?php if(!empty($error)) echo '<div class="msg">'.$error.'</div>'; ?>
<form method="POST">
<?php echo csrf_field(); ?>
<div class="maint-card">
<div class="maint-head">Server Maintenance</div>
<div class="maint-status <?= $maint['active'] ? 'on' : 'off' ?>">
+1
View File
@@ -130,6 +130,7 @@ body{margin:0;background:#f1f5f9;font-family:system-ui,-apple-system,Segoe UI,Ro
</div>
<form id="show" action="admin.php?p=map" method="POST" class="map-filters">
<?php echo csrf_field(); ?>
<div class="filter-group">
<input id="show1" name="show1" type="checkbox" <?php echo $check1;?> value="1">
<label for="show1">Players</label>
+2 -1
View File
@@ -69,7 +69,7 @@ function village_type_by_fieldtype_id($id){
function gen_map_tiles_select_list_form($is_ocuppied,$is_oasis,$oasis_type,$fieldtype,$x,$y){
$not_ocuppied =!$is_ocuppied;
$html = '<div class="tile-edit"><b>New Map Tile Type</b><form method="post" action="?p=map_tile&do_save"><select name="new_field_type" class="tile-select">';
$html = '<div class="tile-edit"><b>New Map Tile Type</b><form method="post" action="?p=map_tile&do_save">' . csrf_field() . '<select name="new_field_type" class="tile-select">';
if($is_oasis){
for($i=1;$i<13;$i++){ $sel=($i==$oasis_type)?' selected':''; $html.='<option value="'.$i.'_0"'.$sel.'>['.$i.'] Oasis '.oasis_type_by_id($i).'</option>'; }
if($not_ocuppied){ for($i=1;$i<13;$i++){ $html.='<option value="'.$i.'_1">['.$i.'] Valley '.village_type_by_fieldtype_id($i).'</option>'; } }
@@ -158,6 +158,7 @@ elseif(isset($_GET['do_get']) && isset($_POST['x'])){
<div class="tile-wrapper">
<form class="tile-form" method="post" action="?p=map_tile&do_get">
<?php echo csrf_field(); ?>
<div class="coord-wrap">
<div class="coord-item">
<b>X</b>
+2
View File
@@ -57,6 +57,7 @@ $deletedArtifacts = $database->getDeletedArtifacts();
<h2>🏰 WW Villages</h2>
<div class="body">
<form method="post" action="../Admin/admin.php?action=addWWVillages">
<?php echo csrf_field(); ?>
<table class="nat-table">
<tr><th>Number</th><th>Player ID</th><th></th></tr>
<tr>
@@ -74,6 +75,7 @@ $deletedArtifacts = $database->getDeletedArtifacts();
<h2>✨ Add Artifacts</h2>
<div class="body">
<form method="post" action="../Admin/admin.php?action=addArtifacts">
<?php echo csrf_field(); ?>
<table class="nat-table">
<tr><th>Icon</th><th>Type</th><th>Qty</th><th>Player</th><th></th></tr>
<tr>
+3
View File
@@ -57,6 +57,7 @@ $active = $admin->getUserActive();
<tbody>
<tr><td>
<form method="post" action="admin.php" style="margin:0">
<?php echo csrf_field(); ?>
<input type="hidden" name="action" value="punish">
<input type="hidden" name="uid" value="<?php echo $user['id'];?>">
<input type="hidden" name="admid" value="<?php echo $_SESSION['id']; ?>">
@@ -80,6 +81,7 @@ $active = $admin->getUserActive();
<tr><td>
<form method="post" action="admin.php" style="margin:0">
<?php echo csrf_field(); ?>
<input type="hidden" name="action" value="punish">
<input type="hidden" name="uid" value="<?php echo $user['id'];?>">
<input type="hidden" name="admid" value="<?php echo $_SESSION['id']; ?>">
@@ -92,6 +94,7 @@ $active = $admin->getUserActive();
<tr><td>
<form method="post" action="admin.php" style="margin:0">
<?php echo csrf_field(); ?>
<input type="hidden" name="action" value="punish">
<input type="hidden" name="uid" value="<?php echo $user['id'];?>">
<input type="hidden" name="admid" value="<?php echo $_SESSION['id']; ?>">
+1
View File
@@ -62,6 +62,7 @@ $search = stripslashes($_POST['s']?? '');
<div class="search-card">
<form action="" method="post" class="search-form" id="searchForm">
<?php echo csrf_field(); ?>
<select name="p" id="searchType">
<?php foreach($types as $k=>$v){?>
<option value="<?php echo $k;?>" <?php echo $current==$k?'selected':'';?>><?php echo $v[1].' '.$v[0];?></option>
+1
View File
@@ -43,6 +43,7 @@ foreach($varray as $vil) $totalpop += $vil['pop'];
</style>
<form action="" method="post">
<?php echo csrf_field(); ?>
<table id="member" class="search-modern">
<thead>
<tr>
+6 -42
View File
@@ -30,12 +30,10 @@ if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// ─── CSRF TOKEN ───────────────────────────────────────────────────────────────
// Generat o singură dată per sesiune și stocat în $_SESSION.
// Toate request-urile POST trebuie să trimită acest token în câmpul _csrf_token.
if (empty($_SESSION['_csrf_token'])) {
$_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
}
// ─── 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");
@@ -97,42 +95,8 @@ function admin_validated_page(string $raw): string
return in_array($raw, $whitelist, true) ? $raw : '';
}
/**
* Returnează token-ul CSRF curent ca string hex.
* Folosit pentru injectare în câmpuri ascunse sau header-e AJAX.
*/
function csrf_token(): string
{
return $_SESSION['_csrf_token'] ?? '';
}
/**
* Emite un <input type="hidden"> gata de pus în orice <form> POST din template-uri.
* Exemplu de utilizare în .tpl: <?php echo csrf_field(); ?>
*/
function csrf_field(): string
{
return '<input type="hidden" name="_csrf_token" value="' . htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') . '">';
}
/**
* Verifică token-ul CSRF dintr-un request POST.
* Oprește execuția cu HTTP 403 dacă token-ul lipsește sau nu se potrivește.
* Apelată automat pe orice $_POST — nu trebuie apelată manual în template-uri.
*
* Folosim hash_equals() în loc de === pentru a preveni timing attacks.
*/
function csrf_verify(): void
{
$submitted = isset($_POST['_csrf_token']) ? (string)$_POST['_csrf_token'] : '';
$stored = csrf_token();
if ($stored === '' || !hash_equals($stored, $submitted)) {
http_response_code(403);
// Mesaj generic — nu dezvăluie detalii despre mecanism
die('<h1>403 Forbidden</h1><p>Invalid or missing security token. Please go back and try again.</p>');
}
}
// 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.
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* CSRF protection helpers (issue #139).
*
* Shared by Admin/admin.php and the admin Mods (GameEngine/Admin/Mods/*.php),
* which are POSTed to directly and therefore cannot rely on admin.php's
* formerly-inline helpers. Include this file after the session is started:
*
* - csrf_token(): current per-session token (hex string)
* - csrf_field(): hidden <input> to drop into any POST <form>
* - csrf_verify(): abort with HTTP 403 if the POSTed token is missing/invalid
*/
// Defensive: callers normally start the session themselves, but make sure we
// have one to store the token in.
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
// Generate the token once per session.
if (empty($_SESSION['_csrf_token'])) {
$_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
}
if (!function_exists('csrf_token')) {
/**
* Return the current CSRF token as a hex string.
*/
function csrf_token(): string
{
return $_SESSION['_csrf_token'] ?? '';
}
/**
* Emit a ready-to-use hidden <input> for any POST <form> in a template.
* Usage in a .tpl: <?php echo csrf_field(); ?>
*/
function csrf_field(): string
{
return '<input type="hidden" name="_csrf_token" value="' . htmlspecialchars(csrf_token(), ENT_QUOTES, 'UTF-8') . '">';
}
/**
* Verify the CSRF token of a POST request.
* Stops execution with HTTP 403 if the token is missing or does not match.
* Uses hash_equals() instead of === to prevent timing attacks.
*/
function csrf_verify(): void
{
$submitted = isset($_POST['_csrf_token']) ? (string)$_POST['_csrf_token'] : '';
$stored = csrf_token();
if ($stored === '' || !hash_equals($stored, $submitted)) {
http_response_code(403);
// Generic message — does not reveal details about the mechanism.
die('<h1>403 Forbidden</h1><p>Invalid or missing security token. Please go back and try again.</p>');
}
}
}