VYPR
High severityNVD Advisory· Published Dec 22, 2022· Updated Apr 15, 2025

CodeIgniter is vulnerable to improper authentication via Session Handlers

CVE-2022-46170

Description

CodeIgniter is a PHP full-stack web framework. When an application uses (1) multiple session cookies (e.g., one for user pages and one for admin pages) and (2) a session handler is set to DatabaseHandler, MemcachedHandler, or RedisHandler, then if an attacker gets one session cookie (e.g., one for user pages), they may be able to access pages that require another session cookie (e.g., for admin pages). This issue has been patched, please upgrade to version 4.2.11 or later. As a workaround, use only one session cookie.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CVE-2022-46170: In CodeIgniter 4 before 4.2.11, using multiple session cookies with database/memcached/redis handlers lets an attacker with one cookie access pages requiring another.

Root

Cause CodeIgniter 4 applications that use multiple session cookies (e.g., separate cookies for user and admin pages) combined with a session handler set to DatabaseHandler, MemcachedHandler, or RedisHandler are vulnerable. The session handling logic does not properly isolate sessions when multiple cookies are used, allowing a session stored for one cookie to be reused for another [3].

Exploitation

An attacker who obtains a single session cookie (for example, one that grants access to standard user pages) can reuse it to access pages that require a different session cookie (such as admin pages). The attack does not require authentication beyond the stolen cookie, and the attacker only needs network access to the application. The vulnerability is present regardless of whether the session ID is regenerated [1][3].

Impact

Successful exploitation allows an attacker to escalate privileges by bypassing the intended separation of session contexts. If an application relies on distinct session cookies to enforce different authorization levels (e.g., user vs. admin), an attacker with one cookie can effectively gain the privileges associated with another cookie, leading to unauthorized access to sensitive functionality and data [3].

Mitigation

The vulnerability is patched in CodeIgniter 4.2.11 and later [3]. Users should upgrade immediately. As a workaround, applications may use only one session cookie to eliminate the possibility of cross-cookie session confusion [3].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
codeigniter4/frameworkPackagist
< 4.2.114.2.11

Affected products

3

Patches

1
f9fb6574fbeb

Merge pull request from GHSA-6cq5-8cj7-g558

