Skip to content

Commit f9fb657

Browse files
authored
Merge pull request from GHSA-6cq5-8cj7-g558
Fix session handlers bug
2 parents bd3343a + 3128af9 commit f9fb657

File tree

11 files changed

+131
-39
lines changed

11 files changed

+131
-39
lines changed

system/Session/Handlers/DatabaseHandler.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,18 @@ class DatabaseHandler extends BaseHandler
6060
*/
6161
protected $rowExists = false;
6262

63+
/**
64+
* ID prefix for multiple session cookies
65+
*/
66+
protected string $idPrefix;
67+
6368
/**
6469
* @throws SessionException
6570
*/
6671
public function __construct(AppConfig $config, string $ipAddress)
6772
{
6873
parent::__construct($config, $ipAddress);
74+
6975
$this->table = $config->sessionSavePath;
7076

7177
if (empty($this->table)) {
@@ -77,6 +83,9 @@ public function __construct(AppConfig $config, string $ipAddress)
7783
$this->db = Database::connect($this->DBGroup);
7884

7985
$this->platform = $this->db->getPlatform();
86+
87+
// Add sessionCookieName for multiple session cookies.
88+
$this->idPrefix = $config->sessionCookieName . ':';
8089
}
8190

8291
/**
@@ -115,7 +124,7 @@ public function read($id)
115124
$this->sessionID = $id;
116125
}
117126

118-
$builder = $this->db->table($this->table)->where('id', $id);
127+
$builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
119128

120129
if ($this->matchIP) {
121130
$builder = $builder->where('ip_address', $this->ipAddress);
@@ -182,7 +191,7 @@ public function write($id, $data): bool
182191

183192
if ($this->rowExists === false) {
184193
$insertData = [
185-
'id' => $id,
194+
'id' => $this->idPrefix . $id,
186195
'ip_address' => $this->ipAddress,
187196
'data' => $this->prepareData($data),
188197
];
@@ -197,7 +206,7 @@ public function write($id, $data): bool
197206
return true;
198207
}
199208

200-
$builder = $this->db->table($this->table)->where('id', $id);
209+
$builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
201210

202211
if ($this->matchIP) {
203212
$builder = $builder->where('ip_address', $this->ipAddress);
@@ -242,7 +251,7 @@ public function close(): bool
242251
public function destroy($id): bool
243252
{
244253
if ($this->lock) {
245-
$builder = $this->db->table($this->table)->where('id', $id);
254+
$builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
246255

247256
if ($this->matchIP) {
248257
$builder = $builder->where('ip_address', $this->ipAddress);
@@ -276,7 +285,11 @@ public function gc($max_lifetime)
276285
$separator = ' ';
277286
$interval = implode($separator, ['', "{$max_lifetime} second", '']);
278287

279-
return $this->db->table($this->table)->where('timestamp <', "now() - INTERVAL {$interval}", false)->delete() ? 1 : $this->fail();
288+
return $this->db->table($this->table)->where(
289+
'timestamp <',
290+
"now() - INTERVAL {$interval}",
291+
false
292+
)->delete() ? 1 : $this->fail();
280293
}
281294

282295
/**

system/Session/Handlers/MemcachedHandler.php

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ public function __construct(AppConfig $config, string $ipAddress)
6161
throw SessionException::forEmptySavepath();
6262
}
6363

64+
// Add sessionCookieName for multiple session cookies.
65+
$this->keyPrefix .= $config->sessionCookieName . ':';
66+
6467
if ($this->matchIP === true) {
6568
$this->keyPrefix .= $this->ipAddress . ':';
6669
}
@@ -89,7 +92,14 @@ public function open($path, $name): bool
8992
$serverList[] = $server['host'] . ':' . $server['port'];
9093
}
9194

92-
if (! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->savePath, $matches, PREG_SET_ORDER)) {
95+
if (
96+
! preg_match_all(
97+
'#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#',
98+
$this->savePath,
99+
$matches,
100+
PREG_SET_ORDER
101+
)
102+
) {
93103
$this->memcached = null;
94104
$this->logger->error('Session: Invalid Memcached save path format: ' . $this->savePath);
95105

@@ -99,13 +109,17 @@ public function open($path, $name): bool
99109
foreach ($matches as $match) {
100110
// If Memcached already has this server (or if the port is invalid), skip it
101111
if (in_array($match[1] . ':' . $match[2], $serverList, true)) {
102-
$this->logger->debug('Session: Memcached server pool already has ' . $match[1] . ':' . $match[2]);
112+
$this->logger->debug(
113+
'Session: Memcached server pool already has ' . $match[1] . ':' . $match[2]
114+
);
103115

104116
continue;
105117
}
106118

107119
if (! $this->memcached->addServer($match[1], (int) $match[2], $match[3] ?? 0)) {
108-
$this->logger->error('Could not add ' . $match[1] . ':' . $match[2] . ' to Memcached server pool.');
120+
$this->logger->error(
121+
'Could not add ' . $match[1] . ':' . $match[2] . ' to Memcached server pool.'
122+
);
109123
} else {
110124
$serverList[] = $match[1] . ':' . $match[2];
111125
}
@@ -260,7 +274,9 @@ protected function lockSession(string $sessionID): bool
260274
}
261275

262276
if (! $this->memcached->set($lockKey, Time::now()->getTimestamp(), 300)) {
263-
$this->logger->error('Session: Error while trying to obtain lock for ' . $this->keyPrefix . $sessionID);
277+
$this->logger->error(
278+
'Session: Error while trying to obtain lock for ' . $this->keyPrefix . $sessionID
279+
);
264280

265281
return false;
266282
}
@@ -270,7 +286,9 @@ protected function lockSession(string $sessionID): bool
270286
} while (++$attempt < 30);
271287

272288
if ($attempt === 30) {
273-
$this->logger->error('Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID . ' after 30 attempts, aborting.');
289+
$this->logger->error(
290+
'Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID . ' after 30 attempts, aborting.'
291+
);
274292

275293
return false;
276294
}
@@ -290,7 +308,9 @@ protected function releaseLock(): bool
290308
! $this->memcached->delete($this->lockKey)
291309
&& $this->memcached->getResultCode() !== Memcached::RES_NOTFOUND
292310
) {
293-
$this->logger->error('Session: Error while trying to free lock for ' . $this->lockKey);
311+
$this->logger->error(
312+
'Session: Error while trying to free lock for ' . $this->lockKey
313+
);
294314

295315
return false;
296316
}

system/Session/Handlers/RedisHandler.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ public function __construct(AppConfig $config, string $ipAddress)
7171

7272
$this->setSavePath();
7373

74+
// Add sessionCookieName for multiple session cookies.
75+
$this->keyPrefix .= $config->sessionCookieName . ':';
76+
7477
if ($this->matchIP === true) {
7578
$this->keyPrefix .= $this->ipAddress . ':';
7679
}

user_guide_src/source/changelogs/v4.2.11.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ SECURITY
1313
********
1414

1515
- *Attackers may spoof IP address when using proxy* was fixed. See the `Security advisory GHSA-ghw3-5qvm-3mqc <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-ghw3-5qvm-3mqc>`_ for more information.
16+
- *Potential Session Handlers Vulnerability* was fixed. See the `Security advisory GHSA-6cq5-8cj7-g558 <https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-6cq5-8cj7-g558>`_ for more information.
1617

1718
BREAKING
1819
********
1920

2021
- The ``Config\App::$proxyIPs`` value format has been changed. See :ref:`Upgrading Guide <upgrade-4211-proxyips>`.
22+
- The key of the session data record for :ref:`sessions-databasehandler-driver`,
23+
:ref:`sessions-memcachedhandler-driver` and :ref:`sessions-redishandler-driver`
24+
has changed. See :ref:`Upgrading Guide <upgrade-4211-session-key>`.
2125

2226
Bugs Fixed
2327
**********

user_guide_src/source/installation/upgrade_4211.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,29 @@ The config value format has been changed. Now you must set your proxy IP address
2929

3030
``ConfigException`` will be thrown for old format config value.
3131

32+
.. _upgrade-4211-session-key:
33+
34+
Session Handler Key Changes
35+
===========================
36+
37+
The key of the session data record for :ref:`sessions-databasehandler-driver`,
38+
:ref:`sessions-memcachedhandler-driver` and :ref:`sessions-redishandler-driver`
39+
has changed. Therefore, any existing session data will be invalidated after
40+
the upgrade if you are using these session handlers.
41+
42+
- When using ``DatabaseHandler``, the ``id`` column value in the session table
43+
now contains the session cookie name (``Config\App::$sessionCookieName``).
44+
- When using ``MemcachedHandler`` or ``RedisHandler``, the key value contains
45+
the session cookie name (``Config\App::$sessionCookieName``).
46+
47+
There is maximum length for the ``id`` column and Memcached key (250 bytes).
48+
If the following values exceed those maximum length, the session will not work properly.
49+
50+
- the session cookie name, delimiter, and session id (32 characters by default)
51+
when using ``DatabaseHandler``
52+
- the prefix (``ci_session``), session cookie name, delimiters, and session id
53+
when using ``MemcachedHandler``
54+
3255
Project Files
3356
*************
3457

@@ -46,3 +69,4 @@ many will be simple comments or formatting that have no effect on the runtime:
4669
* app/Config/Toolbar.php
4770
* app/Views/welcome_message.php
4871
* composer.json
72+
* phpunit.xml.dist

user_guide_src/source/libraries/sessions.rst

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ same way:
358358
unusable during the same request after you destroy the session.
359359

360360
You may also use the ``stop()`` method to completely kill the session
361-
by removing the old session_id, destroying all data, and destroying
362-
the cookie that contained the session id:
361+
by removing the old session ID, destroying all data, and destroying
362+
the cookie that contained the session ID:
363363

364364
.. literalinclude:: sessions/038.php
365365

@@ -390,26 +390,35 @@ all of the options and their effects.
390390
You'll find the following Session related preferences in your
391391
**app/Config/App.php** file:
392392

393-
============================== ============================================ ================================================= ============================================================================================
394-
Preference Default Options Description
395-
============================== ============================================ ================================================= ============================================================================================
396-
**sessionDriver** CodeIgniter\\Session\\Handlers\\FileHandler CodeIgniter\\Session\\Handlers\\FileHandler The session storage driver to use.
397-
CodeIgniter\\Session\\Handlers\\DatabaseHandler
398-
CodeIgniter\\Session\\Handlers\\MemcachedHandler
399-
CodeIgniter\\Session\\Handlers\\RedisHandler
400-
CodeIgniter\\Session\\Handlers\\ArrayHandler
401-
**sessionCookieName** ci_session [A-Za-z\_-] characters only The name used for the session cookie.
402-
**sessionExpiration** 7200 (2 hours) Time in seconds (integer) The number of seconds you would like the session to last.
403-
If you would like a non-expiring session (until browser is closed) set the value to zero: 0
404-
**sessionSavePath** null None Specifies the storage location, depends on the driver being used.
405-
**sessionMatchIP** false true/false (boolean) Whether to validate the user's IP address when reading the session cookie.
406-
Note that some ISPs dynamically changes the IP, so if you want a non-expiring session you
407-
will likely set this to false.
408-
**sessionTimeToUpdate** 300 Time in seconds (integer) This option controls how often the session class will regenerate itself and create a new
409-
session ID. Setting it to 0 will disable session ID regeneration.
410-
**sessionRegenerateDestroy** false true/false (boolean) Whether to destroy session data associated with the old session ID when auto-regenerating
411-
the session ID. When set to false, the data will be later deleted by the garbage collector.
412-
============================== ============================================ ================================================= ============================================================================================
393+
============================== ================== =========================== ============================================================
394+
Preference Default Options Description
395+
============================== ================== =========================== ============================================================
396+
**sessionDriver** FileHandler::class FileHandler::class The session storage driver to use.
397+
DatabaseHandler::class All the session drivers are located in the
398+
MemcachedHandler::class ``CodeIgniter\Session\Handlers\`` namespace.
399+
RedisHandler::class
400+
ArrayHandler::class
401+
**sessionCookieName** ci_session [A-Za-z\_-] characters only The name used for the session cookie.
402+
The value will be included in the key of the
403+
Database/Memcached/Redis session records. So, set the value
404+
so that it does not exceed the maximum length of the key.
405+
**sessionExpiration** 7200 (2 hours) Time in seconds (integer) The number of seconds you would like the session to last.
406+
If you would like a non-expiring session (until browser is
407+
closed) set the value to zero: 0
408+
**sessionSavePath** null None Specifies the storage location, depends on the driver being
409+
used.
410+
**sessionMatchIP** false true/false (boolean) Whether to validate the user's IP address when reading the
411+
session cookie. Note that some ISPs dynamically changes the IP,
412+
so if you want a non-expiring session you will likely set this
413+
to false.
414+
**sessionTimeToUpdate** 300 Time in seconds (integer) This option controls how often the session class will
415+
regenerate itself and create a new session ID. Setting it to 0
416+
will disable session ID regeneration.
417+
**sessionRegenerateDestroy** false true/false (boolean) Whether to destroy session data associated with the old
418+
session ID when auto-regenerating
419+
the session ID. When set to false, the data will be later
420+
deleted by the garbage collector.
421+
============================== ================== =========================== ============================================================
413422

414423
.. note:: As a last resort, the Session library will try to fetch PHP's
415424
session related INI settings, as well as legacy CI settings such as
@@ -498,9 +507,9 @@ permissions will probably break your application.
498507
Instead, you should do something like this, depending on your environment
499508
::
500509

501-
mkdir /<path to your application directory>/Writable/sessions/
502-
chmod 0700 /<path to your application directory>/Writable/sessions/
503-
chown www-data /<path to your application directory>/Writable/sessions/
510+
> mkdir /<path to your application directory>/writable/sessions/
511+
> chmod 0700 /<path to your application directory>/writable/sessions/
512+
> chown www-data /<path to your application directory>/writable/sessions/
504513

505514
Bonus Tip
506515
---------
@@ -518,6 +527,8 @@ In addition, if performance is your only concern, you may want to look
518527
into using `tmpfs <https://eddmann.com/posts/storing-php-sessions-file-caches-in-memory-using-tmpfs/>`_,
519528
(warning: external resource), which can make your sessions blazing fast.
520529

530+
.. _sessions-databasehandler-driver:
531+
521532
DatabaseHandler Driver
522533
======================
523534

@@ -561,6 +572,10 @@ For PostgreSQL::
561572

562573
CREATE INDEX "ci_sessions_timestamp" ON "ci_sessions" ("timestamp");
563574

575+
.. note:: The ``id`` value contains the session cookie name (``Config\App::$sessionCookieName``)
576+
and the session ID and a delimiter. It should be increased as needed, for example,
577+
when using long session IDs.
578+
564579
You will also need to add a PRIMARY KEY **depending on your 'sessionMatchIP'
565580
setting**. The examples below work both on MySQL and PostgreSQL::
566581

@@ -595,6 +610,8 @@ when it generates the code.
595610
done processing session data if you're having performance
596611
issues.
597612

613+
.. _sessions-redishandler-driver:
614+
598615
RedisHandler Driver
599616
===================
600617

@@ -631,6 +648,8 @@ sufficient:
631648

632649
.. literalinclude:: sessions/041.php
633650

651+
.. _sessions-memcachedhandler-driver:
652+
634653
MemcachedHandler Driver
635654
=======================
636655

user_guide_src/source/libraries/sessions/039.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
class App extends BaseConfig
88
{
9-
public $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler';
9+
// ...
10+
public $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler';
11+
// ...
1012
public $sessionSavePath = 'ci_sessions';
1113
// ...
1214
}

user_guide_src/source/libraries/sessions/040.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
class App extends BaseConfig
88
{
9+
// ...
910
public $sessionDBGroup = 'groupName';
1011
// ...
1112
}

user_guide_src/source/libraries/sessions/041.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
class App extends BaseConfig
88
{
9-
public $sessionDiver = 'CodeIgniter\Session\Handlers\RedisHandler';
9+
// ...
10+
public $sessionDiver = 'CodeIgniter\Session\Handlers\RedisHandler';
11+
// ...
1012
public $sessionSavePath = 'tcp://localhost:6379';
1113
// ...
1214
}

user_guide_src/source/libraries/sessions/042.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
class App extends BaseConfig
88
{
9-
public $sessionDriver = 'CodeIgniter\Session\Handlers\MemcachedHandler';
9+
// ...
10+
public $sessionDriver = 'CodeIgniter\Session\Handlers\MemcachedHandler';
11+
// ...
1012
public $sessionSavePath = 'localhost:11211';
1113
// ...
1214
}

user_guide_src/source/libraries/sessions/043.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
class App extends BaseConfig
88
{
9+
// ...
10+
911
// localhost will be given higher priority (5) here,
1012
// compared to 192.0.2.1 with a weight of 1.
1113
public $sessionSavePath = 'localhost:11211:5,192.0.2.1:11211:1';

0 commit comments

Comments
 (0)