Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/img/device_details.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
235 changes: 176 additions & 59 deletions front/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,185 @@

<?php

//------------------------------------------------------------------------------
// check if authenticated
// Be CAREFUL WHEN INCLUDING NEW PHP FILES
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';

$CookieSaveLoginName = 'NetAlertX_SaveLogin';

if ($nax_WebProtection != 'true')
{
header('Location: devices.php');
$_SESSION["login"] = 1;
require_once $_SERVER['DOCUMENT_ROOT'].'/php/server/db.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/php/templates/language/lang.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/php/templates/security.php';

// if (session_status() === PHP_SESSION_NONE) {
// session_start();
// }

session_start();
Comment on lines 8 to 14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Double session_start() will trigger a PHP warning.

security.php (required on line 9) already calls session_start() at its line 49. Calling it again here on line 11 produces a PHP warning in 7.2+. Guard it or remove the duplicate.

Proposed fix
-session_start();
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}
🤖 Prompt for AI Agents
In `@front/index.php` around lines 9 - 11, security.php (required in
front/index.php) already calls session_start(), so the extra session_start() in
front/index.php causes a duplicate session start warning; fix by removing the
redundant session_start() call in front/index.php or guard it with a check like
session_status() !== PHP_SESSION_ACTIVE before calling session_start(),
referencing the existing session_start() in security.php to avoid double
invocation.


const COOKIE_NAME = 'NetAlertX_SaveLogin';
const DEFAULT_REDIRECT = '/devices.php';

/* =====================================================
Helper Functions
===================================================== */

function safe_redirect(string $path): void {
header("Location: {$path}", true, 302);
exit;
}

// Logout
if (isset ($_GET["action"]) && $_GET["action"] == 'logout')
{
setcookie($CookieSaveLoginName, '', time()+1); // reset cookie
$_SESSION["login"] = 0;
header('Location: index.php');
exit;
function validate_local_path(?string $encoded): string {
if (!$encoded) return DEFAULT_REDIRECT;

$decoded = base64_decode($encoded, true);
if ($decoded === false) {
return DEFAULT_REDIRECT;
}

// strict local path check (allow safe query strings + fragments)
// Using ~ as the delimiter instead of #
if (!preg_match('~^(?!//)(?!.*://)/[a-zA-Z0-9_\-./?=&:%#]*$~', $decoded)) {
return DEFAULT_REDIRECT;
}

return $decoded;
}

function append_hash(string $url): string {
if (!empty($_POST['url_hash'])) {
return $url . preg_replace('/[^#a-zA-Z0-9_\-]/', '', $_POST['url_hash']);
}
return $url;
}
Comment on lines 45 to 50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

append_hash doesn't validate that the value starts with #, producing a malformed URL if manipulated.

The JS (line 257-259) always provides window.location.hash (which starts with #), but a crafted POST could send url_hash=abc — the regex on line 47 preserves alphanumerics, resulting in a redirect to e.g. /devices.phpabc. Consider enforcing the # prefix:

Proposed fix
 function append_hash(string $url): string {
     if (!empty($_POST['url_hash'])) {
-        return $url . preg_replace('/[^#a-zA-Z0-9_\-]/', '', $_POST['url_hash']);
+        $sanitized = preg_replace('/[^#a-zA-Z0-9_\-]/', '', $_POST['url_hash']);
+        if (str_starts_with($sanitized, '#')) {
+            return $url . $sanitized;
+        }
     }
     return $url;
 }
🤖 Prompt for AI Agents
In `@front/index.php` around lines 45 - 50, The append_hash function accepts raw
$_POST['url_hash'] and can produce a malformed URL if the value doesn't start
with '#'; update append_hash to first check $_POST['url_hash'], normalize it so
it begins with a single '#' (prepend one if missing), then sanitize the rest
using the existing character whitelist (preg_replace('/[^#a-zA-Z0-9_\-]/', '',
...)) or better apply the regex to the substring after the '#' to avoid
stripping/duplicating the prefix; ensure you return $url . $normalizedHash and
reference append_hash and $_POST['url_hash'] when making the change.


function is_authenticated(): bool {
return isset($_SESSION['login']) && $_SESSION['login'] === 1;
}

function login_user(): void {
$_SESSION['login'] = 1;
session_regenerate_id(true);
}

function is_https_request(): bool {

// Direct HTTPS detection
if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') {
return true;
}

// Standard port check
if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) {
return true;
}

// Trusted proxy headers (only valid if behind a trusted reverse proxy)
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
return true;
}

if (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) &&
strtolower($_SERVER['HTTP_X_FORWARDED_SSL']) === 'on') {
return true;
}

return false;
}


function logout_user(): void {
$_SESSION = [];
session_destroy();

setcookie(COOKIE_NAME,'',[
'expires'=>time()-3600,
'path'=>'/',
'secure'=>is_https_request(),
'httponly'=>true,
'samesite'=>'Strict'
]);
}

/* =====================================================
Redirect Handling
===================================================== */

$redirectTo = validate_local_path($_GET['next'] ?? null);

/* =====================================================
Web Protection Disabled
===================================================== */

if ($nax_WebProtection !== 'true') {
if (!is_authenticated()) {
login_user();
}
safe_redirect(append_hash($redirectTo));
}

