- Add email notification to agents if their 2FA code is entered incorrectly (this may be a sign of account compromise)

- Tidy login code flow so that the "logged" session variable only has to be set in one place, rather than in two (both for 2fa and non-2fa logins)
This commit is contained in:
Marcus Hill 2023-01-21 13:25:16 +00:00
parent 6d26b07d70
commit b9b0440186
1 changed files with 143 additions and 106 deletions

249
login.php
View File

@ -12,6 +12,20 @@ include("functions.php");
$ip = strip_tags(mysqli_real_escape_string($mysqli,get_ip())); $ip = strip_tags(mysqli_real_escape_string($mysqli,get_ip()));
$user_agent = strip_tags(mysqli_real_escape_string($mysqli,$_SERVER['HTTP_USER_AGENT'])); $user_agent = strip_tags(mysqli_real_escape_string($mysqli,$_SERVER['HTTP_USER_AGENT']));
// Query Settings for "default" company (as companies are being removed shortly)
$sql_settings = mysqli_query($mysqli,"SELECT * FROM settings WHERE company_id = 1");
$row = mysqli_fetch_array($sql_settings);
// Mail
$config_smtp_host = $row['config_smtp_host'];
$config_smtp_port = $row['config_smtp_port'];
$config_smtp_encryption = $row['config_smtp_encryption'];
$config_smtp_username = $row['config_smtp_username'];
$config_smtp_password = $row['config_smtp_password'];
$config_mail_from_email = $row['config_mail_from_email'];
$config_mail_from_name = $row['config_mail_from_name'];
// HTTP-Only cookies // HTTP-Only cookies
ini_set("session.cookie_httponly", True); ini_set("session.cookie_httponly", True);
@ -48,47 +62,64 @@ if (isset($_POST['login'])) {
// Passed login brute force check // Passed login brute force check
$email = strip_tags(mysqli_real_escape_string($mysqli, $_POST['email'])); $email = strip_tags(mysqli_real_escape_string($mysqli, $_POST['email']));
$password = $_POST['password']; $password = $_POST['password'];
$current_code = 0; // Default value
if (isset($_POST['current_code'])) { if (isset($_POST['current_code'])) {
$current_code = strip_tags(mysqli_real_escape_string($mysqli, $_POST['current_code'])); $current_code = strip_tags(mysqli_real_escape_string($mysqli, $_POST['current_code']));
} }
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM users LEFT JOIN user_settings on users.user_id = user_settings.user_id WHERE user_email = '$email' AND user_archived_at IS NULL AND user_status = 1")); $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM users LEFT JOIN user_settings on users.user_id = user_settings.user_id WHERE user_email = '$email' AND user_archived_at IS NULL AND user_status = 1"));
// Check password
if ($row && password_verify($password, $row['user_password'])) { if ($row && password_verify($password, $row['user_password'])) {
// User variables // User password correct (partial login)
$token = $row['user_token'];
// Set temporary user variables
$user_name = strip_tags(mysqli_real_escape_string($mysqli, $row['user_name'])); $user_name = strip_tags(mysqli_real_escape_string($mysqli, $row['user_name']));
$user_id = $row['user_id']; $user_id = $row['user_id'];
$user_email = $row['user_email'];
$token = $row['user_token'];
// Session info // Checking for user 2FA
$_SESSION['user_id'] = $user_id; require_once("rfc6238.php");
$_SESSION['user_name'] = $user_name; if (empty($token) || TokenAuth6238::verify($token, $current_code)) {
$_SESSION['user_role'] = $row['user_role'];
$_SESSION['csrf_token'] = bin2hex(random_bytes(78));
// Setup encryption session key // FULL LOGIN SUCCESS - 2FA not configured or was successful
if (isset($row['user_specific_encryption_ciphertext']) && $row['user_role'] > 1) {
$user_encryption_ciphertext = $row['user_specific_encryption_ciphertext'];
$site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password);
generateUserSessionKey($site_encryption_master_key);
// Setup extension // Determine whether 2FA was used (for logs)
if (isset($row['user_extension_key']) && !empty($row['user_extension_key'])) { $extended_log = ''; // Default value
// Extension cookie if ($current_code !== 0 ) {
// Note: Browsers don't accept cookies with SameSite None if they are not HTTPS. $extended_log = 'with 2FA';
setcookie("user_extension_key", "$row[user_extension_key]", ['path' => '/', 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
// Set PHP session in DB so we can access the session encryption data (above)
$user_php_session = session_id();
mysqli_query($mysqli, "UPDATE users SET user_php_session = '$user_php_session' WHERE user_id = '$user_id'");
} }
}
if (empty($token)) { // Logging successful login
// Full Login successful mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login', log_action = 'Success', log_description = '$user_name successfully logged in $extended_log', log_ip = '$ip', log_user_agent = '$user_agent', log_user_id = $user_id");
// Session info
$_SESSION['user_id'] = $user_id;
$_SESSION['user_name'] = $user_name;
$_SESSION['user_role'] = $row['user_role'];
$_SESSION['csrf_token'] = bin2hex(random_bytes(78));
$_SESSION['logged'] = TRUE; $_SESSION['logged'] = TRUE;
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login', log_action = 'Success', log_description = '$user_name successfully logged in', log_ip = '$ip', log_user_agent = '$user_agent', log_user_id = $user_id");
// Setup encryption session key
if (isset($row['user_specific_encryption_ciphertext']) && $row['user_role'] > 1) {
$user_encryption_ciphertext = $row['user_specific_encryption_ciphertext'];
$site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password);
generateUserSessionKey($site_encryption_master_key);
// Setup extension
if (isset($row['user_extension_key']) && !empty($row['user_extension_key'])) {
// Extension cookie
// Note: Browsers don't accept cookies with SameSite None if they are not HTTPS.
setcookie("user_extension_key", "$row[user_extension_key]", ['path' => '/', 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
// Set PHP session in DB so we can access the session encryption data (above)
$user_php_session = session_id();
mysqli_query($mysqli, "UPDATE users SET user_php_session = '$user_php_session' WHERE user_id = '$user_id'");
}
}
// Show start page/dashboard depending on role // Show start page/dashboard depending on role
if ($row['user_role'] == 2) { if ($row['user_role'] == 2) {
@ -97,53 +128,59 @@ if (isset($_POST['login'])) {
header("Location: dashboard_financial.php"); header("Location: dashboard_financial.php");
} }
} else { } else {
// Prompt for MFA
$token_field = "<div class='input-group mb-3'> // MFA is configured and needs to be confirmed, or was unsuccessful
<input type='text' class='form-control' placeholder='Token' name='current_code' autofocus>
<div class='input-group-append'>
<div class='input-group-text'>
<span class='fas fa-key'></span>
</div>
</div>
</div>";
require_once("rfc6238.php"); // HTML code for the token input field
$token_field = "
<div class='input-group mb-3'>
<input type='text' class='form-control' placeholder='2FA Token' name='current_code' required autofocus>
<div class='input-group-append'>
<div class='input-group-text'>
<span class='fas fa-key'></span>
</div>
</div>
</div>";
if (TokenAuth6238::verify($token, $current_code)) { // Log/notify if MFA was unsuccessful
// Full login (with MFA) successful if ($current_code !== 0) {
$_SESSION['logged'] = TRUE;
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login 2FA', log_action = 'Success', log_description = '$user_name successfully logged in using 2FA', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_user_id = $user_id");
// Show start page/dashboard depending on role // Logging
if ($row['user_role'] == 2) {
header("Location: dashboard_technical.php");
} else {
header("Location: dashboard_financial.php");
}
} else {
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login', log_action = '2FA Failed', log_description = '$user_name failed 2FA', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_user_id = $user_id"); mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login', log_action = '2FA Failed', log_description = '$user_name failed 2FA', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_user_id = $user_id");
// Email the tech to advise their credentials may be compromised
if (!empty($config_smtp_host)) {
$subject = "Important: ITFlow failed 2FA login attempt for $user_name";
$body = "Hi $user_name, <br><br>A recent login to ITFlow was unsuccessful due to an incorrect 2FA code. If you did not attempt this login, your credentials may be compromised. <br><br>Thanks, <br>ITFlow";
$mail = sendSingleEmail($config_smtp_host, $config_smtp_username, $config_smtp_password, $config_smtp_encryption, $config_smtp_port,
$config_mail_from_email, $config_mail_from_name,
$user_email, $user_name,
$subject, $body);
}
// HTML feedback for incorrect 2FA code
$response = " $response = "
<div class='alert alert-primary'> <div class='alert alert-warning'>
Please Enter 2FA Key! Please Enter 2FA Key!
<button class='close' data-dismiss='alert'>&times;</button> <button class='close' data-dismiss='alert'>&times;</button>
</div> </div>";
";
} }
} }
} else { } else {
// Password incorrect or user doesn't exist - show generic error
mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login', log_action = 'Failed', log_description = 'Failed login attempt using $email', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW()"); mysqli_query($mysqli, "INSERT INTO logs SET log_type = 'Login', log_action = 'Failed', log_description = 'Failed login attempt using $email', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW()");
$response = " $response = "
<div class='alert alert-danger'> <div class='alert alert-danger'>
Incorrect username or password. Incorrect username or password.
<button class='close' data-dismiss='alert'>&times;</button> <button class='close' data-dismiss='alert'>&times;</button>
</div> </div>";
";
} }
} }
} }
@ -153,60 +190,60 @@ if (isset($_POST['login'])) {
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><?php echo $config_app_name; ?> | Login</title> <title><?php echo $config_app_name; ?> | Login</title>
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex"> <meta name="robots" content="noindex">
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css"> <link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
<!-- Theme style --> <!-- Theme style -->
<link rel="stylesheet" href="dist/css/adminlte.min.css"> <link rel="stylesheet" href="dist/css/adminlte.min.css">
<!-- Google Font: Source Sans Pro --> <!-- Google Font: Source Sans Pro -->
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head> </head>
<body class="hold-transition login-page"> <body class="hold-transition login-page">
<div class="login-box"> <div class="login-box">
<div class="login-logo"> <div class="login-logo">
<b>IT</b>Flow <b>IT</b>Flow
</div> </div>
<!-- /.login-logo --> <!-- /.login-logo -->
<div class="card"> <div class="card">
<div class="card-body login-card-body"> <div class="card-body login-card-body">
<p class="login-box-msg"><?php if(isset($response)) { echo $response; } ?></p> <p class="login-box-msg"><?php if(isset($response)) { echo $response; } ?></p>
<form method="post"> <form method="post">
<div class="input-group mb-3"> <div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Agent Email" name="email" value="<?php if(!empty($token_field)){ echo $email; }?>" required <?php if(empty($token_field)){ echo "autofocus"; } ?> > <input type="text" class="form-control" placeholder="Agent Email" name="email" value="<?php if(!empty($token_field)){ echo $email; }?>" required <?php if(empty($token_field)){ echo "autofocus"; } ?> >
<div class="input-group-append"> <div class="input-group-append">
<div class="input-group-text"> <div class="input-group-text">
<span class="fas fa-envelope"></span> <span class="fas fa-envelope"></span>
</div> </div>
</div> </div>
</div> </div>
<div class="input-group mb-3"> <div class="input-group mb-3">
<input type="password" class="form-control" placeholder="Agent Password" name="password" value="<?php if(!empty($token_field)){ echo $password; } ?>" required> <input type="password" class="form-control" placeholder="Agent Password" name="password" value="<?php if(!empty($token_field)){ echo $password; } ?>" required>
<div class="input-group-append"> <div class="input-group-append">
<div class="input-group-text"> <div class="input-group-text">
<span class="fas fa-lock"></span> <span class="fas fa-lock"></span>
</div> </div>
</div> </div>
</div> </div>
<?php if(!empty($token_field)){ echo $token_field; } ?> <?php if(!empty($token_field)){ echo $token_field; } ?>
<button type="submit" class="btn btn-primary btn-block mb-3" name="login">Sign In</button> <button type="submit" class="btn btn-primary btn-block mb-3" name="login">Sign In</button>
<hr><br> <hr><br>
<h4>Looking for the <a href="portal">Client Portal?<a/></h4> <h4>Looking for the <a href="portal">Client Portal?<a/></h4>
</form> </form>
</div>
<!-- /.login-card-body -->
</div> </div>
<!-- /.login-card-body -->
</div>
</div> </div>
<!-- /.login-box --> <!-- /.login-box -->