CVE-2022-2820 (PHP, published 2022-08-15T11:21Z)
Description: value: Session Fixation in GitHub repository namelessmc/nameless prior to v2.0.2.


Severity: nan (CVSS v3 base score: 8.2)
Weakness: CWE-384 \u2014 Session Fixation
Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
Repository: https://github.com/namelessmc/nameless
Files changed:
core/classes/Core/User.php
core/init.php
core/installation/includes/upgrade_perform.php
core/migrations/20220807142446_add_user_session_activity_columns.php
core/migrations/20220807153708_delete_admin_session_table.php
modules/Core/hooks/DeleteUserHook.php
modules/Core/pages/user/settings.php
Commit message: Rework user sessions system (#3000)

Co-authored-by: samerton <samerton@users.noreply.github.com>

Vulnerable code:
 * @version 2.0.0-pr13
                $user = Session::get($this->_sessionName);
                if ($this->find($user, $field)) {
                $user = Session::get($this->_admSessionName);
                if ($user == $this->data()->id && $this->find($user, $field)) {
            $data = $this->_db->get('users', [$field, $value]);
     * @param string $method What column to check for their details in. Can be either `username` or `email`.
        if (!$username && !$password && $this->exists()) {
            Session::put($sessionName, $this->data()->id);
            Session::put($sessionName, $this->data()->id);
            if ($remember) {
                $hash = SecureRandom::alphanumeric();
                $table = $is_admin ? 'users_admin_session' : 'users_session';
                $hashCheck = $this->_db->get($table, ['user_id', $this->data()->id]);

                if (!$hashCheck->count()) {
                    $this->_db->insert($table, [
                        'user_id' => $this->data()->id,
                        'hash' => $hash
                    ]);
                } else {
                    $hash = $hashCheck->first()->hash;
                }
        $this->_db->delete('users_session', ['user_id', $this->data()->id]);
        $this->_db->delete('users_admin_session', ['user_id', $this->data()->id]);
 *  NamelessMC version 2.0.0-pr9
        $hashCheck = DB::getInstance()->get('users_session', ['hash', $hash]);
            $user->login();
// User admin session -> user profile wall replies
// User admin sessions
try {
    $old = $conn->get('nl1_users_admin_session', ['id', '<>', 0]);
    if ($old->count()) {
        $old = $old->results();

        foreach ($old as $item) {
            DB::getInstance()->insert('users_admin_session', [
                'id' => $item->id,
                'user_id' => $item->user_id,
                'hash' => $item->hash
            ]);
        }
    }
} catch (Exception $e) {
    $errors[] = 'Unable to convert user admin sessions: ' . $e->getMessage();
}

// User sessions
try {
    $old = $conn->get('nl1_users_session', ['id', '<>', 0]);
    if ($old->count()) {
        $old = $old->results();

        foreach ($old as $item) {
            DB::getInstance()->insert('users_session', [
                'id' => $item->id,
                'user_id' => $item->user_id,
                'hash' => $item->hash
            ]);
        }
    }
} catch (Exception $e) {
    $errors[] = 'Unable to convert user sessions: ' . $e->getMessage();
}

            $db->delete('users_admin_session', ['user_id', $params['user_id']]);
 *  NamelessMC version 2.0.0-pr13
                                        'email' => Output::getClean($_POST['email'])

Fixed code:
 * @version 2.0.2
                $hash = Session::get($this->_sessionName);
                if ($this->find($hash, 'hash')) {
                $hash = Session::get($this->_admSessionName);
                if ($this->find($hash, 'hash')) {
            if ($field != 'hash') {
                $data = $this->_db->get('users', [$field, $value]);
            } else {
                $data = $this->_db->query('SELECT nl2_users.* FROM nl2_users LEFT JOIN nl2_users_session ON nl2_users.id = user_id WHERE hash = ? AND nl2_users_session.active = 1', [$value]);
            }
     * @param string $method What column to check for their details in. Can be either `username` or `email` or `oauth`.
        if (!$username && $method == 'hash' && $this->exists()) {
            // Logged in using hash from cookie
            Session::put($sessionName, $password);
            $hash = SecureRandom::alphanumeric();

            $this->_db->insert('users_session', [
                'user_id' => $this->data()->id,
                'hash' => $hash,
                'remember_me' => $remember,
                'active' => 1,
                'login_method' => $is_admin ? 'admin' : $method
            ]);
            Session::put($sessionName, $hash);
            if ($remember) {
    /**
     * Log the user out from all other sessions.
     */
    public function logoutAllOtherSessions(): void {
        DB::getInstance()->query('UPDATE nl2_users_session SET `active` = 0 WHERE user_id = ? AND hash != ?', [
            $this->data()->id,
            Session::get(Config::get('session.session_name'))
        ]);
    }

        $this->_db->update('users_session', [['user_id', $this->data()->id], ['hash', Session::get($this->_sessionName)]], [
            'active' => 0
        ]);
        $this->_db->update('users_session', [['user_id', $this->data()->id], ['hash', Session::get($this->_admSessionName)]], [
            'active' => 0
        ]);
 *  NamelessMC version 2.0.2
        $hashCheck = DB::getInstance()->get('users_session', [['hash', $hash], ['active', true]]);
            $user->login(null, $hash, true, 'hash');
<?php
declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class AddUserSessionActivityColumns extends AbstractMigration
{
    public function change(): void
    {
        $table = $this->table('nl2_users_session');
        $table->addColumn('remember_me', 'boolean', ['default' => false]);
        $table->addColumn('active', 'boolean', ['default' => false]);
        $table->addColumn('device_name', 'string', ['length' => 256, 'null' => true, 'default' => null]);
        $table->addColumn('last_seen', 'integer', ['length' => 11, 'null' => true, 'default' => null]);
        $table->addColumn('login_method', 'string', ['length' => 32]);
        $table->addIndex('hash', ['unique' => true]);
        $table->update();

        // Remove old data
        $table->truncate();
    }
}
<?php
declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class DeleteAdminSessionTable extends AbstractMigration
{
    public function change(): void
    {
        $table = $this->table('nl2_users_admin_session');
        $table->drop();
        $table->update();
    }
}
 *  NamelessMC version 2.0.2

                            // Logout all other sessions for this user
                            $user->logoutAllOtherSessions();

                            // Logout all other sessions for this user
                            $user->logoutAllOtherSessions();

                                        'email' => $_POST['email']