Correctif TeamPass 2.1.21

De EjnTricks
Révision de 28 avril 2016 à 18:49 par Etienne (discussion | contributions)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

Lors des essais de mises à jour vers la version 2.1.21, des bugs ont été mis en évidence. Cet article présente la mise en place de correctifs / patchs afin d'y remédier. Lors de l'installation, la version était trop ancienne pour demander un correctif officiel.


Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Copy-icon.png Mise en place

Une copie de l'instance est réalisée sous le répertoire /var/opt/teampass/2.1.21.1 afin d'y apporter les divers correctifs / patchs.

#sudo cp -p -R /var/opt/teampass/2.1.21 /var/opt/teampass/2.1.21.1
#sudo rm /var/opt/teampass/teampass
#sudo ln -s /var/opt/teampass/2.1.21.1 /var/opt/teampass/teampass
#sudo chown -h www-data:www-data /var/opt/teampass/teampass

Le lien /var/opt/teampass/teampass afin que la configuration Apache pointe sur cette version.


Bug-icon.png Correctifs

Script upgrade.js

La procédure de mise à jour s'effectue depuis l'URL install/upgrade.php. Or le code Javascript de cette page fait appel à une fonction, pour exécuter des requêtes HTTP, qui n'est pas présente. Pour cette version de l'application, le fichier install/upgrade.js est manquant et non référencé dans le fichier php.

Afin d'y remédier, le fichier est récupéré depuis une version plus récente, possible car lors de la rédaction la version 2.1.21 est déjà ancienne.

Les droits sur le fichier sont également mis à jour.

#sudo chown www-data:www-data /var/opt/teampass/2.1.21.1/install/upgrade.js
#sudo chmod 600 /var/opt/teampass/2.1.21.1/install/upgrade.js

Fichier upgrade.php

Le fichier install/upgrade.js a été ajouté afin de mettre à disposition la fonction Javascript utilisée lors de la mise à jour. Or, il faut ajouter la directive dans le fichier install/upgrade.php pour inclure cette référence.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
        <title>TeamPass Installation</title>
        <link rel="stylesheet" href="install.css" type="text/css" />
        <script type="text/javascript" src="../includes/js/functions.js"></script>
        <script type="text/javascript" src="install.js"></script>
        <script type="text/javascript" src="upgrade.js"></script>
        <script type="text/javascript" src="js/jquery.min.js"></script>
        <script type="text/javascript" src="js/jquery-ui.min.js"></script>
        <script type="text/javascript" src="js/aes.min.js"></script>


Fichier upgrade_ajax.php

Lors de la toute première exécution, l'upgrade finit par être en erreur avec le message suivant dans la log Apache.

[Tue Apr 26 13:36:33.202799 2016] [:error] [pid 24294] [client 194.206.87.197:42918] PHP Fatal error:  Uncaught Error: Call to undefined function mysql_result() in
/var/opt/teampass/2.1.21.1/install/upgrade_ajax.php:55\nStack trace:\n#0 /var/opt/teampass/2.1.21.1/install/upgrade_ajax.php(937): tableExists('cpassman_functi...')\n#1
{main}\n  thrown in /var/opt/teampass/2.1.21.1/install/upgrade_ajax.php on line 55, referer: https://www.perso.jouvinio.net/teampass/install/upgrade.php

Il faut donc étudier le contenu de cette fonction tableExists dans le fichier upgrade_ajax.php.

function tableExists($tablename, $database = false)
{
    global $dbTmp;
    if (!$database) {
        $res = mysqli_query($dbTmp, "SELECT DATABASE()");
        $database = mysql_result($res, 0);
    }

    $res = mysqli_query($dbTmp,
        "SELECT COUNT(*) as count
        FROM information_schema.tables
        WHERE table_schema = '$database'
        AND table_name = '$tablename'"
    );

    return mysql_result($res, 0) == 1;
}

Il se trouve que cette fonction mixte les appels de type mysqli_XXX et mysql_XXX. Or dans le cadre de cette installation, la version de php est trop récente et les fonctions mysql_XXX ont été supprimées. Il faut donc modifier cette fonction pour n'utiliser que des commandes de type mysqli_XXX. La modification est la suivante.

function tableExists($tablename, $database = false)
{
    global $dbTmp;
    if (!$database) {
        // $res = mysqli_query($dbTmp, "SELECT DATABASE()");
        // $database = mysql_result($res, 0);
        $queryRes = mysqli_query($dbTmp, "SELECT DATABASE()");
        $resTmp = mysqli_fetch_row($queryRes);
        $database = $resTmp[0];
    }

    // $res = mysqli_query($dbTmp,
    //     "SELECT COUNT(*) as count
    //     FROM information_schema.tables
    //     WHERE table_schema = '$database'
    //     AND table_name = '$tablename'"
    // );

    // return mysql_result($res, 0) == 1;

    $queryRes = mysqli_query($dbTmp,
        "SELECT COUNT(*) as count
        FROM information_schema.tables
        WHERE table_schema = '$database'
        AND table_name = '$tablename'"
    );

    $resTmp = mysqli_fetch_row($queryRes);

    return $resTmp[0] == 1;
}