// Password without Cookie check -> pass and set initial cookie
if (isset ($_POST["loginpassword"]) && $nax_Password === hash('sha256',$_POST["loginpassword"]))
{
header('Location: devices.php');
$_SESSION["login"] = 1;
if (isset($_POST['PWRemember'])) {setcookie($CookieSaveLoginName, hash('sha256',$_POST["loginpassword"]), time()+604800);}
/* =====================================================
Login Attempt
===================================================== */

if (!empty($_POST['loginpassword'])) {

$incomingHash = hash('sha256', $_POST['loginpassword']);

if (hash_equals($nax_Password, $incomingHash)) {

login_user();

if (!empty($_POST['PWRemember'])) {
$token = bin2hex(random_bytes(32));

$_SESSION['remember_token'] = hash('sha256',$token);

setcookie(COOKIE_NAME,$token,[
'expires'=>time()+604800,
'path'=>'/',
'secure'=>is_https_request(),
'httponly'=>true,
'samesite'=>'Strict'
]);
}

safe_redirect(append_hash($redirectTo));
}
}

// active Session or valid cookie (cookie not extends)
if (( isset ($_SESSION["login"]) && ($_SESSION["login"] == 1)) || (isset ($_COOKIE[$CookieSaveLoginName]) && $nax_Password === $_COOKIE[$CookieSaveLoginName]))
{
header('Location: devices.php');
$_SESSION["login"] = 1;
if (isset($_POST['PWRemember'])) {setcookie($CookieSaveLoginName, hash('sha256',$_POST["loginpassword"]), time()+604800);}
/* =====================================================
Remember Me Validation
===================================================== */

if (!is_authenticated() && !empty($_COOKIE[COOKIE_NAME]) && !empty($_SESSION['remember_token'])) {

if (hash_equals($_SESSION['remember_token'], hash('sha256',$_COOKIE[COOKIE_NAME]))) {
login_user();
safe_redirect(append_hash($redirectTo));
}
}

/* =====================================================
Already Logged In
===================================================== */

if (is_authenticated()) {
safe_redirect(append_hash($redirectTo));
}

/* =====================================================
Login UI Variables
===================================================== */

$login_headline = lang('Login_Toggle_Info_headline');
$login_info = lang('Login_Info');
$login_mode = 'danger';
$login_display_mode = 'display: block;';
$login_icon = 'fa-info';

// no active session, cookie not checked
if (isset ($_SESSION["login"]) == FALSE || $_SESSION["login"] != 1)
{
if ($nax_Password === '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92')
{
$login_info = lang('Login_Info');
$login_mode = 'info';
$login_display_mode = 'display:none;';
$login_icon = 'fa-info';

if ($nax_Password === '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92') {
$login_info = lang('Login_Default_PWD');
$login_mode = 'danger';
$login_display_mode = 'display: block;';
$login_display_mode = 'display:block;';
$login_headline = lang('Login_Toggle_Alert_headline');
$login_icon = 'fa-ban';
}
else
{
$login_mode = 'info';
$login_display_mode = 'display: none;';
$login_headline = lang('Login_Toggle_Info_headline');
$login_icon = 'fa-info';
}
}

// ##################################################
// ## Login Processing end
// ##################################################
?>

<!DOCTYPE html>
Expand Down Expand Up @@ -109,8 +218,13 @@
<!-- /.login-logo -->
<div class="login-box-body">
<p class="login-box-msg"><?= lang('Login_Box');?></p>
<form action="index.php" method="post">
<form action="index.php<?php
echo !empty($_GET['next'])
? '?next=' . htmlspecialchars($_GET['next'], ENT_QUOTES, 'UTF-8')
: '';
?>" method="post">
<div class="form-group has-feedback">
<input type="hidden" name="url_hash" id="url_hash">
<input type="password" class="form-control" placeholder="<?= lang('Login_Psw-box');?>" name="loginpassword">
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
Expand All @@ -119,7 +233,7 @@
<div class="checkbox icheck">
<label>
<input type="checkbox" name="PWRemember">
<div style="margin-left: 10px; display: inline-block; vertical-align: top;">
<div style="margin-left: 10px; display: inline-block; vertical-align: top;">
<?= lang('Login_Remember');?><br><span style="font-size: smaller"><?= lang('Login_Remember_small');?></span>
</div>
</label>
Expand All @@ -129,7 +243,7 @@
<div class="col-xs-4" style="padding-top: 10px;">
<button type="submit" class="btn btn-primary btn-block btn-flat"><?= lang('Login_Submit');?></button>
</div>
<!-- /.col -->
<!-- /.col -->
</div>
</form>

Expand Down Expand Up @@ -159,6 +273,9 @@
<!-- iCheck -->
<script src="lib/iCheck/icheck.min.js"></script>
<script>
if (window.location.hash) {
document.getElementById('url_hash').value = window.location.hash;
}
$(function () {
$('input').iCheck({
checkboxClass: 'icheckbox_square-blue',
Expand All @@ -174,7 +291,7 @@ function Passwordhinfo() {
} else {
x.style.display = "none";
}
}
}

</script>
</body>
Expand Down
12 changes: 7 additions & 5 deletions front/php/templates/security.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<?php

// Start session if not already started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}

// Constants
$configFolderPath = rtrim(getenv('NETALERTX_CONFIG') ?: '/data/config', '/');
$legacyConfigPath = $_SERVER['DOCUMENT_ROOT'] . "/../config/app.conf";
Expand Down Expand Up @@ -45,10 +50,6 @@ function redirect($url) {
$authHeader = apache_request_headers()['Authorization'] ?? '';
$sessionLogin = isset($_SESSION['login']) ? $_SESSION['login'] : 0;

// Start session if not already started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}

// Handle logout
if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'logout') {
Expand Down Expand Up @@ -86,7 +87,8 @@ function redirect($url) {
// Logged in or stay on this page if we are on the index.php already
} else {
// We need to redirect
redirect('/index.php');
$returnUrl = rawurlencode(base64_encode($_SERVER['REQUEST_URI']));
redirect("/index.php?next=" . $returnUrl);
exit; // exit is needed to prevent authentication bypass
}
}
Expand Down
Loading