1
0
mirror of https://github.com/moparisthebest/wallabag synced 2024-11-27 11:22:17 -05:00

fix bug #127: update session class

This commit is contained in:
Nicolas Lœuillet 2013-09-17 14:48:16 +02:00
parent a8778dc23e
commit f6597c7cb9
3 changed files with 223 additions and 71 deletions

View File

@ -1,6 +1,7 @@
<?php <?php
/** /**
* Session management class * Session management class
*
* http://www.developpez.net/forums/d51943/php/langage/sessions/ * http://www.developpez.net/forums/d51943/php/langage/sessions/
* http://sebsauvage.net/wiki/doku.php?id=php:session * http://sebsauvage.net/wiki/doku.php?id=php:session
* http://sebsauvage.net/wiki/doku.php?id=php:shaarli * http://sebsauvage.net/wiki/doku.php?id=php:shaarli
@ -8,129 +9,271 @@
* Features: * Features:
* - Everything is stored on server-side (we do not trust client-side data, * - Everything is stored on server-side (we do not trust client-side data,
* such as cookie expiration) * such as cookie expiration)
* - IP addresses + user agent are checked on each access to prevent session * - IP addresses are checked on each access to prevent session cookie hijacking
* cookie hijacking (such as Firesheep) * (such as Firesheep)
* - Session expires on user inactivity (Session expiration date is * - Session expires on user inactivity (Session expiration date is
* automatically updated everytime the user accesses a page.) * automatically updated everytime the user accesses a page.)
* - A unique secret key is generated on server-side for this session * - A unique secret key is generated on server-side for this session
* (and never sent over the wire) which can be used * (and never sent over the wire) which can be used to sign forms (HMAC)
* to sign forms (HMAC) (See $_SESSION['uid'] ) * (See $_SESSION['uid'])
* - Token management to prevent XSRF attacks. * - Token management to prevent XSRF attacks
* - Brute force protection with ban management
* *
* TODO: * TODOs
* - log login fail * - Replace globals with variables in Session class
* - prevent brute force (ban IP)
* *
* HOWTOUSE: * How to use:
* - Just call Session::init(); to initialize session and * - http://tontof.net/kriss/php5/session
* check if connected with Session::isLogged()
*/ */
class Session class Session
{ {
// Personnalize PHP session name
public static $sessionName = '';
// If the user does not access any page within this time, // If the user does not access any page within this time,
// his/her session is considered expired (in seconds). // his/her session is considered expired (3600 sec. = 1 hour)
public static $inactivity_timeout = 3600; public static $inactivityTimeout = 3600;
private static $_instance; // If you get disconnected often or if your IP address changes often.
// Let you disable session cookie hijacking protection
public static $disableSessionProtection = false;
// Ban IP after this many failures.
public static $banAfter = 4;
// Ban duration for IP address after login failures (in seconds).
// (1800 sec. = 30 minutes)
public static $banDuration = 1800;
// File storage for failures and bans. If empty, no ban management.
public static $banFile = '';
// constructor /**
private function __construct() * Initialize session
*/
public static function init()
{ {
// Force cookie path (but do not change lifetime)
$cookie = session_get_cookie_params();
// Default cookie expiration and path.
$cookiedir = '';
if (dirname($_SERVER['SCRIPT_NAME'])!='/') {
$cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/';
}
$ssl = false;
if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") {
$ssl = true;
}
session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['HTTP_HOST'], $ssl);
// Use cookies to store session. // Use cookies to store session.
ini_set('session.use_cookies', 1); ini_set('session.use_cookies', 1);
// Force cookies for session (phpsessionID forbidden in URL) // Force cookies for session (phpsessionID forbidden in URL)
ini_set('session.use_only_cookies', 1); ini_set('session.use_only_cookies', 1);
if (!session_id()){ if (!session_id()) {
// Prevent php to use sessionID in URL if cookies are disabled. // Prevent php to use sessionID in URL if cookies are disabled.
ini_set('session.use_trans_sid', false); ini_set('session.use_trans_sid', false);
session_start('poche'); if (!empty(self::$sessionName)) {
session_name(self::$sessionName);
}
session_start();
} }
} }
// initialize session /**
public static function init() * Returns the IP address
* (Used to prevent session cookie hijacking.)
*
* @return string IP addresses
*/
private static function _allIPs()
{ {
if (!isset(self::$_instance)) { $ip = $_SERVER["REMOTE_ADDR"];
self::$_instance = new Session(); $ip.= isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? '_'.$_SERVER['HTTP_X_FORWARDED_FOR'] : '';
} $ip.= isset($_SERVER['HTTP_CLIENT_IP']) ? '_'.$_SERVER['HTTP_CLIENT_IP'] : '';
return $ip;
} }
// Returns the IP address, user agent and language of the client /**
// (Used to prevent session cookie hijacking.) * Check that user/password is correct and then init some SESSION variables.
private static function _allInfos() *
{ * @param string $login Login reference
$infos = $_SERVER["REMOTE_ADDR"]; * @param string $password Password reference
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { * @param string $loginTest Login to compare with login reference
$infos.=$_SERVER['HTTP_X_FORWARDED_FOR']; * @param string $passwordTest Password to compare with password reference
} * @param array $pValues Array of variables to store in SESSION
if (isset($_SERVER['HTTP_CLIENT_IP'])) { *
$infos.='_'.$_SERVER['HTTP_CLIENT_IP']; * @return true|false True if login and password are correct, false
} * otherwise
$infos.='_'.$_SERVER['HTTP_USER_AGENT']; */
$infos.='_'.$_SERVER['HTTP_ACCEPT_LANGUAGE']; public static function login (
return sha1($infos); $login,
} $password,
$loginTest,
// Check that user/password is correct and init some SESSION variables. $passwordTest,
public static function login($login,$password,$login_test,$password_test,
$pValues = array()) $pValues = array())
{ {
self::banInit();
if (self::banCanLogin()) {
if ($login === $loginTest && $password === $passwordTest) {
self::banLoginOk();
// Generate unique random number to sign forms (HMAC)
$_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand());
$_SESSION['ip'] = self::_allIPs();
$_SESSION['username'] = $login;
// Set session expiration.
$_SESSION['expires_on'] = time() + self::$inactivityTimeout;
foreach ($pValues as $key => $value) { foreach ($pValues as $key => $value) {
$_SESSION[$key] = $value; $_SESSION[$key] = $value;
} }
if ($login==$login_test && $password==$password_test){
// generate unique random number to sign forms (HMAC)
$_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand());
$_SESSION['info']=Session::_allInfos();
$_SESSION['username']=$login;
// Set session expiration.
$_SESSION['expires_on']=time()+Session::$inactivity_timeout;
return true; return true;
} }
self::banLoginFailed();
}
return false; return false;
} }
// Force logout /**
* Unset SESSION variable to force logout
*/
public static function logout() public static function logout()
{ {
unset($_SESSION['uid'],$_SESSION['info'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['poche_user']); unset($_SESSION['uid'], $_SESSION['ip'], $_SESSION['expires_on']);
} }
// Make sure user is logged in. /**
* Make sure user is logged in.
*
* @return true|false True if user is logged in, false otherwise
*/
public static function isLogged() public static function isLogged()
{ {
if (!isset ($_SESSION['uid']) if (!isset ($_SESSION['uid'])
|| $_SESSION['info']!=Session::_allInfos() || (self::$disableSessionProtection === false
|| time()>=$_SESSION['expires_on']){ && $_SESSION['ip'] !== self::_allIPs())
Session::logout(); || time() >= $_SESSION['expires_on']) {
self::logout();
return false; return false;
} }
// User accessed a page : Update his/her session expiration date. // User accessed a page : Update his/her session expiration date.
$_SESSION['expires_on']=time()+Session::$inactivity_timeout; $_SESSION['expires_on'] = time() + self::$inactivityTimeout;
if (!empty($_SESSION['longlastingsession'])) {
$_SESSION['expires_on'] += $_SESSION['longlastingsession'];
}
return true; return true;
} }
// Returns a token. /**
public static function getToken() * Create a token, store it in SESSION and return it
*
* @param string $salt to prevent birthday attack
*
* @return string Token created
*/
public static function getToken($salt = '')
{ {
if (!isset($_SESSION['tokens'])){ if (!isset($_SESSION['tokens'])) {
$_SESSION['tokens']=array(); $_SESSION['tokens']=array();
} }
// We generate a random string and store it on the server side. // We generate a random string and store it on the server side.
$rnd = sha1(uniqid('',true).'_'.mt_rand()); $rnd = sha1(uniqid('', true).'_'.mt_rand().$salt);
$_SESSION['tokens'][$rnd]=1; $_SESSION['tokens'][$rnd]=1;
return $rnd; return $rnd;
} }
// Tells if a token is ok. Using this function will destroy the token. /**
// return true if token is ok. * Tells if a token is ok. Using this function will destroy the token.
*
* @param string $token Token to test
*
* @return true|false True if token is correct, false otherwise
*/
public static function isToken($token) public static function isToken($token)
{ {
if (isset($_SESSION['tokens'][$token])) if (isset($_SESSION['tokens'][$token])) {
{
unset($_SESSION['tokens'][$token]); // Token is used: destroy it. unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
return true; // Token is ok. return true; // Token is ok.
} }
return false; // Wrong token, or already used. return false; // Wrong token, or already used.
} }
/**
* Signal a failed login. Will ban the IP if too many failures:
*/
public static function banLoginFailed()
{
if (self::$banFile !== '') {
$ip = $_SERVER["REMOTE_ADDR"];
$gb = $GLOBALS['IPBANS'];
if (!isset($gb['FAILURES'][$ip])) {
$gb['FAILURES'][$ip] = 0;
}
$gb['FAILURES'][$ip]++;
if ($gb['FAILURES'][$ip] > (self::$banAfter - 1)) {
$gb['BANS'][$ip]= time() + self::$banDuration;
}
$GLOBALS['IPBANS'] = $gb;
file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
}
}
/**
* Signals a successful login. Resets failed login counter.
*/
public static function banLoginOk()
{
if (self::$banFile !== '') {
$ip = $_SERVER["REMOTE_ADDR"];
$gb = $GLOBALS['IPBANS'];
unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
$GLOBALS['IPBANS'] = $gb;
file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
}
}
/**
* Ban init
*/
public static function banInit()
{
if (self::$banFile !== '') {
if (!is_file(self::$banFile)) {
file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(), 'BANS'=>array()), true).";\n?>");
}
include self::$banFile;
}
}
/**
* Checks if the user CAN login. If 'true', the user can try to login.
*
* @return boolean true if user is banned, false otherwise
*/
public static function banCanLogin()
{
if (self::$banFile !== '') {
$ip = $_SERVER["REMOTE_ADDR"];
$gb = $GLOBALS['IPBANS'];
if (isset($gb['BANS'][$ip])) {
// User is banned. Check if the ban has expired:
if ($gb['BANS'][$ip] <= time()) {
// Ban expired, user can try to login again.
unset($gb['FAILURES'][$ip]);
unset($gb['BANS'][$ip]);
file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
return true; // Ban has expired, user can login.
}
return false; // User is banned.
}
}
return true; // User is not banned.
}
} }

View File

@ -93,6 +93,7 @@ class Poche
private function init() private function init()
{ {
Tools::initPhp(); Tools::initPhp();
Session::$sessionName = 'poche';
Session::init(); Session::init();
if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {

View File

@ -49,3 +49,11 @@ if (!ini_get('date.timezone') || !@date_default_timezone_set(ini_get('date.timez
} }
$poche = new Poche(); $poche = new Poche();
#XSRF protection with token
if (!empty($_POST)) {
if (!Session::isToken($_POST['token'])) {
die(_('Wrong token'));
}
unset($_SESSION['tokens']);
}