Icon-database-search.png Lecture langues

Une fois la mise à jour réalisée, l'accès à la page d'accueil provoque une erreur. Le message suivant est affichée en guise de page HTML.

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' label='SELECT * FROM cpassman_languages
GROUP BY name ORDER BY na' at line 3
Query: SELECT * FROM cpassman_languages GROUP BY name ORDER BY name ASC

La requête générée doit permettre de lister les langues disponibles ainsi que des informations d'affichage. Or avec une version 5.7 de MySql, la syntaxe n'est plus valide de base.

mysql> SELECT * FROM cpassman_languages GROUP BY name ORDER BY name;
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'cpassman.cpassman_languages.id' which is not functionally
dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

Ceci fonctionnait sur des versions plus anciennes de MySql, mais des changements ont été réalisés. Il serait envisageable de modifier le paramétrage de la base de données afin de rendre valide la recherche, mais il est préféré de corriger le code.

La construction de cette requête est faite dans le fichier sources/core.php, en ligne 103. La fonction est modifiée pour ne plus effectuer de GROUP BY, qui est alors inutile vu la composition de la table, pas de doublons.

if (empty($languagesDropmenu)) {
    $languagesDropmenu = "";
    $languagesList = array();
    // $rows = DB::query("SELECT * FROM ".$pre."languages GROUP BY name ORDER BY name ASC");
    $rows = DB::query("SELECT * FROM ".$pre."languages ORDER BY name ASC");
    foreach ($rows as $record) {
        $languagesDropmenu .= '<li><a href="#"><img class="flag" src="includes/images/flags/'.
            $record['flag'].'"alt="'.$record['label'].'" title="'.
            $record['label'].'" onclick="ChangeLanguage(\''.$record['name'].'\')" /></a></li>';
        array_push($languagesList, $record['name']);
        if (isset($_SESSION['user_language']) && $record['name'] == $_SESSION['user_language']) {
            $_SESSION['user_language_flag'] = $record['flag'];
            $_SESSION['user_language_code'] = $record['code'];
            $_SESSION['user_language_label'] = $record['label'];
            $_SESSION['user_language_id'] = $record['id'];
        }
    }
}


Configuration-icon.png Elements de configurations

Lors de l'accès à l'application, après avoir résolu le problème des langues, le message d'erreur suivant est constaté dans les logs Apache.

[Thu Apr 28 14:12:19.640489 2016] [:error] [pid 14996] [client 37.162.254.89:4082] PHP Warning:  require_once(/var/opt/cpassman/sources/SplClassLoader.php):
failed to open stream: No such file or directory in /var/opt/teampass/2.1.21.1/index.php on line 46
[Thu Apr 28 14:12:19.640569 2016] [:error] [pid 14996] [client 37.162.254.89:4082] PHP Fatal error:  require_once(): Failed opening required
'/var/opt/cpassman/sources/SplClassLoader.php' (include_path='.:/usr/share/php') in /var/opt/teampass/2.1.21.1/index.php on line 46

Lors de la mise à jour de l'instance, des éléments ont été demandés comme l'URL de base, l'emplacement de l'installation... Cependant, ces informations ne sont pas répercutées dans la base de données.

mysql> select * from cpassman_misc where intitule in ('cpassman_dir', 'cpassman_url');
+-------+--------------+-----------------------------------------+
| type  | intitule     | valeur                                  |
+-------+--------------+-----------------------------------------+
| admin | cpassman_dir | /var/opt/cpassman                       |
| admin | cpassman_url | https://www.XXXX.net/cpassman           |
+-------+--------------+-----------------------------------------+
2 rows in set (0,00 sec)

Il faut donc les modifier manuellement pour refléter les bonnes valeurs.

mysql> update cpassman_misc set valeur = '/var/opt/teampass/teampass' where intitule = 'cpassman_dir';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update cpassman_misc set valeur = 'https://www.XXXX.net/teampass' where intitule = 'cpassman_url';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

Attention, il semblerait qu'il y ait beaucoup plus de problème que cela. Pour cela, il suffit de regarder l'ensemble des paramètres et se rendre compte que potentiellement, ce qui a été spécifié lors de l'initialisation n'a pas été pris en compte. Dans le cadre de cette installation, ce qui a été constaté est le suivant.

mysql> select * from cpassman_misc where intitule in ('favicon', 'path_to_files_folder', 'path_to_upload_folder', 'url_to_files_folder', 'url_to_upload_folder');
+-------+-----------------------+-----------------------------------------------------+
| type  | intitule              | valeur                                              |
+-------+-----------------------+-----------------------------------------------------+
| admin | favicon               | https://www.XXXX.net/cpassman/favicon.ico           |
| admin | path_to_upload_folder | /var/www/perso/teampass/upload                      |
| admin | url_to_upload_folder  | http://www.XXXX.net/teampass/upload                 |
| admin | path_to_files_folder  | /var/www/perso/teampass/files                       |
| admin | url_to_files_folder   | http://www.XXXX.net/teampass/files                  |
+-------+-----------------------+-----------------------------------------------------+
5 rows in set (0,00 sec)

