CVE-2017-15055 (PHP, published 2017-11-27T19:29Z)
Description: value: TeamPass before 2.1.27.9 does not properly enforce item access control when requesting items.queries.php. It is then possible to copy any arbitrary item into a directory controlled by the attacker, edit any item within a read-only directory, delete an arbitrary item, delete the file attachments of an arbitrary item, copy the password of an arbitrary item to the copy/paste buffer, access the history of an arbitrary item, and edit attributes of an arbitrary directory. To exploit the vulnerability, an authenticated attacker must tamper with the requests sent directly, for example by changing the "item_id" parameter when invoking "copy_item" on items.queries.php.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-269 \u2014 Improper Privilege Management
The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.
Repository: https://github.com/nilsteampassnet/TeamPass
Files changed:
index.php
items.load.php
sources/core.php
sources/items.queries.php
sources/main.functions.php
Commit message: 2.1.27

Several fixes


Vulnerable code:
    require_once $SETTINGS['cpassman_dir'].'/includes/language/'.$session_user_language.'.php';
                    $("#display_categories").val(data.displayCategories);
                        if ($(".tr_fields") != undefined && data.displayCategories != "") {
                        if ($(".tr_fields") != undefined && data.displayCategories != "") {
                                        type     : "move_item",
                                        item_id : ui.draggable.attr("id"),
                                        key        : "<?php echo $_SESSION['key']; ?>"
                    if (data.error == "ERR_JSON_FORMAT") {
                        $("#edit_show_error").html(data.error + ' ERROR (JSON is broken)!!!!!');
                        $("#edit_show_error").show();
                    } else if (data.error == "ERR_KEY_NOT_CORRECT") {
                        $("#edit_show_error").html('Key verification for Query is not correct!');
                        $("#edit_show_error").show();
                    }else if (data.error == "ERR_ENCRYPTION_NOT_CORRECT") {
                        $("#edit_show_error").html('Item password could not be correctly encrypted!');
                        $("#edit_show_error").show();
                    } else if (data.error == "ERR_PWD_TOO_LONG") {
                        $("#edit_show_error").html('<?php echo addslashes($LANG['error_pw_too_long']); ?>');
                        $("#edit_show_error").show();
                    } else if (data.error == "ERR_NOT_ALLOWED_TO_EDIT") {
                        $("#new_show_error").html('User not allowed to edit this Item!');
                        $("#new_show_error").show();
                    } else if (data.error != "") {
                        $("#edit_show_error").html('<?php echo addslashes($LANG['error_not_allowed_to']); ?>');
                        $("#edit_show_error").show();
                    type                : 'show_details_item',
                    id                  : id,
                    folder_id           : $('#hid_cat').val(),
                    salt_key_required   : $('#recherche_group_pf').val(),
                    salt_key_set        : $('#personal_sk_set').val(),
                    expired_item        : expired_item,
                    restricted          : restricted,
                    page                : "items",
                    key                 : "<?php echo $_SESSION['key']; ?>"
                    } else if (data.show_details == "1" && data.show_detail_option == "2") {
                            type    : "update_rep",
        require_once $SETTINGS['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'.php';
            $label = noHTML(htmlspecialchars_decode($dataReceived['label']));
            $url = htmlspecialchars_decode($dataReceived['url']);
            $login = htmlspecialchars_decode($dataReceived['login']);
                $label = noHTML(($dataReceived['label']));
                $url = noHTML(htmlspecialchars_decode($dataReceived['url']));
                $login = noHTML(htmlspecialchars_decode($dataReceived['login']));
                $email = noHTML(htmlspecialchars_decode($dataReceived['email']));
                    (@in_array(
                        $post_id,
                        $_SESSION['list_folders_limited'][$post_folder_id]
                    ))
                $returnValues = '[{"error" : "not_allowed"}, {"error_text" : "2'.addslashes($LANG['error_not_allowed_to']).'"}]';
            if (null !== $post_item_id
                && empty($post_item_id) === false
            $rows_tmp = DB::query("SELECT role_id FROM ".prefix_table("restriction_to_roles")." WHERE item_id=%i", $post_id);
            $myTest = 0;
            if (in_array($_SESSION['user_id'], $rows_tmp)) {
                $myTest = 1;
            if (null !== filter_input(INPUT_POST, 'salt_key_required', FILTER_SANITIZE_STRING)
                && filter_input(INPUT_POST, 'salt_key_required', FILTER_SANITIZE_STRING) === '1'
                && null !== filter_input(INPUT_POST, 'salt_key_set', FILTER_SANITIZE_STRING)
                && filter_input(INPUT_POST, 'salt_key_set', FILTER_SANITIZE_STRING) === '1'
            if (null !== filter_input(INPUT_POST, 'expired_item', FILTER_SANITIZE_STRING)
                && filter_input(INPUT_POST, 'expired_item', FILTER_SANITIZE_STRING) === '1'
                    && isset($_SESSION['list_folders_limited'][$post_folder_id])
                    && in_array($post_id, $_SESSION['list_folders_limited'][$post_folder_id]))
                if (null !== filter_input(INPUT_POST, 'restricted', FILTER_SANITIZE_STRING)) {
                    $arrData['restricted'] = filter_input(INPUT_POST, 'restricted', FILTER_SANITIZE_STRING);
                    "has_change_proposal" => DB::count()
        case "update_rep":
            $title = htmlspecialchars_decode($dataReceived['title']);
                $dataReceived['folder']
                    $dataReceived['folder']
                    $dataReceived['folder'],
                $dataReceived['source_folder_id']
                $dataReceived['target_folder_id']
            if ($tree->isChildOf($dataReceived['target_folder_id'], $dataReceived['source_folder_id']) === true) {
                        'parent_id' => $dataReceived['target_folder_id']
                    $dataReceived['source_folder_id']
        /*
          * CASE
          * WANT TO CLIPBOARD PW/LOGIN OF ITEM
        */
        case "get_clipboard_item":
            $dataItem = DB::queryfirstrow(
                "SELECT pw,login,perso FROM ".prefix_table("items")." WHERE id=%i",
                $post_id
            );

            if (filter_input(INPUT_POST, 'field', FILTER_SANITIZE_STRING) === "pw") {
                if ($dataItem['perso'] === '1') {
                    $data = cryption(
                        $dataItem['pw'],
                        $_SESSION['user_settings']['session_psk'],
                        "decrypt"
                    );
                } else {
                    $data = cryption(
                        $dataItem['pw'],
                        "",
                        "decrypt"
                    );
                }
            } else {
                $data = $dataItem['login'];
            }
            // Encrypt data to return
            echo prepareExchangedData($data, "encode");
            break;

                "SELECT name,id_item,file
        $tmp = explode(";", $_SESSION['fonction_id']);

Fixed code:
    if (file_exists($SETTINGS['cpassman_dir'].'/includes/language/'.$session_user_language.'.php') === true) {
        require_once $SETTINGS['cpassman_dir'].'/includes/language/'.$session_user_language.'.php';
    }
                    if (data.displayCategories !== undefined) {
                        $("#display_categories").val(data.displayCategories);
                    }
                        if ($(".tr_fields") !== undefined && data.displayCategories !== undefined && data.displayCategories !== "") {
                        if ($(".tr_fields") != undefined && data.displayCategories !== undefined && data.displayCategories != "") {
                                        type      : "move_item",
                                        item_id   : ui.draggable.attr("id"),
                                        key       : "<?php echo $_SESSION['key']; ?>"
                    if (data.error === "ERR_JSON_FORMAT") {
                        $("#edit_show_error")
                            .html(data.error + ' ERROR (JSON is broken)!!!!!')
                            .show();
                    } else if (data.error === "ERR_KEY_NOT_CORRECT") {
                        $("#edit_show_error")
                            .html('Key verification for Query is not correct!')
                            .show();
                    }else if (data.error === "ERR_ENCRYPTION_NOT_CORRECT") {
                        $("#edit_show_error")
                            .html('Item password could not be correctly encrypted!')
                            .show();
                    } else if (data.error === "ERR_PWD_TOO_LONG") {
                        $("#edit_show_error")
                            .html('<?php echo addslashes($LANG['error_pw_too_long']); ?>')
                            .show();
                    } else if (data.error === "ERR_NOT_ALLOWED_TO_EDIT") {
                        $("#new_show_error")
                            .html('User not allowed to edit this Item!')
                            .show();
                    } else if (data.error !== "") {
                        $("#edit_show_error")
                            .html('<?php echo addslashes($LANG['error_not_allowed_to']); ?>')
                            .show();
            var data = {
                "id" : id,
                "folder_id" : $('#hid_cat').val(),
                "salt_key_required" : $('#recherche_group_pf').val(),
                "salt_key_set" : $('#personal_sk_set').val(),
                "expired_item" : expired_item,
                "restricted" : restricted,
                "page" : "items"
            };

                    type : 'show_details_item',
                    data : prepareExchangedData(JSON.stringify(data), "encode", "<?php echo $_SESSION['key']; ?>"),
                    key  : "<?php echo $_SESSION['key']; ?>"
                    } else if (data.show_details === "1" && data.show_detail_option === "2") {
            if (data.error !== "") {
                $("#div_dialog_message_text").html(data.error_text);
                $("#div_dialog_message").show();
            }

                            type    : "update_folder",
        if (file_exists($SETTINGS['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'.php') === true) {
            require_once $SETTINGS['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'.php';
        }
            $label = filter_var(htmlspecialchars_decode($dataReceived['label']), FILTER_SANITIZE_STRING);
            $url = filter_var(htmlspecialchars_decode($dataReceived['url']), FILTER_SANITIZE_STRING);
            $login = filter_var(htmlspecialchars_decode($dataReceived['login']), FILTER_SANITIZE_STRING);
                $label = filter_var(($dataReceived['label']), FILTER_SANITIZE_STRING);
                $url = filter_var(htmlspecialchars_decode($dataReceived['url']), FILTER_SANITIZE_STRING);
                $login = filter_var(htmlspecialchars_decode($dataReceived['login']), FILTER_SANITIZE_STRING);
                $email = filter_var(htmlspecialchars_decode($dataReceived['email']), FILTER_SANITIZE_STRING);
                    (null !== $post_folder_id
                    && isset($_SESSION['list_restricted_folders_for_items'][$post_folder_id])
                    && in_array($post_id, $_SESSION['list_restricted_folders_for_items'][$post_folder_id])
                    && $post_restricted === '1'
                    && $restrictionActive === false)
                $returnValues = '[{"error" : "not_allowed"}, {"error_text" : "'.addslashes($LANG['error_not_allowed_to']).'"}]';
            if (empty($post_item_id) === false

                // Check if the folder where this item is, is accessible to the user
                if (in_array($originalRecord['id_tree'], $_SESSION['groupes_visibles'])) {
                    $returnValues = '[{"error" : "not_allowed"}, {"error_text" : "'.addslashes($LANG['error_not_allowed_to']).'"}]';
                    echo $returnValues;
                    break;
                }

                // Load the destination folder record into an array
            // Decrypt and retreive data in JSON format
            $dataReceived = prepareExchangedData($post_data, "decode");
            // Init post variables
            $post_id = filter_var(htmlspecialchars_decode($dataReceived['id']), FILTER_SANITIZE_NUMBER_INT);
            $post_folder_id = filter_var(htmlspecialchars_decode($dataReceived['folder_id']), FILTER_SANITIZE_NUMBER_INT);
            $post_salt_key_required = filter_var(htmlspecialchars_decode($dataReceived['salt_key_required']), FILTER_SANITIZE_STRING);
            $post_salt_key_set = filter_var(htmlspecialchars_decode($dataReceived['salt_key_set']), FILTER_SANITIZE_STRING);
            $post_expired_item = filter_var(htmlspecialchars_decode($dataReceived['expired_item']), FILTER_SANITIZE_STRING);
            $post_restricted = filter_var(htmlspecialchars_decode($dataReceived['restricted']), FILTER_SANITIZE_STRING);
            $post_page = filter_var(htmlspecialchars_decode($dataReceived['page']), FILTER_SANITIZE_STRING);

            $user_in_restricted_list_of_item = false;
                    if ($_SESSION['user_id'] === $record['id']) {
                        $user_in_restricted_list_of_item = true;
                    }


            $rows_tmp = DB::query(
                "SELECT role_id
                FROM ".prefix_table("restriction_to_roles")."
                WHERE item_id=%i",
                $post_id
            );
            foreach ($rows_tmp as $rec_tmp) {
                if (in_array($rec_tmp['role_id'], explode(';', $_SESSION['fonction_id']))) {
                    $restrictionActive = false;
                }
            if (null !== $post_salt_key_required
                && $post_salt_key_required === '1'
                && null !== $post_salt_key_set
                && $post_salt_key_set === '1'
            if (null !== $post_expired_item
                && $post_expired_item === '1'


                    && isset($_SESSION['list_restricted_folders_for_items'][$post_folder_id])
                    && in_array($post_id, $_SESSION['list_restricted_folders_for_items'][$post_folder_id])
                    && $post_restricted === '1'
                    && $user_in_restricted_list_of_item === true)
                ||
                (isset($SETTINGS['restricted_to_roles']) && $SETTINGS['restricted_to_roles'] === '1'
                    && $restrictionActive === false
                )
                if (null !== $post_restricted) {
                    $arrData['restricted'] = $post_restricted;
            // Check if the folder where this item is, is accessible to the user
            if (in_array($dataItem['id_tree'], $_SESSION['groupes_visibles'])) {
                $returnValues = '[{"error" : "not_allowed"}, {"error_text" : "'.addslashes($LANG['error_not_allowed_to']).'"}]';
                echo prepareExchangedData($returnValues, "encode");
                break;
            }

                    "has_change_proposal" => DB::count(),
                    "error" => ""
            // Check that user can access this item
            $granted = accessToItemIsGranted($post_id);
            if ($granted !== true) {
                echo prepareExchangedData(array("error" => $granted), "encode");
                break;
            }

            // Load item data
            $data = DB::queryFirstRow(
                "SELECT id_tree
                FROM ".prefix_table("items")."
                WHERE id = %i",
                $post_id
            );

        case "update_folder":
            $title = filter_var(htmlspecialchars_decode($dataReceived['title']), FILTER_SANITIZE_STRING);
            $post_folder_id = filter_var(htmlspecialchars_decode($dataReceived['folder']), FILTER_SANITIZE_NUMBER_INT);

            // Check if user is allowed to access this folder
            if (!in_array($post_folder_id, $_SESSION['groupes_visibles'])) {
                echo '[{"error" : "'.addslashes($LANG['error_not_allowed_to']).'"}]';
                break;
            }

                $post_folder_id
                    $post_folder_id
                    $post_folder_id,
            $post_source_folder_id = filter_var(htmlspecialchars_decode($dataReceived['source_folder_id']), FILTER_SANITIZE_NUMBER_INT);
            $post_target_folder_id = filter_var(htmlspecialchars_decode($dataReceived['target_folder_id']), FILTER_SANITIZE_NUMBER_INT);

            // Check that user can access this folder
            if (!in_array($post_source_folder_id, $_SESSION['groupes_visibles'])
                || !in_array($post_target_folder_id, $_SESSION['groupes_visibles'])
            ) {
                $returnValues = '[{"error" : "'.addslashes($LANG['error_not_allowed_to']).'"}]';
                echo $returnValues;
                break;
            }
                $post_source_folder_id
                $post_target_folder_id
            if ($tree->isChildOf($post_target_folder_id, $post_source_folder_id) === true) {
                        'parent_id' => $post_target_folder_id
                    $post_source_folder_id
                "SELECT name, id_item, file

            // Load item data
            $data_item = DB::queryFirstRow(
                "SELECT id_tree
                FROM ".prefix_table("items")."
                WHERE id = %i",
                $data['id_item']
            );

            // Check that user can access this folder
            if (!in_array($data_item['id_tree'], $_SESSION['groupes_visibles'])) {
                echo prepareExchangedData(array("error" => "ERR_FOLDER_NOT_ALLOWED"), "encode");
                break;
            }

            // Check that user can access this folder
            if (!in_array($dataSource['id_tree'], $_SESSION['groupes_visibles'])
                || !in_array($post_folder_id, $_SESSION['groupes_visibles'])
            ) {
                echo '[{"error" => "ERR_FOLDER_NOT_ALLOWED"}]';
                break;
            }


                    // Check that user can access this folder
                    if (!in_array($dataSource['id_tree'], $_SESSION['groupes_visibles'])
                        || !in_array($post_folder_id, $_SESSION['groupes_visibles'])
                    ) {
                        echo '[{"error":"not_allowed" , "status":"ok"}]';
                        break;
                    }

                    // Check that user can access this folder
                    if (!in_array($dataSource['id_tree'], $_SESSION['groupes_visibles'])
                        || !in_array($post_folder_id, $_SESSION['groupes_visibles'])
                    ) {
                        echo prepareExchangedData(array("error" => "ERR_FOLDER_NOT_ALLOWED"), "encode");
                        break;
                    }

        $tmp = explode(";", $idFonctions);
        $_SESSION['fonction_id'] = $idFonctions;

/**
 * Check if user can access to this item
 * @param $item_id
 */
function accessToItemIsGranted($item_id)
{
    // Load item data
    $data = DB::queryFirstRow(
        "SELECT id_tree
        FROM ".prefix_table("items")."
        WHERE id = %i",
        $item_id
    );

    // Check if user can access this folder
    if (!in_array($data['id'], $_SESSION['groupes_visibles'])) {
        return "ERR_FOLDER_NOT_ALLOWED";
    }

    return true;
}