diff --git a/.gitignore b/.gitignore
index dffdf1c..8556044 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
src/xlxd
ambed/ambed
ambedtest/ambedtest
+.DS_Store
diff --git a/dashboard2/changes.txt b/dashboard2/changes.txt
index 68bc80f..17f8c4a 100644
--- a/dashboard2/changes.txt
+++ b/dashboard2/changes.txt
@@ -1,3 +1,28 @@
+xlx db v2.3.8
+
+SECURITY UPDATE - XSS Vulnerability Patches and Security Enhancements
+- "functions.php" added SafeOutput() and SafeOutputAttr() for XSS protection
+ added GenerateCSRFToken() and ValidateCSRFToken() for CSRF protection
+- "index.php" added session_start() for CSRF token support
+ added SafeOutput() to all $_GET['show'] outputs
+ added input whitelist validation for $_GET['show'] parameter
+ changed file permission from 777 to 600 for hash file (security hardening)
+ added SafeOutputAttr() to all meta tag outputs
+ added SafeOutput() to contact email output
+ improved error messages to prevent information disclosure
+- "users.php" added CSRF token validation for all POST requests
+ added CSRF tokens to both filter forms
+ added input validation with regex for callsign filter (alphanumeric, dash, asterisk only)
+ added input validation with regex for module filter (single letter A-Z only)
+ added SafeOutput() and SafeOutputAttr() to all user data outputs
+ added SafeOutput() to all callsign, suffix, via, peer, and module outputs
+- "repeaters.php" added SafeOutput() to all node callsign, suffix, protocol, and module outputs
+ added SafeOutput() to all IP address outputs
+- "peers.php" added SafeOutput() and SafeOutputAttr() to peer name and URL outputs
+ added SafeOutput() to protocol, module, and IP address outputs
+- "reflectors.php" added SafeOutput() and SafeOutputAttr() to reflector name, country, comment, and URL outputs
+- "class.reflector.php" added URL validation in CallHome() method to prevent remote file inclusion attacks
+
xlx db v2.3.1
- "config.inc.php" $CallingHome['InterlinkFile'] added
@@ -16,7 +41,7 @@ xlx db v2.2.2
This version is a major release with voluntary self-registration feature build in.
You need to edit the conf.inc.php to your needs.
-On the first run your personal hash to access the database is place in the server’s /tmp folder.
+On the first run your personal hash to access the database is place in the server�s /tmp folder.
Take care to make a backup of this file because this folder is cleaned up after a server reboot.
This version is a major release
@@ -44,7 +69,7 @@ xlx db v2.1.4
- "class.reflector.php" improved the flag search
- "country.csv" added serveral prefixes
-- "flags" added Puerto Ricco and Åland Islands
+- "flags" added Puerto Ricco and �land Islands
xlx db v2.1.3
diff --git a/dashboard2/index.php b/dashboard2/index.php
index 25fea68..bff4404 100644
--- a/dashboard2/index.php
+++ b/dashboard2/index.php
@@ -1,4 +1,5 @@
');
@fclose($Ressource);
- @exec("chmod 777 " . $CallingHome['HashFile']);
+ @exec("chmod 600 " . $CallingHome['HashFile']);
$CallHomeNow = true;
}
} else {
@@ -79,12 +80,11 @@ if ($CallingHome['Active']) {
-
-
-
-
-
-
+
+
+
+
+
GetReflectorName(); ?> Reflector Dashboard
@@ -113,8 +113,8 @@ if ($CallingHome['Active']) {
if (($_SERVER['REQUEST_METHOD'] === 'POST') || isset($_GET['do'])) {
echo '
document.location.href = "./index.php';
- if (isset($_GET['show'])) {
- echo '?show=' . $_GET['show'];
+ if (isset($_GET['show']) && $_GET['show'] !== '') {
+ echo '?show=' . SafeOutput($_GET['show']);
}
echo '";';
} else {
@@ -194,6 +194,15 @@ if ($CallingHome['Active']) {
}
}
+ // Whitelist allowed values
+ if (!isset($_GET['show'])) {
+ $_GET['show'] = '';
+ }
+ $allowed_shows = ['users', 'repeaters', 'liveircddb', 'peers', 'reflectors', ''];
+ if (!in_array($_GET['show'], $allowed_shows, true)) {
+ $_GET['show'] = '';
+ }
+
switch ($_GET['show']) {
case 'users' :
require_once("./pgs/users.php");
@@ -222,7 +231,7 @@ if ($CallingHome['Active']) {
diff --git a/dashboard2/pgs/class.reflector.php b/dashboard2/pgs/class.reflector.php
index 2d29483..db07d87 100644
--- a/dashboard2/pgs/class.reflector.php
+++ b/dashboard2/pgs/class.reflector.php
@@ -419,6 +419,10 @@ class xReflector {
}
public function CallHome() {
+ // Validate URL before making request
+ if (!filter_var($this->CallingHomeServerURL, FILTER_VALIDATE_URL)) {
+ return false;
+ }
$xml = '
CallingHome '.$this->ReflectorXML.$this->InterlinkXML;
$p = @stream_context_create(array('http' => array('header' => "Content-type: application/x-www-form-urlencoded\r\n",
diff --git a/dashboard2/pgs/config.inc.php b/dashboard2/pgs/config.inc.php
index b17f3fb..3b83f7a 100644
--- a/dashboard2/pgs/config.inc.php
+++ b/dashboard2/pgs/config.inc.php
@@ -16,7 +16,7 @@ $PageOptions = array();
$PageOptions['ContactEmail'] = 'your_email'; // Support E-Mail address
-$PageOptions['DashboardVersion'] = '2.3.7'; // Dashboard Version
+$PageOptions['DashboardVersion'] = '2.3.8'; // Dashboard Version
$PageOptions['PageRefreshActive'] = true; // Activate automatic refresh
$PageOptions['PageRefreshDelay'] = '10000'; // Page refresh time in miliseconds
diff --git a/dashboard2/pgs/country.csv b/dashboard2/pgs/country.csv
old mode 100755
new mode 100644
diff --git a/dashboard2/pgs/functions.php b/dashboard2/pgs/functions.php
index a90af8c..7ce5b08 100644
--- a/dashboard2/pgs/functions.php
+++ b/dashboard2/pgs/functions.php
@@ -59,4 +59,30 @@ function CreateCode ($laenge) {
return $out;
}
+function SafeOutput($string, $encoding = 'UTF-8') {
+ return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, $encoding);
+}
+
+function SafeOutputAttr($string, $encoding = 'UTF-8') {
+ // Extra safe for attributes
+ return htmlspecialchars($string, ENT_QUOTES | ENT_HTML5, $encoding);
+}
+
+function GenerateCSRFToken() {
+ if (!isset($_SESSION)) {
+ session_start();
+ }
+ if (!isset($_SESSION['csrf_token'])) {
+ $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
+ }
+ return $_SESSION['csrf_token'];
+}
+
+function ValidateCSRFToken($token) {
+ if (!isset($_SESSION)) {
+ session_start();
+ }
+ return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
+}
+
?>
diff --git a/dashboard2/pgs/peers.php b/dashboard2/pgs/peers.php
index 1f45c13..27292d7 100644
--- a/dashboard2/pgs/peers.php
+++ b/dashboard2/pgs/peers.php
@@ -44,8 +44,8 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
'.($i+1).' ';
- $Name = $Reflector->Peers[$i]->GetCallSign();
- $URL = '';
+ $Name = $Reflector->Peers[$i]->GetCallSign();
+ $URL = '';
for ($j=1;$jGetElement($Reflectors[$j], "name")) {
@@ -53,15 +53,15 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
}
}
if ($Result && (trim($URL) != "")) {
- echo ''.$Name.' ';
+ echo '' . SafeOutput($Name) . ' ';
} else {
- echo ''.$Name.' ';
+ echo '' . SafeOutput($Name) . ' ';
}
echo '
'.date("d.m.Y H:i", $Reflector->Peers[$i]->GetLastHeardTime()).'
'.FormatSeconds(time()-$Reflector->Peers[$i]->GetConnectTime()).' s
- '.$Reflector->Peers[$i]->GetProtocol().'
- '.$Reflector->Peers[$i]->GetLinkedModule().' ';
+ '.SafeOutput($Reflector->Peers[$i]->GetProtocol()).'
+ '.SafeOutput($Reflector->Peers[$i]->GetLinkedModule()).' ';
if ($PageOptions['PeerPage']['IPModus'] != 'HideIP') {
echo '';
$Bytes = explode(".", $Reflector->Peers[$i]->GetIP());
@@ -70,7 +70,7 @@ for ($i=0;$i<$Reflector->PeerCount();$i++) {
case 'ShowLast1ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[3]; break;
case 'ShowLast2ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[2].'.'.$Bytes[3]; break;
case 'ShowLast3ByteOfIP' : echo $PageOptions['PeerPage']['MasqueradeCharacter'].'.'.$Bytes[1].'.'.$Bytes[2].'.'.$Bytes[3]; break;
- default : echo ''.$Reflector->Peers[$i]->GetIP().' ';
+ default : echo ''.SafeOutput($Reflector->Peers[$i]->GetIP()).' ';
}
}
echo ' ';
diff --git a/dashboard2/pgs/reflectors.php b/dashboard2/pgs/reflectors.php
index f8e1adb..c67214a 100644
--- a/dashboard2/pgs/reflectors.php
+++ b/dashboard2/pgs/reflectors.php
@@ -38,10 +38,10 @@ for ($i=0;$i
'.($i+1).'
- '.$NAME.'
- '.$COUNTRY.'
+ ' . SafeOutput($NAME) . '
+ ' . SafeOutput($COUNTRY) . '
- '.$COMMENT.'
+ ' . SafeOutput($COMMENT) . '
';
}
diff --git a/dashboard2/pgs/repeaters.php b/dashboard2/pgs/repeaters.php
index a7562b9..61dd321 100644
--- a/dashboard2/pgs/repeaters.php
+++ b/dashboard2/pgs/repeaters.php
@@ -30,10 +30,10 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
echo ''.$Name.' ';
}
echo '
- Nodes[$i]->GetSuffix();
- echo '" class="pl" target="_blank">'.$Reflector->Nodes[$i]->GetCallSign();
- if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.$Reflector->Nodes[$i]->GetSuffix(); }
+ Nodes[$i]->GetSuffix());
+ echo '" class="pl" target="_blank">'.SafeOutput($Reflector->Nodes[$i]->GetCallSign());
+ if ($Reflector->Nodes[$i]->GetSuffix() != "") { echo '-'.SafeOutput($Reflector->Nodes[$i]->GetSuffix()); }
echo '
';
if (($Reflector->Nodes[$i]->GetPrefix() == 'REF') || ($Reflector->Nodes[$i]->GetPrefix() == 'XRF')) {
@@ -55,8 +55,8 @@ for ($i=0;$i<$Reflector->NodeCount();$i++) {
echo '
'.date("d.m.Y H:i", $Reflector->Nodes[$i]->GetLastHeardTime()).'
'.FormatSeconds(time()-$Reflector->Nodes[$i]->GetConnectTime()).' s
- '.$Reflector->Nodes[$i]->GetProtocol().'
- '.$Reflector->Nodes[$i]->GetLinkedModule().' ';
+ '.SafeOutput($Reflector->Nodes[$i]->GetProtocol()).'
+ '.SafeOutput($Reflector->Nodes[$i]->GetLinkedModule()).' ';
if ($PageOptions['RepeatersPage']['IPModus'] != 'HideIP') {
echo '
';
diff --git a/dashboard2/pgs/users.php b/dashboard2/pgs/users.php
index bc8f92f..5ace5b2 100644
--- a/dashboard2/pgs/users.php
+++ b/dashboard2/pgs/users.php
@@ -9,6 +9,11 @@ if (!isset($_SESSION['FilterModule'])) {
}
if (isset($_POST['do'])) {
+ // Validate CSRF token
+ if (!isset($_POST['csrf_token']) || !ValidateCSRFToken($_POST['csrf_token'])) {
+ die('CSRF token validation failed');
+ }
+
if ($_POST['do'] == 'SetFilter') {
if (isset($_POST['txtSetCallsignFilter'])) {
@@ -17,12 +22,17 @@ if (isset($_POST['do'])) {
$_SESSION['FilterCallSign'] = null;
}
else {
- $_SESSION['FilterCallSign'] = $_POST['txtSetCallsignFilter'];
- if (strpos($_SESSION['FilterCallSign'], "*") === false) {
- $_SESSION['FilterCallSign'] = "*".$_SESSION['FilterCallSign']."*";
+ // Validate callsign format (alphanumeric, dash, asterisk only)
+ if (preg_match('/^[A-Z0-9\-\*]+$/i', $_POST['txtSetCallsignFilter'])) {
+ $_SESSION['FilterCallSign'] = $_POST['txtSetCallsignFilter'];
+ if (strpos($_SESSION['FilterCallSign'], "*") === false) {
+ $_SESSION['FilterCallSign'] = "*".$_SESSION['FilterCallSign']."*";
+ }
+ } else {
+ // Invalid format, reject it
+ $_SESSION['FilterCallSign'] = null;
}
}
-
}
if (isset($_POST['txtSetModuleFilter'])) {
@@ -31,9 +41,14 @@ if (isset($_POST['do'])) {
$_SESSION['FilterModule'] = null;
}
else {
- $_SESSION['FilterModule'] = $_POST['txtSetModuleFilter'];
+ // Validate module is single letter A-Z
+ if (preg_match('/^[A-Z]$/i', $_POST['txtSetModuleFilter'])) {
+ $_SESSION['FilterModule'] = strtoupper($_POST['txtSetModuleFilter']);
+ } else {
+ // Invalid format, reject it
+ $_SESSION['FilterModule'] = null;
+ }
}
-
}
}
}
@@ -60,7 +75,8 @@ if ($PageOptions['UserPage']['ShowFilter']) {
';
@@ -72,7 +88,8 @@ if ($PageOptions['UserPage']['ShowFilter']) {
@@ -133,16 +150,16 @@ for ($i=0;$i<$Reflector->StationCount();$i++) {
echo '' . $Name . ' ';
}
echo '
- ' . $Reflector->Stations[$i]->GetCallsignOnly() . '
- ' . $Reflector->Stations[$i]->GetSuffix() . '
-
- ' . $Reflector->Stations[$i]->GetVia();
+ ' . SafeOutput($Reflector->Stations[$i]->GetCallsignOnly()) . '
+ ' . SafeOutput($Reflector->Stations[$i]->GetSuffix()) . '
+
+ ' . SafeOutput($Reflector->Stations[$i]->GetVia());
if ($Reflector->Stations[$i]->GetPeer() != $Reflector->GetReflectorName()) {
- echo ' / ' . $Reflector->Stations[$i]->GetPeer();
+ echo ' / ' . SafeOutput($Reflector->Stations[$i]->GetPeer());
}
echo '
' . @date("d.m.Y H:i", $Reflector->Stations[$i]->GetLastHeardTime()) . '
- ' . $Reflector->Stations[$i]->GetModule() . '
+ ' . SafeOutput($Reflector->Stations[$i]->GetModule()) . '
';
}
if ($i == $PageOptions['LastHeardPage']['LimitTo']) {
@@ -192,7 +209,7 @@ for ($i=0;$iGetCallsignAndSuffixByID($Users[$j]);
echo '
- '.$Displayname.'
+ ' . SafeOutput($Displayname) . '
';
$UserCheckedArray[] = $Users[$j];
}