Fout gedrag custom session object
Ik maak gebruik van een custom session object om sessie te implementeren. De code is gebaseerd op How to Create a Secure Session Management System in PHP and MySQL
De code werk zonder problemen in php v 5.x, in php v7 is er een probleem met het vernieuwen van de id van de sessie ( session_regenerate_id(true); )
De standaard workarounds (zoals dat door gewijzigd gedrag geen waarde een lege string moeten doorgegeven worden door een strengere typecasting van v7 zijn uiteraard reeds toegepast)
Iemand enig idee wat er mis is ?
De klasse is de volgende:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
<?php
class session {
function __construct() {
// set our custom session functions.
session_set_save_handler(array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc'));
// This line prevents unexpected effects when using objects as save handlers.
register_shutdown_function('session_write_close');
}
function start_session($session_name, $secure) {
// Make sure the session cookie is not accessable via javascript.
$httponly = true;
// Hash algorithm to use for the sessionid. (use hash_algos() to get a list of available hashes.)
$session_hash = 'sha512';
// Check if hash is available
if (in_array($session_hash, hash_algos())) {
// Set the has function.
ini_set('session.hash_function', $session_hash);
}
// How many bits per character of the hash.
// The possible values are '4' (0-9, a-f), '5' (0-9, a-v), and '6' (0-9, a-z, A-Z, "-", ",").
ini_set('session.hash_bits_per_character', 5);
// Force the session to only use cookies, not URL variables.
ini_set('session.use_only_cookies', 1);
// Get session cookie parameters
$cookieParams = session_get_cookie_params();
// Set the parameters
session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly);
// Change the session name
session_name($session_name);
// Now we cat start the session
try
{
// This line regenerates the session and delete the old one.
// It also generates a new encryption key in the database.
session_start();
session_regenerate_id(true);
/* $new_session_id = session_id();
echo "old_session_id:{$old_session_id} " . "<br>\r\n";
echo "new_session_id:{$new_session_id}<br>\r\n";
echo "<br>\r\n";
*/
}
catch(Throwable $t)
{
echo $t->getMessage();
exit;
}
}
function open() {
$host = '***';
$user = '***';
$pass = '***';
$name = '***';
$mysqli = new mysqli($host, $user, $pass, $name);
$this->db = $mysqli;
return true;
}
function close() {
$this->db->close();
return true;
}
function read($id) {
if(!isset($this->read_stmt)) {
$this->read_stmt = $this->db->prepare("SELECT data FROM tblSessies WHERE id = ? LIMIT 1");
}
$this->read_stmt->bind_param('s', $id);
$this->read_stmt->execute();
$this->read_stmt->store_result();
$this->read_stmt->bind_result($data);
$this->read_stmt->fetch();
$key = $this->getkey($id);
$data = $this->decrypt($data, $key);
if($data == null) return "";
return (string) $data;
}
function write($id, $data) {
// Get unique key
$key = $this->getkey($id);
// Encrypt the data
$data = $this->encrypt($data, $key);
$time = time();
if(!isset($this->w_stmt)) {
$this->w_stmt = $this->db->prepare("REPLACE INTO tblSessies (id, set_time, data, session_key) VALUES (?, ?, ?, ?)");
}
$this->w_stmt->bind_param('siss', $id, $time, $data, $key);
$this->w_stmt->execute();
return true;
}
function destroy($id) {
if(!isset($this->delete_stmt)) {
$this->delete_stmt = $this->db->prepare("DELETE FROM tblSessies WHERE id = ?");
}
$this->delete_stmt->bind_param('s', $id);
$this->delete_stmt->execute();
return true;
}
function gc($max) {
if(!isset($this->gc_stmt)) {
$this->gc_stmt = $this->db->prepare("DELETE FROM tblSessies WHERE set_time < ?");
}
$old = time() - $max;
$this->gc_stmt->bind_param('s', $old);
$this->gc_stmt->execute();
return true;
}
private function getkey($id) {
if(!isset($this->key_stmt)) {
$this->key_stmt = $this->db->prepare("SELECT session_key FROM tblSessies WHERE id = ? LIMIT 1");
}
$this->key_stmt->bind_param('s', $id);
$this->key_stmt->execute();
$this->key_stmt->store_result();
if($this->key_stmt->num_rows == 1) {
$this->key_stmt->bind_result($key);
$this->key_stmt->fetch();
return (string) $key;
} else {
$random_key = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
return (string) $random_key;
}
}
private function encrypt($data, $key) {
$salt = '*** some salt ***';
$key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv));
return (string)$encrypted;
}
private function decrypt($data, $key) {
$salt = '*** some salt ***';
$key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($data), MCRYPT_MODE_ECB, $iv);
return (string)$decrypted;
}
function login($email, $password) {
if ($stmt = $this->db->prepare("SELECT id, username, password, salt FROM tblGebruikers WHERE email = ? LIMIT 1"))
{
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password, $salt);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
//echo "$password\n<br>";
if ($stmt->num_rows == 1)
{
// If the user exists we check if the account is locked
// from too many login attempts
if ($this->checkbrute($user_id) == true)
{
// Account is locked
// Send an email to user saying their account is locked
return false;
} else
{
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['userid'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/",
"",
$username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512',
$password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$this->db->query("INSERT INTO tblAanmeldingen(`user_id`, `tijdstip`) VALUES ('$user_id', '$now')");
return false;
}
}
}
else
{
// No user exists.
return false;
}
}
return false;
}
private function checkbrute($user_id)
{
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $this->db->prepare("SELECT tijdstip
FROM tblAanmeldingen
WHERE user_id = ? AND tijdstip > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there have been more than 5 failed logins
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
return true;
}
function userLoggedIn()
{
// Check if all session variables are set
if (isset($_SESSION['userid'], $_SESSION['username'], $_SESSION['login_string']))
{
$user_id = $_SESSION['userid'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
if ($stmt = $this->db->prepare("SELECT password FROM tblGebruikers WHERE id = ? LIMIT 1"))
{
// Bind "$user_id" to parameter.
$stmt->bind_param('i', $user_id);
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if ($stmt->num_rows == 1)
{
// If the user exists get variables from result.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash('sha512', $password . $user_browser);
if ($login_check == $login_string) {
// Logged In!!!!
return true;
}
else
{
// Not logged in
return false;
}
}
else
{
// Not logged in
return false;
}
}
else
{
// Not logged in
return false;
}
}
else
{
// Not logged in
return false;
}
return false;
}
}
?>
class session {
function __construct() {
// set our custom session functions.
session_set_save_handler(array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc'));
// This line prevents unexpected effects when using objects as save handlers.
register_shutdown_function('session_write_close');
}
function start_session($session_name, $secure) {
// Make sure the session cookie is not accessable via javascript.
$httponly = true;
// Hash algorithm to use for the sessionid. (use hash_algos() to get a list of available hashes.)
$session_hash = 'sha512';
// Check if hash is available
if (in_array($session_hash, hash_algos())) {
// Set the has function.
ini_set('session.hash_function', $session_hash);
}
// How many bits per character of the hash.
// The possible values are '4' (0-9, a-f), '5' (0-9, a-v), and '6' (0-9, a-z, A-Z, "-", ",").
ini_set('session.hash_bits_per_character', 5);
// Force the session to only use cookies, not URL variables.
ini_set('session.use_only_cookies', 1);
// Get session cookie parameters
$cookieParams = session_get_cookie_params();
// Set the parameters
session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly);
// Change the session name
session_name($session_name);
// Now we cat start the session
try
{
// This line regenerates the session and delete the old one.
// It also generates a new encryption key in the database.
session_start();
session_regenerate_id(true);
/* $new_session_id = session_id();
echo "old_session_id:{$old_session_id} " . "<br>\r\n";
echo "new_session_id:{$new_session_id}<br>\r\n";
echo "<br>\r\n";
*/
}
catch(Throwable $t)
{
echo $t->getMessage();
exit;
}
}
function open() {
$host = '***';
$user = '***';
$pass = '***';
$name = '***';
$mysqli = new mysqli($host, $user, $pass, $name);
$this->db = $mysqli;
return true;
}
function close() {
$this->db->close();
return true;
}
function read($id) {
if(!isset($this->read_stmt)) {
$this->read_stmt = $this->db->prepare("SELECT data FROM tblSessies WHERE id = ? LIMIT 1");
}
$this->read_stmt->bind_param('s', $id);
$this->read_stmt->execute();
$this->read_stmt->store_result();
$this->read_stmt->bind_result($data);
$this->read_stmt->fetch();
$key = $this->getkey($id);
$data = $this->decrypt($data, $key);
if($data == null) return "";
return (string) $data;
}
function write($id, $data) {
// Get unique key
$key = $this->getkey($id);
// Encrypt the data
$data = $this->encrypt($data, $key);
$time = time();
if(!isset($this->w_stmt)) {
$this->w_stmt = $this->db->prepare("REPLACE INTO tblSessies (id, set_time, data, session_key) VALUES (?, ?, ?, ?)");
}
$this->w_stmt->bind_param('siss', $id, $time, $data, $key);
$this->w_stmt->execute();
return true;
}
function destroy($id) {
if(!isset($this->delete_stmt)) {
$this->delete_stmt = $this->db->prepare("DELETE FROM tblSessies WHERE id = ?");
}
$this->delete_stmt->bind_param('s', $id);
$this->delete_stmt->execute();
return true;
}
function gc($max) {
if(!isset($this->gc_stmt)) {
$this->gc_stmt = $this->db->prepare("DELETE FROM tblSessies WHERE set_time < ?");
}
$old = time() - $max;
$this->gc_stmt->bind_param('s', $old);
$this->gc_stmt->execute();
return true;
}
private function getkey($id) {
if(!isset($this->key_stmt)) {
$this->key_stmt = $this->db->prepare("SELECT session_key FROM tblSessies WHERE id = ? LIMIT 1");
}
$this->key_stmt->bind_param('s', $id);
$this->key_stmt->execute();
$this->key_stmt->store_result();
if($this->key_stmt->num_rows == 1) {
$this->key_stmt->bind_result($key);
$this->key_stmt->fetch();
return (string) $key;
} else {
$random_key = hash('sha512', uniqid(mt_rand(1, mt_getrandmax()), true));
return (string) $random_key;
}
}
private function encrypt($data, $key) {
$salt = '*** some salt ***';
$key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv));
return (string)$encrypted;
}
private function decrypt($data, $key) {
$salt = '*** some salt ***';
$key = substr(hash('sha256', $salt.$key.$salt), 0, 32);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($data), MCRYPT_MODE_ECB, $iv);
return (string)$decrypted;
}
function login($email, $password) {
if ($stmt = $this->db->prepare("SELECT id, username, password, salt FROM tblGebruikers WHERE email = ? LIMIT 1"))
{
$stmt->bind_param('s', $email); // Bind "$email" to parameter.
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
// get variables from result.
$stmt->bind_result($user_id, $username, $db_password, $salt);
$stmt->fetch();
// hash the password with the unique salt.
$password = hash('sha512', $password . $salt);
//echo "$password\n<br>";
if ($stmt->num_rows == 1)
{
// If the user exists we check if the account is locked
// from too many login attempts
if ($this->checkbrute($user_id) == true)
{
// Account is locked
// Send an email to user saying their account is locked
return false;
} else
{
// Check if the password in the database matches
// the password the user submitted.
if ($db_password == $password) {
// Password is correct!
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
// XSS protection as we might print this value
$user_id = preg_replace("/[^0-9]+/", "", $user_id);
$_SESSION['userid'] = $user_id;
// XSS protection as we might print this value
$username = preg_replace("/[^a-zA-Z0-9_\-]+/",
"",
$username);
$_SESSION['username'] = $username;
$_SESSION['login_string'] = hash('sha512',
$password . $user_browser);
// Login successful.
return true;
} else {
// Password is not correct
// We record this attempt in the database
$now = time();
$this->db->query("INSERT INTO tblAanmeldingen(`user_id`, `tijdstip`) VALUES ('$user_id', '$now')");
return false;
}
}
}
else
{
// No user exists.
return false;
}
}
return false;
}
private function checkbrute($user_id)
{
// Get timestamp of current time
$now = time();
// All login attempts are counted from the past 2 hours.
$valid_attempts = $now - (2 * 60 * 60);
if ($stmt = $this->db->prepare("SELECT tijdstip
FROM tblAanmeldingen
WHERE user_id = ? AND tijdstip > '$valid_attempts'")) {
$stmt->bind_param('i', $user_id);
// Execute the prepared query.
$stmt->execute();
$stmt->store_result();
// If there have been more than 5 failed logins
if ($stmt->num_rows > 5) {
return true;
} else {
return false;
}
}
return true;
}
function userLoggedIn()
{
// Check if all session variables are set
if (isset($_SESSION['userid'], $_SESSION['username'], $_SESSION['login_string']))
{
$user_id = $_SESSION['userid'];
$login_string = $_SESSION['login_string'];
$username = $_SESSION['username'];
// Get the user-agent string of the user.
$user_browser = $_SERVER['HTTP_USER_AGENT'];
if ($stmt = $this->db->prepare("SELECT password FROM tblGebruikers WHERE id = ? LIMIT 1"))
{
// Bind "$user_id" to parameter.
$stmt->bind_param('i', $user_id);
$stmt->execute(); // Execute the prepared query.
$stmt->store_result();
if ($stmt->num_rows == 1)
{
// If the user exists get variables from result.
$stmt->bind_result($password);
$stmt->fetch();
$login_check = hash('sha512', $password . $user_browser);
if ($login_check == $login_string) {
// Logged In!!!!
return true;
}
else
{
// Not logged in
return false;
}
}
else
{
// Not logged in
return false;
}
}
else
{
// Not logged in
return false;
}
}
else
{
// Not logged in
return false;
}
return false;
}
}
?>
Gewijzigd op 28/05/2016 22:01:57 door Bart Michiels
Wel handig als je dan vertelt wat dat probleem is. Welke foutmelding krijg je?
Quote:
Error receiving response header (lsphp is killed?): ReceiveResponseHeader: receive pkg hdr failed: ReceivePkgHdr: nothing to read from backend socket
Gewijzigd op 28/05/2016 22:10:34 door Bart Michiels
En welke is dat dan? Want je hebt zojuist veel te veel code gepost om even door te spitten. Handiger dus als je alleen de regel geeft waarop het fout gaat.
regel 50
Gebruik je Joomla?
Neen, de rest van de website is volledig gebouwd met eigen code
Je hebt verder niets in de code gewijzigd? Niet per ongeluk al output verstuurd voordat session_regenerate_id plaatsvindt? Anders zou ik het zo gauw ook niet weten. Een duidelijkere errormelding zou wellicht wat meer info kunnen geven ...
Klinkt als een segfault, van je coredumps? Dan kun je er misschien wat zinnigs van maken.
Ik heb er al behoorlijk wat tijd ingestopt, en al wat wijzigingen aangebracht aan de standaa rdcode ( niveau return values voor typecasting die strenger is vanaf v7 ), op niveau van instellingen van php,... Maar het is blijkbaar een tandenbijter.
Als ik de sessie id verversing track onder een lagere versie werkt alles perfect, maar vanuit het security standpunt dat de sessieid dient te vernieuwen waanneer de rechten worden aangepast ( aanmelden / afmelden ) wens ik dit uiteraard te doen.
Inderdaad jammer dat ik geen foutmelding heb, enkel een error 500 van de apache webserver na de crash van het proces...
Toevoeging op 28/05/2016 23:20:36:
Het script draait bij een webhoster, ik denk dus niet dat ik dumps kan maken ? Ik kan het maandag wel even navragen
Waarom regenereer je het session id tijdens de start? Dit voelt als een actie die mogelijk recursief kan gaan lopen en tot een crash leidt. Probeer de regel eens weg te halen, hij is hoe dan ook niet nodig.
De regel is wel nodig vanuit security standpunt. Zowel het publieke als het private gedeelte van de website werken met sessies. Het is een algemeen gebruik dat wanneer er wijzigingen zijn aan de permissies, hiervoor een nieuwe sessie opzet (session hijacking, session fixation,...)
Ja, maar dan doe je dat niet gedurende de start van je sessie, maar moet dit een afzonderlijke actie zijn, en alleen wanneer dit echt nodig is. Maar goed, het is afwachten wat je hoster ervan zegt, als ze er al wat van zeggen.