11 files changed · +131 39
  • system/Session/Handlers/DatabaseHandler.php+18 5 modified
    @@ -60,12 +60,18 @@ class DatabaseHandler extends BaseHandler
          */
         protected $rowExists = false;
     
    +    /**
    +     * ID prefix for multiple session cookies
    +     */
    +    protected string $idPrefix;
    +
         /**
          * @throws SessionException
          */
         public function __construct(AppConfig $config, string $ipAddress)
         {
             parent::__construct($config, $ipAddress);
    +
             $this->table = $config->sessionSavePath;
     
             if (empty($this->table)) {
    @@ -77,6 +83,9 @@ public function __construct(AppConfig $config, string $ipAddress)
             $this->db = Database::connect($this->DBGroup);
     
             $this->platform = $this->db->getPlatform();
    +
    +        // Add sessionCookieName for multiple session cookies.
    +        $this->idPrefix = $config->sessionCookieName . ':';
         }
     
         /**
    @@ -115,7 +124,7 @@ public function read($id)
                 $this->sessionID = $id;
             }
     
    -        $builder = $this->db->table($this->table)->where('id', $id);
    +        $builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
     
             if ($this->matchIP) {
                 $builder = $builder->where('ip_address', $this->ipAddress);
    @@ -182,7 +191,7 @@ public function write($id, $data): bool
     
             if ($this->rowExists === false) {
                 $insertData = [
    -                'id'         => $id,
    +                'id'         => $this->idPrefix . $id,
                     'ip_address' => $this->ipAddress,
                     'data'       => $this->prepareData($data),
                 ];
    @@ -197,7 +206,7 @@ public function write($id, $data): bool
                 return true;
             }
     
    -        $builder = $this->db->table($this->table)->where('id', $id);
    +        $builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
     
             if ($this->matchIP) {
                 $builder = $builder->where('ip_address', $this->ipAddress);
    @@ -242,7 +251,7 @@ public function close(): bool
         public function destroy($id): bool
         {
             if ($this->lock) {
    -            $builder = $this->db->table($this->table)->where('id', $id);
    +            $builder = $this->db->table($this->table)->where('id', $this->idPrefix . $id);
     
                 if ($this->matchIP) {
                     $builder = $builder->where('ip_address', $this->ipAddress);
    @@ -276,7 +285,11 @@ public function gc($max_lifetime)
             $separator = ' ';
             $interval  = implode($separator, ['', "{$max_lifetime} second", '']);
     
    -        return $this->db->table($this->table)->where('timestamp <', "now() - INTERVAL {$interval}", false)->delete() ? 1 : $this->fail();
    +        return $this->db->table($this->table)->where(
    +            'timestamp <',
    +            "now() - INTERVAL {$interval}",
    +            false
    +        )->delete() ? 1 : $this->fail();
         }
     
         /**
    
  • system/Session/Handlers/MemcachedHandler.php+26 6 modified
    @@ -61,6 +61,9 @@ public function __construct(AppConfig $config, string $ipAddress)
                 throw SessionException::forEmptySavepath();
             }
     
    +        // Add sessionCookieName for multiple session cookies.
    +        $this->keyPrefix .= $config->sessionCookieName . ':';
    +
             if ($this->matchIP === true) {
                 $this->keyPrefix .= $this->ipAddress . ':';
             }
    @@ -89,7 +92,14 @@ public function open($path, $name): bool
                 $serverList[] = $server['host'] . ':' . $server['port'];
             }
     
    -        if (! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->savePath, $matches, PREG_SET_ORDER)) {
    +        if (
    +            ! preg_match_all(
    +                '#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#',
    +                $this->savePath,
    +                $matches,
    +                PREG_SET_ORDER
    +            )
    +        ) {
                 $this->memcached = null;
                 $this->logger->error('Session: Invalid Memcached save path format: ' . $this->savePath);
     
    @@ -99,13 +109,17 @@ public function open($path, $name): bool
             foreach ($matches as $match) {
                 // If Memcached already has this server (or if the port is invalid), skip it
                 if (in_array($match[1] . ':' . $match[2], $serverList, true)) {
    -                $this->logger->debug('Session: Memcached server pool already has ' . $match[1] . ':' . $match[2]);
    +                $this->logger->debug(
    +                    'Session: Memcached server pool already has ' . $match[1] . ':' . $match[2]
    +                );
     
                     continue;
                 }
     
                 if (! $this->memcached->addServer($match[1], (int) $match[2], $match[3] ?? 0)) {
    -                $this->logger->error('Could not add ' . $match[1] . ':' . $match[2] . ' to Memcached server pool.');
    +                $this->logger->error(
    +                    'Could not add ' . $match[1] . ':' . $match[2] . ' to Memcached server pool.'
    +                );
                 } else {
                     $serverList[] = $match[1] . ':' . $match[2];
                 }
    @@ -260,7 +274,9 @@ protected function lockSession(string $sessionID): bool
                 }
     
                 if (! $this->memcached->set($lockKey, Time::now()->getTimestamp(), 300)) {
    -                $this->logger->error('Session: Error while trying to obtain lock for ' . $this->keyPrefix . $sessionID);
    +                $this->logger->error(
    +                    'Session: Error while trying to obtain lock for ' . $this->keyPrefix . $sessionID
    +                );
     
                     return false;
                 }
    @@ -270,7 +286,9 @@ protected function lockSession(string $sessionID): bool
             } while (++$attempt < 30);
     
             if ($attempt === 30) {
    -            $this->logger->error('Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID . ' after 30 attempts, aborting.');
    +            $this->logger->error(
    +                'Session: Unable to obtain lock for ' . $this->keyPrefix . $sessionID . ' after 30 attempts, aborting.'
    +            );
     
                 return false;
             }
    @@ -290,7 +308,9 @@ protected function releaseLock(): bool
                     ! $this->memcached->delete($this->lockKey)
                     && $this->memcached->getResultCode() !== Memcached::RES_NOTFOUND
                 ) {
    -                $this->logger->error('Session: Error while trying to free lock for ' . $this->lockKey);
    +                $this->logger->error(
    +                    'Session: Error while trying to free lock for ' . $this->lockKey
    +                );
     
                     return false;
                 }
    
  • system/Session/Handlers/RedisHandler.php+3 0 modified
    @@ -71,6 +71,9 @@ public function __construct(AppConfig $config, string $ipAddress)
     
             $this->setSavePath();
     
    +        // Add sessionCookieName for multiple session cookies.
    +        $this->keyPrefix .= $config->sessionCookieName . ':';
    +
             if ($this->matchIP === true) {
                 $this->keyPrefix .= $this->ipAddress . ':';
             }
    
  • user_guide_src/source/changelogs/v4.2.11.rst+4 0 modified
    @@ -13,11 +13,15 @@ SECURITY
     ********
     
     - *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.
    +- *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.
     
     BREAKING
     ********
     
     - The ``Config\App::$proxyIPs`` value format has been changed. See :ref:`Upgrading Guide <upgrade-4211-proxyips>`.
    +- The key of the session data record for :ref:`sessions-databasehandler-driver`,
    +  :ref:`sessions-memcachedhandler-driver` and :ref:`sessions-redishandler-driver`
    +  has changed. See :ref:`Upgrading Guide <upgrade-4211-session-key>`.
     
     Bugs Fixed
     **********
    
  • user_guide_src/source/installation/upgrade_4211.rst+24 0 modified
    @@ -29,6 +29,29 @@ The config value format has been changed. Now you must set your proxy IP address
     
     ``ConfigException`` will be thrown for old format config value.
     
    +.. _upgrade-4211-session-key:
    +
    +Session Handler Key Changes
    +===========================
    +
    +The key of the session data record for :ref:`sessions-databasehandler-driver`,
    +:ref:`sessions-memcachedhandler-driver` and :ref:`sessions-redishandler-driver`
    +has changed. Therefore, any existing session data will be invalidated after
    +the upgrade if you are using these session handlers.
    +
    +- When using ``DatabaseHandler``, the ``id`` column value in the session table
    +  now contains the session cookie name (``Config\App::$sessionCookieName``).
    +- When using ``MemcachedHandler`` or ``RedisHandler``, the key value contains
    +  the session cookie name (``Config\App::$sessionCookieName``).
    +
    +There is maximum length for the ``id`` column and Memcached key (250 bytes).
    +If the following values exceed those maximum length, the session will not work properly.
    +
    +- the session cookie name, delimiter, and session id (32 characters by default)
    +  when using ``DatabaseHandler``
    +- the prefix (``ci_session``), session cookie name, delimiters, and session id
    +  when using  ``MemcachedHandler``
    +
     Project Files
     *************
     
    @@ -46,3 +69,4 @@ many will be simple comments or formatting that have no effect on the runtime:
     * app/Config/Toolbar.php
     * app/Views/welcome_message.php
     * composer.json
    +* phpunit.xml.dist
    
  • user_guide_src/source/libraries/sessions/039.php+3 1 modified
    @@ -6,7 +6,9 @@
     
     class App extends BaseConfig
     {
    -    public $sessionDriver   = 'CodeIgniter\Session\Handlers\DatabaseHandler';
    +    // ...
    +    public $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler';
    +    // ...
         public $sessionSavePath = 'ci_sessions';
         // ...
     }
    
  • user_guide_src/source/libraries/sessions/040.php+1 0 modified
    @@ -6,6 +6,7 @@
     
     class App extends BaseConfig
     {
    +    // ...
         public $sessionDBGroup = 'groupName';
         // ...
     }
    
  • user_guide_src/source/libraries/sessions/041.php+3 1 modified
    @@ -6,7 +6,9 @@
     
     class App extends BaseConfig
     {
    -    public $sessionDiver    = 'CodeIgniter\Session\Handlers\RedisHandler';
    +    // ...
    +    public $sessionDiver = 'CodeIgniter\Session\Handlers\RedisHandler';
    +    // ...
         public $sessionSavePath = 'tcp://localhost:6379';
         // ...
     }
    
  • user_guide_src/source/libraries/sessions/042.php+3 1 modified
    @@ -6,7 +6,9 @@
     
     class App extends BaseConfig
     {
    -    public $sessionDriver   = 'CodeIgniter\Session\Handlers\MemcachedHandler';
    +    // ...
    +    public $sessionDriver = 'CodeIgniter\Session\Handlers\MemcachedHandler';
    +    // ...
         public $sessionSavePath = 'localhost:11211';
         // ...
     }
    
  • user_guide_src/source/libraries/sessions/043.php+2 0 modified
    @@ -6,6 +6,8 @@
     
     class App extends BaseConfig
     {
    +    // ...
    +
         // localhost will be given higher priority (5) here,
         // compared to 192.0.2.1 with a weight of 1.
         public $sessionSavePath = 'localhost:11211:5,192.0.2.1:11211:1';
    
  • user_guide_src/source/libraries/sessions.rst+44 25 modified
    @@ -358,8 +358,8 @@ same way:
         unusable during the same request after you destroy the session.
     
     You may also use the ``stop()`` method to completely kill the session
    -by removing the old session_id, destroying all data, and destroying
    -the cookie that contained the session id:
    +by removing the old session ID, destroying all data, and destroying
    +the cookie that contained the session ID:
     
     .. literalinclude:: sessions/038.php
     
    @@ -390,26 +390,35 @@ all of the options and their effects.
     You'll find the following Session related preferences in your
     **app/Config/App.php** file:
     
    -============================== ============================================ ================================================= ============================================================================================
    -Preference                     Default                                      Options                                           Description
    -============================== ============================================ ================================================= ============================================================================================
    -**sessionDriver**              CodeIgniter\\Session\\Handlers\\FileHandler  CodeIgniter\\Session\\Handlers\\FileHandler       The session storage driver to use.
    -                                                                            CodeIgniter\\Session\\Handlers\\DatabaseHandler
    -                                                                            CodeIgniter\\Session\\Handlers\\MemcachedHandler
    -                                                                            CodeIgniter\\Session\\Handlers\\RedisHandler
    -                                                                            CodeIgniter\\Session\\Handlers\\ArrayHandler
    -**sessionCookieName**          ci_session                                   [A-Za-z\_-] characters only                       The name used for the session cookie.
    -**sessionExpiration**          7200 (2 hours)                               Time in seconds (integer)                         The number of seconds you would like the session to last.
    -                                                                                                                              If you would like a non-expiring session (until browser is closed) set the value to zero: 0
    -**sessionSavePath**            null                                         None                                              Specifies the storage location, depends on the driver being used.
    -**sessionMatchIP**             false                                        true/false (boolean)                              Whether to validate the user's IP address when reading the session cookie.
    -                                                                                                                              Note that some ISPs dynamically changes the IP, so if you want a non-expiring session you
    -                                                                                                                              will likely set this to false.
    -**sessionTimeToUpdate**        300                                          Time in seconds (integer)                         This option controls how often the session class will regenerate itself and create a new
    -                                                                                                                              session ID. Setting it to 0 will disable session ID regeneration.
    -**sessionRegenerateDestroy**   false                                        true/false (boolean)                              Whether to destroy session data associated with the old session ID when auto-regenerating
    -                                                                                                                              the session ID. When set to false, the data will be later deleted by the garbage collector.
    -============================== ============================================ ================================================= ============================================================================================
    +============================== ================== =========================== ============================================================
    +Preference                     Default            Options                     Description
    +============================== ================== =========================== ============================================================
    +**sessionDriver**              FileHandler::class FileHandler::class          The session storage driver to use.
    +                                                  DatabaseHandler::class      All the session drivers are located in the
    +                                                  MemcachedHandler::class     ``CodeIgniter\Session\Handlers\`` namespace.
    +                                                  RedisHandler::class
    +                                                  ArrayHandler::class
    +**sessionCookieName**          ci_session         [A-Za-z\_-] characters only The name used for the session cookie.
    +                                                                              The value will be included in the key of the
    +                                                                              Database/Memcached/Redis session records. So, set the value
    +                                                                              so that it does not exceed the maximum length of the key.
    +**sessionExpiration**          7200 (2 hours)     Time in seconds (integer)   The number of seconds you would like the session to last.
    +                                                                              If you would like a non-expiring session (until browser is
    +                                                                              closed) set the value to zero: 0
    +**sessionSavePath**            null               None                        Specifies the storage location, depends on the driver being
    +                                                                              used.
    +**sessionMatchIP**             false              true/false (boolean)        Whether to validate the user's IP address when reading the
    +                                                                              session cookie. Note that some ISPs dynamically changes the IP,
    +                                                                              so if you want a non-expiring session you will likely set this
    +                                                                              to false.
    +**sessionTimeToUpdate**        300                Time in seconds (integer)   This option controls how often the session class will
    +                                                                              regenerate itself and create a new session ID. Setting it to 0
    +                                                                              will disable session ID regeneration.
    +**sessionRegenerateDestroy**   false              true/false (boolean)        Whether to destroy session data associated with the old
    +                                                                              session ID when auto-regenerating
    +                                                                              the session ID. When set to false, the data will be later
    +                                                                              deleted by the garbage collector.
    +============================== ================== =========================== ============================================================
     
     .. note:: As a last resort, the Session library will try to fetch PHP's
         session related INI settings, as well as legacy CI settings such as
    @@ -498,9 +507,9 @@ permissions will probably break your application.
     Instead, you should do something like this, depending on your environment
     ::
     
    -    mkdir /<path to your application directory>/Writable/sessions/
    -    chmod 0700 /<path to your application directory>/Writable/sessions/
    -    chown www-data /<path to your application directory>/Writable/sessions/
    +    > mkdir /<path to your application directory>/writable/sessions/
    +    > chmod 0700 /<path to your application directory>/writable/sessions/
    +    > chown www-data /<path to your application directory>/writable/sessions/
     
     Bonus Tip
     ---------
    @@ -518,6 +527,8 @@ In addition, if performance is your only concern, you may want to look
     into using `tmpfs <https://eddmann.com/posts/storing-php-sessions-file-caches-in-memory-using-tmpfs/>`_,
     (warning: external resource), which can make your sessions blazing fast.
     
    +.. _sessions-databasehandler-driver:
    +
     DatabaseHandler Driver
     ======================
     
    @@ -561,6 +572,10 @@ For PostgreSQL::
     
         CREATE INDEX "ci_sessions_timestamp" ON "ci_sessions" ("timestamp");
     
    +.. note:: The ``id`` value contains the session cookie name (``Config\App::$sessionCookieName``)
    +    and the session ID and a delimiter. It should be increased as needed, for example,
    +    when using long session IDs.
    +
     You will also need to add a PRIMARY KEY **depending on your 'sessionMatchIP'
     setting**. The examples below work both on MySQL and PostgreSQL::
     
    @@ -595,6 +610,8 @@ when it generates the code.
         done processing session data if you're having performance
         issues.
     
    +.. _sessions-redishandler-driver:
    +
     RedisHandler Driver
     ===================
     
    @@ -631,6 +648,8 @@ sufficient:
     
     .. literalinclude:: sessions/041.php
     
    +.. _sessions-memcachedhandler-driver:
    +
     MemcachedHandler Driver
     =======================
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.