Les corrections sont donc apportées manuellement.

mysql> update cpassman_misc set valeur = 'https://www.XXXX.net/teampass/favicon.ico' where intitule = 'favicon';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update cpassman_misc set valeur = '/var/opt/teampass/teampass/files' where intitule = 'path_to_files_folder';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update cpassman_misc set valeur = '/var/opt/teampass/teampass/upload' where intitule = 'path_to_upload_folder';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update cpassman_misc set valeur = 'https://www.XXXX.net/teampass/files' where intitule = 'url_to_files_folder';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update cpassman_misc set valeur = 'https://www.XXXX.net/teampass/upload' where intitule = 'url_to_upload_folder';
Query OK, 1 row affected (0,03 sec)
Rows matched: 1  Changed: 1  Warnings: 0


Motpasse-utilisateur.png Echec connexion

Lors des premières tentatives de connexion, l'application ne semble pas répondre. Pourtant en regardant les requêtes HTTP, avec les outils de debugger des navigateurs, on constate que la réponse à la requête identify.php retourne le résultat suivant.

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' label='UPDATE `cpassman_users`
SET `key_tempo`=NULL, `last_connex' at line 3
Query: UPDATE `cpassman_users` SET `key_tempo`=NULL, `last_connexion`=1461847925, `disabled`=0, `no_bad_attempts`=1 WHERE id=4

En effet, sur l'échec de connexion, l'évènement est audité mais en essayant d'injecter une valeur null dans la colonne key_tempo de la table XXXX_users, dont la définition est la suivante.

mysql> describe XXXX_users;
+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| id                     | int(12)      | NO   | PRI | NULL    | auto_increment |
| login                  | varchar(50)  | NO   | UNI | NULL    |                |
| pw                     | varchar(400) | YES  |     | NULL    |                |
| groupes_visibles       | varchar(250) | NO   |     | NULL    |                |
| derniers               | mediumtext   | NO   |     | NULL    |                |
| key_tempo              | varchar(100) | NO   |     | NULL    |                |
| last_pw_change         | varchar(30)  | NO   |     | NULL    |                |
| last_pw                | mediumtext   | NO   |     | NULL    |                |
| admin                  | tinyint(1)   | NO   |     | 0       |                |
| fonction_id            | varchar(255) | NO   |     | NULL    |                |
| groupes_interdits      | varchar(255) | NO   |     | NULL    |                |
| last_connexion         | varchar(30)  | NO   |     | NULL    |                |
| gestionnaire           | int(11)      | NO   |     | 0       |                |
| email                  | varchar(300) | NO   |     | NULL    |                |
| favourites             | varchar(300) | NO   |     | NULL    |                |
| latest_items           | varchar(300) | NO   |     | NULL    |                |
| personal_folder        | int(1)       | NO   |     | 0       |                |
| disabled               | tinyint(1)   | NO   |     | 0       |                |
| no_bad_attempts        | tinyint(1)   | NO   |     | 0       |                |
| can_create_root_folder | tinyint(1)   | NO   |     | 0       |                |
| read_only              | tinyint(1)   | NO   |     | 0       |                |
| timestamp              | varchar(30)  | NO   |     | 0       |                |
| user_language          | varchar(30)  | NO   |     | english |                |
| name                   | varchar(100) | YES  |     | NULL    |                |
| lastname               | varchar(100) | YES  |     | NULL    |                |
| session_end            | varchar(30)  | YES  |     | NULL    |                |
| isAdministratedByRole  | tinyint(5)   | NO   |     | 0       |                |
| psk                    | varchar(400) | YES  |     | NULL    |                |
| ga                     | varchar(50)  | YES  |     | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+
29 rows in set (0,00 sec)

La colonne étant définie comme non nulle, la requête ne peut aboutir.

L'erreur provient lors d'un échac d'authentification qui est tracé depuis le fichier sources/identify.php. A la ligne 558, un enregsitrement dans la table XXXX_users est réalisé ainsi.

            DB::update(
                $pre.'users',
                array(
                    'key_tempo' => $_SESSION['key'],
                    'last_connexion' => time(),
                    'disabled' => $userIsLocked,
                    'no_bad_attempts' => $nbAttempts
                ),
                "id=%i",
                $data['id']
            );

Or la variable de session key n'est générée qu'en cas de succès de connexion. Donc, à la première tentative, cela ne peut être que nulle. Ceci est modifié pour vérifier la valeur de la variable avant de l'injecter dans l'instruction. Si elle n'est pas définie, une chaîne vide est utilisée, inspiré de ce qui peut être réalisé dans d'autre condition.

            DB::update(
                $pre.'users',
                array(
                    // 'key_tempo' => $_SESSION['key'],
                    'key_tempo' => isset($_SESSION['key']) ? $_SESSION['key'] : '',
                    'last_connexion' => time(),
                    'disabled' => $userIsLocked,
                    'no_bad_attempts' => $nbAttempts
                ),
                "id=%i",
                $data['id']
            );