CVE-2023-1545: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: SQL Injection in GitHub repository nilsteampassnet/teampass prior to 3.0.0.23.
CWE description: 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.
CVSS v3 base score: 7.5
Published: 2023-03-21T11:15Z
Repository: https://github.com/nilsteampassnet/teampass
Language: PHP
Files changed: api/Model/AuthModel.php
Commit date: 2023-02-09 18:46:52 +0100
Commit message: 3.0.0.22

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

Diff: diff --git a/api/Model/AuthModel.php b/api/Model/AuthModel.php
index dcc37ec8a..6d97c0e25 100755
--- a/api/Model/AuthModel.php
+++ b/api/Model/AuthModel.php
@@ -39,46 +39,75 @@ class AuthModel extends Database
      * @param string $apikey
      * @return array
      */
     public function getUserAuth(string $login, string $password, string $apikey): array
     {
-        // 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];
+        // 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 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) {
+        // 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
                 // get user keys
-                $privateKeyClear = decryptPrivateKey($password, (string) $userInfo['private_key']); //prepareUserEncryptionKeys($userInfo, $password);
+                $privateKeyClear = decryptPrivateKey($inputData['password'], (string) $userInfo['private_key']);
 
                 // get user folders list
                 $folders = $this->buildUserFoldersList($userInfo);
 
                 // create JWT
                 return $this->createUserJWT(
                     $userInfo['id'],
-                    $login,
+                    $inputData['login'],
                     $userInfo['personal_folder'],
                     $userInfo['public_key'],
                     $privateKeyClear,
                     implode(",", $folders)
                 );
             } else {
-                return ["error" => "Login failed.", "apikey" => "Not valid"];
+                return ["error" => "Login failed.", "password" => "Not valid"];
             }
-        } else {
-            return ["error" => "Login failed.", "password" => $password];
         }
     }
     //end getUserAuth
 
     /**

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"];