aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNicolas LÅ“uillet <nicolas.loeuillet@gmail.com>2013-09-17 14:48:16 +0200
committerNicolas LÅ“uillet <nicolas.loeuillet@gmail.com>2013-09-17 14:48:16 +0200
commitf6597c7cb90e9bfa96f01f5f78f98cd72696da55 (patch)
tree78a3ad1edb1bd55bedee847455831404d99ad93b
parenta8778dc23e60e65b47e2aae5d4cdf92660ee4c02 (diff)
downloadwallabag-f6597c7cb90e9bfa96f01f5f78f98cd72696da55.tar.gz
wallabag-f6597c7cb90e9bfa96f01f5f78f98cd72696da55.tar.zst
wallabag-f6597c7cb90e9bfa96f01f5f78f98cd72696da55.zip
fix bug #127: update session class
-rw-r--r--inc/3rdparty/Session.class.php283
-rw-r--r--inc/poche/Poche.class.php1
-rwxr-xr-xinc/poche/config.inc.php10
3 files changed, 223 insertions, 71 deletions
diff --git a/inc/3rdparty/Session.class.php b/inc/3rdparty/Session.class.php
index 3162f507..08126bad 100644
--- a/inc/3rdparty/Session.class.php
+++ b/inc/3rdparty/Session.class.php
@@ -1,136 +1,279 @@
1<?php 1<?php
2/** 2/**
3 * Session management class 3 * Session management class
4 *
4 * http://www.developpez.net/forums/d51943/php/langage/sessions/ 5 * http://www.developpez.net/forums/d51943/php/langage/sessions/
5 * http://sebsauvage.net/wiki/doku.php?id=php:session 6 * http://sebsauvage.net/wiki/doku.php?id=php:session
6 * http://sebsauvage.net/wiki/doku.php?id=php:shaarli 7 * http://sebsauvage.net/wiki/doku.php?id=php:shaarli
7 * 8 *
8 * Features: 9 * Features:
9 * - Everything is stored on server-side (we do not trust client-side data, 10 * - Everything is stored on server-side (we do not trust client-side data,
10 * such as cookie expiration) 11 * such as cookie expiration)
11 * - IP addresses + user agent are checked on each access to prevent session 12 * - IP addresses are checked on each access to prevent session cookie hijacking
12 * cookie hijacking (such as Firesheep) 13 * (such as Firesheep)
13 * - Session expires on user inactivity (Session expiration date is 14 * - Session expires on user inactivity (Session expiration date is
14 * automatically updated everytime the user accesses a page.) 15 * automatically updated everytime the user accesses a page.)
15 * - A unique secret key is generated on server-side for this session 16 * - A unique secret key is generated on server-side for this session
16 * (and never sent over the wire) which can be used 17 * (and never sent over the wire) which can be used to sign forms (HMAC)
17 * to sign forms (HMAC) (See $_SESSION['uid'] ) 18 * (See $_SESSION['uid'])
18 * - Token management to prevent XSRF attacks. 19 * - Token management to prevent XSRF attacks
20 * - Brute force protection with ban management
19 * 21 *
20 * TODO: 22 * TODOs
21 * - log login fail 23 * - Replace globals with variables in Session class
22 * - prevent brute force (ban IP)
23 * 24 *
24 * HOWTOUSE: 25 * How to use:
25 * - Just call Session::init(); to initialize session and 26 * - http://tontof.net/kriss/php5/session
26 * check if connected with Session::isLogged()
27 */ 27 */
28
29class Session 28class Session
30{ 29{
30 // Personnalize PHP session name
31 public static $sessionName = '';
31 // If the user does not access any page within this time, 32 // If the user does not access any page within this time,
32 // his/her session is considered expired (in seconds). 33 // his/her session is considered expired (3600 sec. = 1 hour)
33 public static $inactivity_timeout = 3600; 34 public static $inactivityTimeout = 3600;
34 private static $_instance; 35 // If you get disconnected often or if your IP address changes often.
36 // Let you disable session cookie hijacking protection
37 public static $disableSessionProtection = false;
38 // Ban IP after this many failures.
39 public static $banAfter = 4;
40 // Ban duration for IP address after login failures (in seconds).
41 // (1800 sec. = 30 minutes)
42 public static $banDuration = 1800;
43 // File storage for failures and bans. If empty, no ban management.
44 public static $banFile = '';
35 45
36 // constructor 46 /**
37 private function __construct() 47 * Initialize session
48 */
49 public static function init()
38 { 50 {
51 // Force cookie path (but do not change lifetime)
52 $cookie = session_get_cookie_params();
53 // Default cookie expiration and path.
54 $cookiedir = '';
55 if (dirname($_SERVER['SCRIPT_NAME'])!='/') {
56 $cookiedir = dirname($_SERVER["SCRIPT_NAME"]).'/';
57 }
58 $ssl = false;
59 if (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") {
60 $ssl = true;
61 }
62 session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['HTTP_HOST'], $ssl);
39 // Use cookies to store session. 63 // Use cookies to store session.
40 ini_set('session.use_cookies', 1); 64 ini_set('session.use_cookies', 1);
41 // Force cookies for session (phpsessionID forbidden in URL) 65 // Force cookies for session (phpsessionID forbidden in URL)
42 ini_set('session.use_only_cookies', 1); 66 ini_set('session.use_only_cookies', 1);
43 if (!session_id()){ 67 if (!session_id()) {
44 // Prevent php to use sessionID in URL if cookies are disabled. 68 // Prevent php to use sessionID in URL if cookies are disabled.
45 ini_set('session.use_trans_sid', false); 69 ini_set('session.use_trans_sid', false);
46 session_start('poche'); 70 if (!empty(self::$sessionName)) {
71 session_name(self::$sessionName);
72 }
73 session_start();
47 } 74 }
48 } 75 }
49 76
50 // initialize session 77 /**
51 public static function init() 78 * Returns the IP address
79 * (Used to prevent session cookie hijacking.)
80 *
81 * @return string IP addresses
82 */
83 private static function _allIPs()
52 { 84 {
53 if (!isset(self::$_instance)) { 85 $ip = $_SERVER["REMOTE_ADDR"];
54 self::$_instance = new Session(); 86 $ip.= isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? '_'.$_SERVER['HTTP_X_FORWARDED_FOR'] : '';
55 } 87 $ip.= isset($_SERVER['HTTP_CLIENT_IP']) ? '_'.$_SERVER['HTTP_CLIENT_IP'] : '';
56 }
57 88
58 // Returns the IP address, user agent and language of the client 89 return $ip;
59 // (Used to prevent session cookie hijacking.)
60 private static function _allInfos()
61 {
62 $infos = $_SERVER["REMOTE_ADDR"];
63 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
64 $infos.=$_SERVER['HTTP_X_FORWARDED_FOR'];
65 }
66 if (isset($_SERVER['HTTP_CLIENT_IP'])) {
67 $infos.='_'.$_SERVER['HTTP_CLIENT_IP'];
68 }
69 $infos.='_'.$_SERVER['HTTP_USER_AGENT'];
70 $infos.='_'.$_SERVER['HTTP_ACCEPT_LANGUAGE'];
71 return sha1($infos);
72 } 90 }
73 91
74 // Check that user/password is correct and init some SESSION variables. 92 /**
75 public static function login($login,$password,$login_test,$password_test, 93 * Check that user/password is correct and then init some SESSION variables.
76 $pValues = array()) 94 *
95 * @param string $login Login reference
96 * @param string $password Password reference
97 * @param string $loginTest Login to compare with login reference
98 * @param string $passwordTest Password to compare with password reference
99 * @param array $pValues Array of variables to store in SESSION
100 *
101 * @return true|false True if login and password are correct, false
102 * otherwise
103 */
104 public static function login (
105 $login,
106 $password,
107 $loginTest,
108 $passwordTest,
109 $pValues = array())
77 { 110 {
78 foreach ($pValues as $key => $value) { 111 self::banInit();
79 $_SESSION[$key] = $value; 112 if (self::banCanLogin()) {
80 } 113 if ($login === $loginTest && $password === $passwordTest) {
81 if ($login==$login_test && $password==$password_test){ 114 self::banLoginOk();
82 // generate unique random number to sign forms (HMAC) 115 // Generate unique random number to sign forms (HMAC)
83 $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); 116 $_SESSION['uid'] = sha1(uniqid('', true).'_'.mt_rand());
84 $_SESSION['info']=Session::_allInfos(); 117 $_SESSION['ip'] = self::_allIPs();
85 $_SESSION['username']=$login; 118 $_SESSION['username'] = $login;
86 // Set session expiration. 119 // Set session expiration.
87 $_SESSION['expires_on']=time()+Session::$inactivity_timeout; 120 $_SESSION['expires_on'] = time() + self::$inactivityTimeout;
88 return true; 121
122 foreach ($pValues as $key => $value) {
123 $_SESSION[$key] = $value;
124 }
125
126 return true;
127 }
128 self::banLoginFailed();
89 } 129 }
130
90 return false; 131 return false;
91 } 132 }
92 133
93 // Force logout 134 /**
135 * Unset SESSION variable to force logout
136 */
94 public static function logout() 137 public static function logout()
95 { 138 {
96 unset($_SESSION['uid'],$_SESSION['info'],$_SESSION['expires_on'],$_SESSION['tokens'], $_SESSION['login'], $_SESSION['pass'], $_SESSION['poche_user']); 139 unset($_SESSION['uid'], $_SESSION['ip'], $_SESSION['expires_on']);
97 } 140 }
98 141
99 // Make sure user is logged in. 142 /**
143 * Make sure user is logged in.
144 *
145 * @return true|false True if user is logged in, false otherwise
146 */
100 public static function isLogged() 147 public static function isLogged()
101 { 148 {
102 if (!isset ($_SESSION['uid']) 149 if (!isset ($_SESSION['uid'])
103 || $_SESSION['info']!=Session::_allInfos() 150 || (self::$disableSessionProtection === false
104 || time()>=$_SESSION['expires_on']){ 151 && $_SESSION['ip'] !== self::_allIPs())
105 Session::logout(); 152 || time() >= $_SESSION['expires_on']) {
153 self::logout();
154
106 return false; 155 return false;
107 } 156 }
108 // User accessed a page : Update his/her session expiration date. 157 // User accessed a page : Update his/her session expiration date.
109 $_SESSION['expires_on']=time()+Session::$inactivity_timeout; 158 $_SESSION['expires_on'] = time() + self::$inactivityTimeout;
159 if (!empty($_SESSION['longlastingsession'])) {
160 $_SESSION['expires_on'] += $_SESSION['longlastingsession'];
161 }
162
110 return true; 163 return true;
111 } 164 }
112 165
113 // Returns a token. 166 /**
114 public static function getToken() 167 * Create a token, store it in SESSION and return it
168 *
169 * @param string $salt to prevent birthday attack
170 *
171 * @return string Token created
172 */
173 public static function getToken($salt = '')
115 { 174 {
116 if (!isset($_SESSION['tokens'])){ 175 if (!isset($_SESSION['tokens'])) {
117 $_SESSION['tokens']=array(); 176 $_SESSION['tokens']=array();
118 } 177 }
119 // We generate a random string and store it on the server side. 178 // We generate a random string and store it on the server side.
120 $rnd = sha1(uniqid('',true).'_'.mt_rand()); 179 $rnd = sha1(uniqid('', true).'_'.mt_rand().$salt);
121 $_SESSION['tokens'][$rnd]=1; 180 $_SESSION['tokens'][$rnd]=1;
181
122 return $rnd; 182 return $rnd;
123 } 183 }
124 184
125 // Tells if a token is ok. Using this function will destroy the token. 185 /**
126 // return true if token is ok. 186 * Tells if a token is ok. Using this function will destroy the token.
187 *
188 * @param string $token Token to test
189 *
190 * @return true|false True if token is correct, false otherwise
191 */
127 public static function isToken($token) 192 public static function isToken($token)
128 { 193 {
129 if (isset($_SESSION['tokens'][$token])) 194 if (isset($_SESSION['tokens'][$token])) {
130 {
131 unset($_SESSION['tokens'][$token]); // Token is used: destroy it. 195 unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
196
132 return true; // Token is ok. 197 return true; // Token is ok.
133 } 198 }
199
134 return false; // Wrong token, or already used. 200 return false; // Wrong token, or already used.
135 } 201 }
136} \ No newline at end of file 202
203 /**
204 * Signal a failed login. Will ban the IP if too many failures:
205 */
206 public static function banLoginFailed()
207 {
208 if (self::$banFile !== '') {
209 $ip = $_SERVER["REMOTE_ADDR"];
210 $gb = $GLOBALS['IPBANS'];
211
212 if (!isset($gb['FAILURES'][$ip])) {
213 $gb['FAILURES'][$ip] = 0;
214 }
215 $gb['FAILURES'][$ip]++;
216 if ($gb['FAILURES'][$ip] > (self::$banAfter - 1)) {
217 $gb['BANS'][$ip]= time() + self::$banDuration;
218 }
219
220 $GLOBALS['IPBANS'] = $gb;
221 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
222 }
223 }
224
225 /**
226 * Signals a successful login. Resets failed login counter.
227 */
228 public static function banLoginOk()
229 {
230 if (self::$banFile !== '') {
231 $ip = $_SERVER["REMOTE_ADDR"];
232 $gb = $GLOBALS['IPBANS'];
233 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
234 $GLOBALS['IPBANS'] = $gb;
235 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
236 }
237 }
238
239 /**
240 * Ban init
241 */
242 public static function banInit()
243 {
244 if (self::$banFile !== '') {
245 if (!is_file(self::$banFile)) {
246 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(), 'BANS'=>array()), true).";\n?>");
247 }
248 include self::$banFile;
249 }
250 }
251
252 /**
253 * Checks if the user CAN login. If 'true', the user can try to login.
254 *
255 * @return boolean true if user is banned, false otherwise
256 */
257 public static function banCanLogin()
258 {
259 if (self::$banFile !== '') {
260 $ip = $_SERVER["REMOTE_ADDR"];
261 $gb = $GLOBALS['IPBANS'];
262 if (isset($gb['BANS'][$ip])) {
263 // User is banned. Check if the ban has expired:
264 if ($gb['BANS'][$ip] <= time()) {
265 // Ban expired, user can try to login again.
266 unset($gb['FAILURES'][$ip]);
267 unset($gb['BANS'][$ip]);
268 file_put_contents(self::$banFile, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb, true).";\n?>");
269
270 return true; // Ban has expired, user can login.
271 }
272
273 return false; // User is banned.
274 }
275 }
276
277 return true; // User is not banned.
278 }
279}
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 9db4a034..2f0f7038 100644
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -93,6 +93,7 @@ class Poche
93 private function init() 93 private function init()
94 { 94 {
95 Tools::initPhp(); 95 Tools::initPhp();
96 Session::$sessionName = 'poche';
96 Session::init(); 97 Session::init();
97 98
98 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { 99 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
diff --git a/inc/poche/config.inc.php b/inc/poche/config.inc.php
index aaa26af8..9247c292 100755
--- a/inc/poche/config.inc.php
+++ b/inc/poche/config.inc.php
@@ -48,4 +48,12 @@ if (!ini_get('date.timezone') || !@date_default_timezone_set(ini_get('date.timez
48 date_default_timezone_set('UTC'); 48 date_default_timezone_set('UTC');
49} 49}
50 50
51$poche = new Poche(); \ No newline at end of file 51$poche = new Poche();
52
53#XSRF protection with token
54if (!empty($_POST)) {
55 if (!Session::isToken($_POST['token'])) {
56 die(_('Wrong token'));
57 }
58 unset($_SESSION['tokens']);
59} \ No newline at end of file