Login and Security improves

This commit is contained in:
Diego Najar 2017-07-16 00:42:37 +02:00
parent 0be75f22c4
commit 7f8e012486
13 changed files with 76 additions and 127 deletions

View File

@ -91,7 +91,7 @@ function checkGet($args)
} }
// Bruteforce protection, add IP to blacklist. // Bruteforce protection, add IP to blacklist.
$Security->addLoginFail(); $Security->addToBlacklist();
return false; return false;
} }

View File

@ -8,27 +8,26 @@
// Functions // Functions
// ============================================================================ // ============================================================================
function checkPost($args) function checkLogin($args)
{ {
global $Security; global $Security;
global $Login; global $Login;
global $Language; global $Language;
if($Security->isBlocked()) { if ($Security->isBlocked()) {
Alert::set($Language->g('IP address has been blocked').'<br>'.$Language->g('Try again in a few minutes')); Alert::set($Language->g('IP address has been blocked').'<br>'.$Language->g('Try again in a few minutes'));
return false; return false;
} }
// Verify User sanitize the input if ($Login->verifyUser($_POST['username'], $_POST['password'])) {
if( $Login->verifyUser($_POST['username'], $_POST['password']) ) {
// Renew the token. This token will be the same inside the session for multiple forms. // Renew the token. This token will be the same inside the session for multiple forms.
$Security->generateTokenCSRF(); $Security->generateTokenCSRF();
Redirect::page('dashboard'); Redirect::page('dashboard');
return true; return true;
} }
// Bruteforce protection, add IP to blacklist. // Bruteforce protection, add IP to the blacklist
$Security->addLoginFail(); $Security->addToBlacklist();
// Create alert // Create alert
Alert::set($Language->g('Username or password incorrect')); Alert::set($Language->g('Username or password incorrect'));
@ -44,9 +43,9 @@ function checkPost($args)
// POST Method // POST Method
// ============================================================================ // ============================================================================
if( $_SERVER['REQUEST_METHOD'] == 'POST' ) if ($_SERVER['REQUEST_METHOD']=='POST')
{ {
checkPost($_POST); checkLogin($_POST);
} }
// ============================================================================ // ============================================================================

View File

@ -144,6 +144,9 @@ $(document).ready(function() {
<?php <?php
if( Sanitize::pathFile(PATH_ADMIN_VIEWS, $layout['view'].'.php') ) { if( Sanitize::pathFile(PATH_ADMIN_VIEWS, $layout['view'].'.php') ) {
include(PATH_ADMIN_VIEWS.$layout['view'].'.php'); include(PATH_ADMIN_VIEWS.$layout['view'].'.php');
} else {
echo '<h1 style="width:100%; text-align:center">Hey!</h1>';
echo '<h2 style="width:100%; text-align:center">Have you seen my ball?</h2>';
} }
?> ?>
</div> </div>

View File

@ -4,7 +4,7 @@
<form method="post" action="" class="uk-form" autocomplete="off"> <form method="post" action="" class="uk-form" autocomplete="off">
<input type="hidden" id="jstoken" name="tokenCSRF" value="<?php $Security->printTokenCSRF() ?>"> <input type="hidden" id="jstoken" name="tokenCSRF" value="<?php echo $Security->getTokenCSRF() ?>">
<div class="uk-form-row"> <div class="uk-form-row">
<input name="email" class="uk-width-1-1 uk-form-large" placeholder="<?php $L->p('Email') ?>" type="text"> <input name="email" class="uk-width-1-1 uk-form-large" placeholder="<?php $L->p('Email') ?>" type="text">

View File

@ -2,7 +2,7 @@
<form method="post" action="" class="uk-form" autocomplete="off"> <form method="post" action="" class="uk-form" autocomplete="off">
<input type="hidden" id="jstoken" name="tokenCSRF" value="<?php $Security->printTokenCSRF() ?>"> <input type="hidden" id="jstoken" name="tokenCSRF" value="<?php echo $Security->getTokenCSRF() ?>">
<div class="uk-form-row"> <div class="uk-form-row">
<input name="username" class="uk-width-1-1 uk-form-large" placeholder="<?php $L->p('Username') ?>" type="text"> <input name="username" class="uk-width-1-1 uk-form-large" placeholder="<?php $L->p('Username') ?>" type="text">

View File

@ -50,14 +50,12 @@ else
// User not logged. // User not logged.
// Slug is login. // Slug is login.
// Slug is login-email. // Slug is login-email.
if($Url->notFound() || !$Login->isLogged() || ($Url->slug()==='login') || ($Url->slug()==='login-email') ) if($Url->notFound() || !$Login->isLogged() || ($Url->slug()==='login') || ($Url->slug()==='login-email') ) {
{
$layout['controller'] = 'login'; $layout['controller'] = 'login';
$layout['view'] = 'login'; $layout['view'] = 'login';
$layout['template'] = 'login.php'; $layout['template'] = 'login.php';
if($Url->slug()==='login-email') if ($Url->slug()==='login-email') {
{
$layout['controller'] = 'login-email'; $layout['controller'] = 'login-email';
$layout['view'] = 'login-email'; $layout['view'] = 'login-email';
} }

View File

@ -57,6 +57,7 @@ define('DB_CATEGORIES', PATH_DATABASES.'categories.php');
define('DB_TAGS', PATH_DATABASES.'tags.php'); define('DB_TAGS', PATH_DATABASES.'tags.php');
define('DB_SYSLOG', PATH_DATABASES.'syslog.php'); define('DB_SYSLOG', PATH_DATABASES.'syslog.php');
define('DB_USERS', PATH_DATABASES.'users.php'); define('DB_USERS', PATH_DATABASES.'users.php');
define('DB_SECURITY', PATH_DATABASES.'security.php');
// Log separator // Log separator
define('LOG_SEP', ' | '); define('LOG_SEP', ' | ');

View File

@ -185,19 +185,18 @@ class dbUsers extends dbJSON
return $token; return $token;
} }
// ---- OLD
// Returns array with the username databases filtered by username, FALSE otherwise // Returns array with the username databases filtered by username, FALSE otherwise
public function getDb($username) public function getDB($username)
{ {
if($this->exists($username)) { if ($this->exists($username)) {
$user = $this->db[$username]; return $this->db[$username];
return $user;
} }
return false; return false;
} }
// ---- OLD
public function getAll() public function getAll()
{ {
return $this->db; return $this->db;

View File

@ -55,13 +55,9 @@
public static function destroy() public static function destroy()
{ {
session_destroy(); session_destroy();
unset($_SESSION); unset($_SESSION);
self::$started = false; self::$started = false;
Log::set(__METHOD__.LOG_SEP.'Session destroyed.'); Log::set(__METHOD__.LOG_SEP.'Session destroyed.');
return !isset($_SESSION); return !isset($_SESSION);
} }

View File

@ -19,6 +19,26 @@ class Login {
return Session::get('role'); return Session::get('role');
} }
// Returns TRUE if the user is logged, FALSE otherwise
public function isLogged()
{
if (Session::get('fingerPrint')===$this->fingerPrint()) {
$username = Session::get('username');
if (!empty($username)) {
return true;
}
else {
Log::set(__METHOD__.LOG_SEP.'Session username empty, destroy the session.');
Session::destroy();
return false;
}
}
Log::set(__METHOD__.LOG_SEP.'FingerPrint are differents. Current fingerPrint: '.Session::get('fingerPrint').' !== Current fingerPrint: '.$this->fingerPrint());
return false;
}
// Set the session for the user logged
public function setLogin($username, $role) public function setLogin($username, $role)
{ {
Session::set('username', $username); Session::set('username', $username);
@ -26,30 +46,12 @@ class Login {
Session::set('fingerPrint', $this->fingerPrint()); Session::set('fingerPrint', $this->fingerPrint());
Session::set('sessionTime', time()); Session::set('sessionTime', time());
Log::set(__METHOD__.LOG_SEP.'Set fingerPrint: '.$this->fingerPrint()); Log::set(__METHOD__.LOG_SEP.'User logged, fingerprint: '.$this->fingerPrint());
}
public function isLogged()
{
if(Session::get('fingerPrint')===$this->fingerPrint())
{
$username = Session::get('username');
if(!empty($username)) {
return true;
}
else {
Log::set(__METHOD__.LOG_SEP.'Session username empty: '.$username);
}
}
else
{
Log::set(__METHOD__.LOG_SEP.'FingerPrint are differents. Session fingerPrint: '.Session::get('fingerPrint').' !== Current fingerPrint: '.$this->fingerPrint());
}
return false;
} }
// Check if the username and the password are valid
// Returns TRUE if valid and set the session
// Returns FALSE for invalid username or password
public function verifyUser($username, $password) public function verifyUser($username, $password)
{ {
$username = Sanitize::html($username); $username = Sanitize::html($username);
@ -58,25 +60,21 @@ class Login {
$username = trim($username); $username = trim($username);
$password = trim($password); $password = trim($password);
if(empty($username) || empty($password)) { if (empty($username) || empty($password)) {
Log::set(__METHOD__.LOG_SEP.'Username or password empty. Username: '.$username.' - Password: '.$password); Log::set(__METHOD__.LOG_SEP.'Username or password empty. Username: '.$username.' - Password: '.$password);
return false; return false;
} }
$user = $this->dbUsers->getDb($username); $user = $this->dbUsers->getDB($username);
if($user==false) { if($user==false) {
Log::set(__METHOD__.LOG_SEP.'Username does not exist: '.$username); Log::set(__METHOD__.LOG_SEP.'Username does not exist: '.$username);
return false; return false;
} }
$passwordHash = sha1($password.$user['salt']); $passwordHash = $this->dbUsers->generatePasswordHash($password, $user['salt']);
if ($passwordHash===$user['password']) {
if($passwordHash === $user['password'])
{
$this->setLogin($username, $user['role']); $this->setLogin($username, $user['role']);
Log::set(__METHOD__.LOG_SEP.'User logged succeeded by username and password - Username: '.$username); Log::set(__METHOD__.LOG_SEP.'User logged succeeded by username and password - Username: '.$username);
return true; return true;
} }
else { else {
@ -130,27 +128,14 @@ class Login {
return false; return false;
} }
public function fingerPrint($random=false) public function fingerPrint()
{ {
// User agent // User agent
$agent = getenv('HTTP_USER_AGENT'); $agent = getenv('HTTP_USER_AGENT');
if(empty($agent)) { if (empty($agent)) {
$agent = 'Bludit/1.0 (Mr Nibbler Protocol)'; $agent = 'Bludit/2.0 (Mr Nibbler Protocol)';
} }
// User IP
if(getenv('HTTP_X_FORWARDED_FOR'))
$ip = getenv('HTTP_X_FORWARDED_FOR');
elseif(getenv('HTTP_CLIENT_IP'))
$ip = getenv('HTTP_CLIENT_IP');
else
$ip = getenv('REMOTE_ADDR');
if($random) {
return sha1(mt_rand().$agent.$ip);
}
// DEBUG: Ver CLIENT IP, hay veces que retorna la ip ::1 y otras 127.0.0.1
return sha1($agent); return sha1($agent);
} }

View File

@ -1,9 +1,8 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); <?php defined('BLUDIT') or die('Bludit Badass CMS.');
class Security extends dbJSON class Security extends dbJSON
{ {
private $dbFields = array( private $dbFields = array(
'key1'=>'Where we go we dont need roads',
'minutesBlocked'=>5, 'minutesBlocked'=>5,
'numberFailuresAllowed'=>10, 'numberFailuresAllowed'=>10,
'blackList'=>array() 'blackList'=>array()
@ -11,51 +10,33 @@ class Security extends dbJSON
function __construct() function __construct()
{ {
parent::__construct(PATH_DATABASES.'security.php'); parent::__construct(DB_SECURITY);
} }
// Authentication key
// --------------------------------------------------------------------
public function key1()
{
return $this->db['key1'];
}
// ==================================================== // ====================================================
// TOKEN FOR CSRF // TOKEN FOR CSRF
// ==================================================== // ====================================================
// Generate and save the token in Session. // Generate and save the token in Session
public function generateTokenCSRF() public function generateTokenCSRF()
{ {
$token = Text::randomText(8); $token = sha1( uniqid().time() );
$token = sha1($token);
Log::set(__METHOD__.LOG_SEP.'New tokenCSRF was generated '.$token);
Session::set('tokenCSRF', $token); Session::set('tokenCSRF', $token);
} }
// Validate the token. // Validate the token
public function validateTokenCSRF($token) public function validateTokenCSRF($token)
{ {
$sessionToken = Session::get('tokenCSRF'); $sessionToken = $this->getTokenCSRF();
return ( !empty($sessionToken) && ($sessionToken===$token) ); return ( !empty($sessionToken) && ($sessionToken===$token) );
} }
// Returns the token. // Returns the token
public function getTokenCSRF() public function getTokenCSRF()
{ {
return Session::get('tokenCSRF'); return Session::get('tokenCSRF');
} }
public function printTokenCSRF()
{
echo Session::get('tokenCSRF');
}
// ==================================================== // ====================================================
// BRUTE FORCE PROTECTION // BRUTE FORCE PROTECTION
// ==================================================== // ====================================================
@ -64,7 +45,7 @@ class Security extends dbJSON
{ {
$ip = $this->getUserIp(); $ip = $this->getUserIp();
if(!isset($this->db['blackList'][$ip])) { if (!isset($this->db['blackList'][$ip])) {
return false; return false;
} }
@ -73,51 +54,42 @@ class Security extends dbJSON
$numberFailures = $userBlack['numberFailures']; $numberFailures = $userBlack['numberFailures'];
$lastFailure = $userBlack['lastFailure']; $lastFailure = $userBlack['lastFailure'];
// Check if the IP is expired, then is not blocked. // Check if the IP is expired, then is not blocked
if($currentTime > $lastFailure + ($this->db['minutesBlocked']*60)) { if ($currentTime > $lastFailure + ($this->db['minutesBlocked']*60)) {
return false; return false;
} }
// The IP has more failures than number of failures, then the IP is blocked. // The IP has more failures than number of failures, then the IP is blocked
if($numberFailures >= $this->db['numberFailuresAllowed']) { if ($numberFailures >= $this->db['numberFailuresAllowed']) {
Log::set(__METHOD__.LOG_SEP.'IP Blocked:'.$ip); Log::set(__METHOD__.LOG_SEP.'IP Blocked:'.$ip);
return true; return true;
} }
// Otherwise the IP is not blocked. // Otherwise the IP is not blocked
return false; return false;
} }
public function addLoginFail() // Add or update the current client IP on the blacklist
public function addToBlacklist()
{ {
$ip = $this->getUserIp(); $ip = $this->getUserIp();
$currentTime = time(); $currentTime = time();
$numberFailures = 1; $numberFailures = 1;
if(isset($this->db['blackList'][$ip])) if (isset($this->db['blackList'][$ip])) {
{
$userBlack = $this->db['blackList'][$ip]; $userBlack = $this->db['blackList'][$ip];
$lastFailure = $userBlack['lastFailure']; $lastFailure = $userBlack['lastFailure'];
// Check if the IP is expired, then renew the number of failures. // Check if the IP is expired, then renew the number of failures
if($currentTime <= $lastFailure + ($this->db['minutesBlocked']*60)) if($currentTime <= $lastFailure + ($this->db['minutesBlocked']*60)) {
{
$numberFailures = $userBlack['numberFailures']; $numberFailures = $userBlack['numberFailures'];
$numberFailures = $numberFailures + 1; $numberFailures = $numberFailures + 1;
} }
} }
$this->db['blackList'][$ip] = array('lastFailure'=>$currentTime, 'numberFailures'=>$numberFailures); $this->db['blackList'][$ip] = array('lastFailure'=>$currentTime, 'numberFailures'=>$numberFailures);
Log::set(__METHOD__.LOG_SEP.'Blacklist, IP:'.$ip.', Number of failures:'.$numberFailures); Log::set(__METHOD__.LOG_SEP.'Blacklist, IP:'.$ip.', Number of failures:'.$numberFailures);
return $this->save();
// Save the database
if( $this->save() === false ) {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.');
return false;
}
return true;
} }
public function getNumberFailures($ip=null) public function getNumberFailures($ip=null)
@ -134,14 +106,13 @@ class Security extends dbJSON
public function getUserIp() public function getUserIp()
{ {
// User IP if (getenv('HTTP_X_FORWARDED_FOR')) {
if(getenv('HTTP_X_FORWARDED_FOR'))
$ip = getenv('HTTP_X_FORWARDED_FOR'); $ip = getenv('HTTP_X_FORWARDED_FOR');
elseif(getenv('HTTP_CLIENT_IP')) } elseif (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP'); $ip = getenv('HTTP_CLIENT_IP');
else } else {
$ip = getenv('REMOTE_ADDR'); $ip = getenv('REMOTE_ADDR');
}
return $ip; return $ip;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -418,10 +418,7 @@ function install($adminPassword, $email, $timezone)
file_put_contents(PATH_DATABASES.'syslog.php', $dataHead.json_encode($data, JSON_PRETTY_PRINT), LOCK_EX); file_put_contents(PATH_DATABASES.'syslog.php', $dataHead.json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
// File security.php // File security.php
$randomKey = sha1( uniqid() );
$data = array( $data = array(
'key1'=>$randomKey,
'minutesBlocked'=>5, 'minutesBlocked'=>5,
'numberFailuresAllowed'=>10, 'numberFailuresAllowed'=>10,
'blackList'=>array() 'blackList'=>array()