CVE-2023-1545 (PHP, published 2023-03-21T11:15Z)
Description: value: SQL Injection in GitHub repository nilsteampassnet/teampass prior to 3.0.0.23.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-89 \u2014 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
Repository: https://github.com/nilsteampassnet/teampass
Files changed:
api/Model/AuthModel.php
Commit message: 3.0.0.22

Fix CWE-89: SQL Injection in API (Sonia Zorba)


Vulnerable code:
        // Check if user exists
        $userInfoRes = $this->select("SELECT id, pw, public_key, private_key, personal_folder, fonction_id, groupes_visibles, groupes_interdits, user_api_key FROM " . prefixTable('users') . " WHERE login='".$login."'");
        $userInfoRes[0]['special'] = '';
        $userInfo = $userInfoRes[0];
        // Check password
        include_once API_ROOT_PATH . '/../sources/SplClassLoader.php';
        $pwdlib = new SplClassLoader('PasswordLib', API_ROOT_PATH . '/../includes/libraries');
        $pwdlib->register();
        $pwdlib = new PasswordLib\PasswordLib();
        if ($pwdlib->verifyPasswordHash($password, $userInfo['pw']) === true) {
            // Correct credentials
            // Now check apikey
            // We check if it is the correct user api or if it is a generic api key
            $apiInfo = $this->select("SELECT count(*) FROM " . prefixTable('api') . " WHERE value='".$apikey."'");
            if ($apikey === $userInfo['user_api_key'] || (int) $apiInfo[0]['count(*)'] === 1) {
                $privateKeyClear = decryptPrivateKey($password, (string) $userInfo['private_key']); //prepareUserEncryptionKeys($userInfo, $password);
                    $login,
                return ["error" => "Login failed.", "apikey" => "Not valid"];
        } else {
            return ["error" => "Login failed.", "password" => $password];

Fixed code:
        // Sanitize
        include_once API_ROOT_PATH . '/../sources/main.functions.php';
        $inputData = dataSanitizer(
            [
                'login' => isset($login) === true ? $login : '',
                'password' => isset($password) === true ? $password : '',
                'apikey' => isset($apikey) === true ? $apikey : '',
            ],
            [
                'login' => 'trim|escape',
                'password' => 'trim|escape',
                'apikey' => 'trim|escape',
            ],
            API_ROOT_PATH . '/..'
        );
        if (empty($inputData['login']) === true || empty($inputData['apikey']) === true) {
            return ["error" => "Login failed."];
        }
        // Check apikey
        if (empty($inputData['password']) === true) {
            // case where it is a generic key
            $apiInfo = $this->select("SELECT count(*) FROM " . prefixTable('api') . " WHERE value='".$inputData['apikey']."' AND label='".$inputData['login']."'");
            if ((int) $apiInfo[0]['count(*)'] === 0) {
                return ["error" => "Login failed.", "apikey" => "Not valid"];
            }

            return ["error" => "Not managed."];
        } else {
            // case where it is a user api key
            $apiInfo = $this->select("SELECT count(*) FROM " . prefixTable('users') . " WHERE user_api_key='".$inputData['apikey']."' AND login='".$inputData['login']."'");
            if ((int) $apiInfo[0]['count(*)'] === 0) {
                return ["error" => "Login failed.", "apikey" => "Not valid"];
            }

            // Check if user exists
            $userInfoRes = $this->select("SELECT id, pw, public_key, private_key, personal_folder, fonction_id, groupes_visibles, groupes_interdits, user_api_key FROM " . prefixTable('users') . " WHERE login='".$inputData['login']."'");
            $userInfoRes[0]['special'] = '';
            $userInfo = $userInfoRes[0];
            
            // Check password
            include_once API_ROOT_PATH . '/../sources/SplClassLoader.php';
            $pwdlib = new SplClassLoader('PasswordLib', API_ROOT_PATH . '/../includes/libraries');
            $pwdlib->register();
            $pwdlib = new PasswordLib\PasswordLib();
            if ($pwdlib->verifyPasswordHash($inputData['password'], $userInfo['pw']) === true) {
                // Correct credentials
                $privateKeyClear = decryptPrivateKey($inputData['password'], (string) $userInfo['private_key']);
                    $inputData['login'],
                return ["error" => "Login failed.", "password" => "Not valid"];