CVE-2014-3771 (published 2014-08-07T11:13Z)

TeamPass before 2.1.20 allows remote attackers to bypass access restrictions via the language file path in a (1) request to index.php or (2) "change_user_language" request to sources/main.queries.php.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)

Weakness information:
cwe_id: CWE-264
cwe_name: Permissions, Privileges, and Access Controls
description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.

Fix information:
repo_url: https://github.com/nilsteampassnet/TeamPass
commit_details: repo_url: https://github.com/nilsteampassnet/TeamPass
author: nils@teampass.net
author_date: 2014-04-26 11:52:49+02:00
msg: Several security improvements
New feature: one time item view for anonymous user
repository: repo_url: https://github.com/nilsteampassnet/TeamPass
repo_name: nilsteampassnet/TeamPass
description: Collaborative Passwords Manager
repo_language: PHP
owner: nilsteampassnet
file_changes: filename: changelog.md
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,7 @@
  #544 - DataTables warning: JSON data from server could not be parsed
  Fork from slimm609 - Encrypted Sessions and CSRFGuard enabled
  Issues with folder creation in "personal folder"
+ New: one time view page for anonymous user
 
 2.1.19
  #413 - fix for PHP Parse error: syntax error, unexpected '['

programming_language: Markdown
filename: include.php
change_type: ModificationType.MODIFY
diff: @@ -22,11 +22,14 @@ $k['office_file_ext'] = array('xls', 'xlsx', 'docx', 'doc', 'csv', 'ppt', 'pptx'
 $k['admin_full_right'] = true;
 $k['admin_no_info'] = false;
 $k['copyright'] = " &copy; 2009 - 2014";
+$k['otv_expiration_period'] = 604800; // 1 week
+$k['allowedTags'] = "<b><i><sup><sub><em><strong><u><br><br /><a><strike><ul><blockquote><blockquote><img><li><h1><h2><h3><h4><h5><ol><small><font>";
 
 @define('ERR_NOT_ALLOWED', "1000");
 @define('ERR_NOT_EXIST', "1001");
 @define('ERR_SESS_EXPIRED', "1002");
 @define('ERR_NO_MCRYPT', "1003");
+@define('ERR_VALID_SESSION', "1004");
 
 // Management Pages
 $mngPages = array(

programming_language: PHP
filename: english.php
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,8 @@ if (!isset($_SESSION['settings']['cpassman_url'])) {
     $TeamPass_url = $_SESSION['settings']['cpassman_url'];
 }
 
+$txt['one_time_item_view'] = "One time view link";
+$txt['one_time_view_item_url_box'] = "Share the One-Time URL with a person of Trust <br><br>#URL#<br><br>Remember that this link will only be visible one time until the #DAY#";
 $txt['url_copied_clipboard'] = "URL copied in clipboard";
 $txt['url_copy'] = "Copy URL in clipboard";
 

programming_language: PHP
filename: index.php
change_type: ModificationType.MODIFY
diff: @@ -10,7 +10,7 @@ session_start();
  *
  * @file          index.php
  * @author        Nils Laumaillé
- * @version       2.1.19
+ * @version       2.1.20
  * @copyright     (c) 2009-2014 Nils Laumaillé
  * @licensing     GNU AFFERO GPL 3.0
  * @link          http://www.teampass.net
@@ -51,15 +51,12 @@ $db->connect();
 
 //load main functions needed
 require_once 'sources/main.functions.php';
+// Load CORE
+require_once $_SESSION['settings']['cpassman_dir'].'/sources/core.php';
 
 /* DEFINE WHAT LANGUAGE TO USE */
 if (!isset($_SESSION['user_id']) && !isset($_POST['language'])) {
     //get default language
-    /*$dataLanguage =
-        $db->fetchRow(
-            "SELECT valeur FROM ".$pre."misc
-            WHERE type = 'admin' AND intitule = 'default_language'"
-        );*/
     $dataLanguage = $db->queryGetRow(
         "misc",
         array(
@@ -91,12 +88,16 @@ if (!isset($_SESSION['user_id']) && !isset($_POST['language'])) {
     }
 }
 // Load user languages files
-require_once $_SESSION['settings']['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'.php';
-if (isset($_GET['page']) && $_GET['page'] == "kb") {
-    require_once $_SESSION['settings']['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'_kb.php';
+if (in_array($_SESSION['user_language'], $languagesList)) {
+	require_once $_SESSION['settings']['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'.php';
+	if (isset($_GET['page']) && $_GET['page'] == "kb") {
+		require_once $_SESSION['settings']['cpassman_dir'].'/includes/language/'.$_SESSION['user_language'].'_kb.php';
+	}
+} else {
+	$_SESSION['error']['code'] = ERR_NOT_ALLOWED; //not allowed page
+	include 'error.php';
 }
-// Load CORE
-require_once $_SESSION['settings']['cpassman_dir'].'/sources/core.php';
+
 // Load links, css and javascripts
 @require_once $_SESSION['settings']['cpassman_dir'].'/load.php';
 ?>
@@ -208,7 +209,8 @@ if (isset($_SESSION['login'])) {
         </div>';
 }
 // Display language menu
-echo '
+if (!isset($_GET['otv'])) {
+	echo '
         <div style="float:right;">
             <dl id="flags" class="dropdown">
                 <dt><img src="includes/images/flags/'.$_SESSION['user_language_flag'].'" alt="" /></dt>
@@ -218,7 +220,9 @@ echo '
                     </ul>
                 </dd>
             </dl>
-        </div>
+        </div>';
+}
+echo '
     </div>';
 
 /* LAST SEEN */
@@ -299,7 +303,9 @@ if (isset($_SESSION['autoriser']) && $_SESSION['autoriser'] == true && isset($_G
             <br />
             <button title="'.$txt['history'].'" id="menu_button_history" class="" onclick="OpenDialog(\'div_item_history\', \'false\')"><img src="includes/images/report.png" id="div_history" alt="" /></button>
             <br />
-            <button title="'.$txt['share'].'" id="menu_button_share" class="" onclick="OpenDialog(\'div_item_share\', \'false\')"><img src="includes/images/share.png" id="div_share" alt="" /></button>';
+            <button title="'.$txt['share'].'" id="menu_button_share" class="" onclick="OpenDialog(\'div_item_share\', \'false\')"><img src="includes/images/share.png" id="div_share" alt="" /></button>
+			<br />
+            <button title="'.$txt['one_time_item_view'].'" id="menu_button_otv" class="" onclick="prepareOneTimeView()"><img src="includes/images/globe-share.png" id="div_otv" alt="" /></button>';
     if (isset($_SESSION['settings']['enable_email_notification_on_item_shown']) && $_SESSION['settings']['enable_email_notification_on_item_shown'] == 1) {
         echo '
             <br />
@@ -458,9 +464,14 @@ if (isset($_SESSION['validite_pw']) && $_SESSION['validite_pw'] == true && !empt
     include 'error.php';
 } elseif ((!isset($_SESSION['validite_pw']) || empty($_SESSION['validite_pw']) || empty($_SESSION['user_id'])) && isset($_GET['otv']) && $_GET['otv'] == "true") {
     // case where one-shot viewer
-    if (isset($_GET['session']) && !empty($_GET['session'])) {
-        include 'otv.php?session='.$_GET['session'];
-    } else {
+	if (
+		isset($_GET['code']) && !empty($_GET['code'])
+		&& isset($_GET['item_id']) && !empty($_GET['item_id'])
+		&& isset($_GET['stamp']) && !empty($_GET['stamp'])
+		&& isset($_GET['otv_id'])
+	) {
+		include 'otv.php';
+	} else {
         $_SESSION['error']['code'] = ERR_VALID_SESSION;
         $_SESSION['initial_url'] = substr($_SERVER["REQUEST_URI"], strpos($_SERVER["REQUEST_URI"], "index.php?"));
         include 'error.php';

programming_language: PHP
filename: items.load.php
change_type: ModificationType.MODIFY
diff: @@ -162,7 +162,7 @@ function ListerItems(groupe_id, restricted, start)
         if ($(".tr_fields") != undefined) $(".tr_fields, .newItemCat, .editItemCat").hide();
 
         //Disable menu buttons
-        $('#menu_button_edit_item,#menu_button_del_item,#menu_button_add_fav,#menu_button_del_fav,#menu_button_show_pw,#menu_button_copy_pw,#menu_button_copy_login,#menu_button_copy_link,#menu_button_copy_item,#menu_button_notify,#menu_button_history,#menu_button_share').attr('disabled', 'disabled');
+        $('#menu_button_edit_item,#menu_button_del_item,#menu_button_add_fav,#menu_button_del_fav,#menu_button_show_pw,#menu_button_copy_pw,#menu_button_copy_login,#menu_button_copy_link,#menu_button_copy_item,#menu_button_notify,#menu_button_history,#menu_button_share,#menu_button_otv').attr('disabled', 'disabled');
 
         // clear existing clips
         //ZeroClipboard.destroy();
@@ -197,7 +197,7 @@ function ListerItems(groupe_id, restricted, start)
                 } else if (data.error == "not_authorized") {
                     //warn user
                     $("#hid_cat").val("");
-                    $("#menu_button_copy_item, #menu_button_add_group, #menu_button_edit_group, #menu_button_del_group, #menu_button_add_item, #menu_button_edit_item, #menu_button_del_item, #menu_button_history, #menu_button_share").attr('disabled', 'disabled');
+                    $("#menu_button_copy_item, #menu_button_add_group, #menu_button_edit_group, #menu_button_del_group, #menu_button_add_item, #menu_button_edit_item, #menu_button_del_item, #menu_button_history, #menu_button_share, #menu_button_otv").attr('disabled', 'disabled');
                     $("#item_details_nok").show();
                     $("#item_details_ok, #item_details_no_personal_saltkey").hide();
                     $("#items_list_loader").hide();
@@ -1115,9 +1115,9 @@ function AfficherDetailsItem(id, salt_key_required, expired_item, restricted, di
 
                         // disable share button for personal folder
                         if ($("#recherche_group_pf").val() == 1) {
-            		        $("#menu_button_share").attr('disabled', 'disabled');
+            		        $("#menu_button_share, #menu_button_otv").attr('disabled', 'disabled');
             		    } else {
-            		    	$("#menu_button_share").prop("disabled", false);
+            		    	$("#menu_button_share, #menu_button_otv").prop("disabled", false);
             		    }
 
                         //Manage to deleted information
@@ -2600,4 +2600,30 @@ function aes_decrypt(text)
 {
     return Aes.Ctr.decrypt(text, "<?php echo $_SESSION['key'];?>", 256);
 }
+
+/*
+* Launch the redirection to OTV page
+*/
+function prepareOneTimeView()
+{
+    //Send query
+    $.post(
+        "sources/items.queries.php",
+        {
+            type    : "generate_OTV_url",
+            id      : $("#id_item").val(),
+            key     : "<?php echo $_SESSION['key'];?>"
+        },
+        function(data) {
+            //check if format error
+            if (data[0].error == "") {
+                $("#div_dialog_message").dialog('open');
+                $("#div_dialog_message_text").html(data[0].url);
+            } else {
+                $("#item_history_log_error").html(data[0].error).show();
+            }
+        },
+        "json"
+   );
+}
 </script>

programming_language: JavaScript
filename: items.php
change_type: ModificationType.MODIFY
diff: @@ -14,9 +14,9 @@
  */
 
 if (
-    !isset($_SESSION['CPM']) || $_SESSION['CPM'] != 1 || 
-    !isset($_SESSION['user_id']) || empty($_SESSION['user_id']) || 
-    !isset($_SESSION['key']) || empty($_SESSION['key'])) 
+    !isset($_SESSION['CPM']) || $_SESSION['CPM'] != 1 ||
+    !isset($_SESSION['user_id']) || empty($_SESSION['user_id']) ||
+    !isset($_SESSION['key']) || empty($_SESSION['key']))
 {
     die('Hacking attempt...');
 }
@@ -74,11 +74,11 @@ foreach ($rows as $reccord) {
 $selectVisibleFoldersOptions = $selectVisibleNonPersonalFoldersOptions = "";
 // Hidden things
 echo '
-<input type="hidden" name="hid_cat" id="hid_cat" value="', isset($_GET['group']) ? $_GET['group'] : "", '" />
+<input type="hidden" name="hid_cat" id="hid_cat" value="', isset($_GET['group']) ? htmlspecialchars($_GET['group']) : "", '" />
 <input type="hidden" id="complexite_groupe" />
 <input type="hidden" name="selected_items" id="selected_items" />
 <input type="hidden" name="input_liste_utilisateurs" id="input_liste_utilisateurs" value="'.$usersString.'" />
-<input type="hidden" name="input_list_roles" id="input_list_roles" value="'.htmlentities($listRoles, ENT_QUOTES, 'UTF-8').'" />
+<input type="hidden" name="input_list_roles" id="input_list_roles" value="'.htmlspecialchars(htmlentities($listRoles, ENT_QUOTES, 'UTF-8')).'" />
 <input type="hidden" id="bloquer_creation_complexite" />
 <input type="hidden" id="bloquer_modification_complexite" />
 <input type="hidden" id="error_detected" />
@@ -87,7 +87,7 @@ echo '
 <input type="hidden" id="richtext_on" value="1" />
 <input type="hidden" id="query_next_start" value="0" />
 <input type="hidden" id="display_categories" value="0" />
-<input type="hidden" id="nb_items_to_display_once" value="', isset($_SESSION['settings']['nb_items_by_query']) ? $_SESSION['settings']['nb_items_by_query'] : 'auto', '" />
+<input type="hidden" id="nb_items_to_display_once" value="', isset($_SESSION['settings']['nb_items_by_query']) ? htmlspecialchars($_SESSION['settings']['nb_items_by_query']) : 'auto', '" />
 <input type="hidden" id="user_is_read_only" value="', isset($_SESSION['user_read_only']) && $_SESSION['user_read_only'] == 1 ? '1' : '', '" />
 <input type="hidden" id="request_ongoing" />
 <input type="hidden" id="request_lastItem" />
@@ -118,7 +118,7 @@ if (isset($_COOKIE['jstree_select']) && !empty($_COOKIE['jstree_select'])) {
     $firstGroup = "";
 }
 echo '
-<input type="hidden" name="jstree_group_selected" id="jstree_group_selected" value="'.$firstGroup.'" />';
+<input type="hidden" name="jstree_group_selected" id="jstree_group_selected" value="'.htmlspecialchars($firstGroup).'" />';
 
 echo '
 <div id="div_items">';

programming_language: PHP
filename: otv.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,103 @@
+<?php
+/**
+ * @file          otv.php
+ * @author        Nils Laumaill
+ * @version       2.1.20
+ * @copyright     (c) 2009-2014 Nils Laumaill
+ * @licensing     GNU AFFERO GPL 3.0
+ * @link          http://www.teampass.net
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+require_once('sources/sessions.php');
+@session_start();
+if (!isset($_SESSION['CPM']) || $_SESSION['CPM'] != 1) {
+    die('Hacking attempt...');
+}
+
+$html = "";
+if (
+    filter_var($_GET['code'], FILTER_SANITIZE_STRING) != ""
+    && filter_var($_GET['item_id'], FILTER_SANITIZE_STRING) >= 0
+    && filter_var($_GET['stamp'], FILTER_SANITIZE_STRING) >= 0
+    && filter_var($_GET['otv_id'], FILTER_SANITIZE_STRING) >= 0
+) {
+    //Include files
+    require_once $_SESSION['settings']['cpassman_dir'].'/includes/settings.php';
+    require_once $_SESSION['settings']['cpassman_dir'].'/includes/include.php';
+    require_once $_SESSION['settings']['cpassman_dir'].'/sources/SplClassLoader.php';
+
+    // connect to DB
+    $db = new SplClassLoader('Database\Core', '../includes/libraries');
+    $db->register();
+    $db = new Database\Core\DbCore($server, $user, $pass, $database, $pre);
+    $db->connect();
+
+    // Include main functions used by TeamPass
+    require_once 'sources/main.functions.php';
+
+    // check session validity
+    $data = $db->queryGetRow(
+        "otv",
+        array(
+            "timestamp",
+            "code",
+            "item_id"
+        ),
+        array(
+            "id" => intval($_GET['otv_id'])
+        )
+    );
+    if (
+        $data[0] == $_GET['stamp']
+        && $data[1] == $_GET['code']
+        && $data[2] == $_GET['item_id']
+    ) {
+        // otv is too old
+        if ($data[0] < (time() - $k['otv_expiration_period']) ) {
+            $html = "Link is too old!";
+        } else {
+            $dataItem = $db->queryFirst(
+                "SELECT *
+                FROM ".$pre."items as i
+                INNER JOIN ".$pre."log_items as l ON (l.id_item = i.id)
+                WHERE i.id=".intval($_GET['item_id'])."
+                AND l.action = 'at_creation'"
+            );
+
+            // get data
+            $pw = decrypt($dataItem['pw']);
+            $label = $dataItem['label'];
+            $email = $dataItem['email'];
+            $url = $dataItem['url'];
+            $description = preg_replace('/(?<!\\r)\\n+(?!\\r)/', '', strip_tags($dataItem['description'], $k['allowedTags']));
+            $login = str_replace('"', '&quot;', $dataItem['login']);
+
+            // display data
+            $html = "<div style='margin:30px;'>".
+            	"<div style='font-size:20px;font-weight:bold;'>Welcome to One-Time item view page.</div>".
+            	"<div style='font-style:italic;'>Here are the details of the Item that has been shared to you</div>".
+            	"<div style='margin-top:10px;'><table>".
+				"<tr><td>Label:</td><td>" . $label . "</td</tr>".
+            	"<tr><td>Password:</td><td>" . $pw . "</td</tr>".
+            	"<tr><td>Description:</td><td>" . $description . "</td</tr>".
+            	"<tr><td>login:</td><td>" . $login . "</td</tr>".
+            	"<tr><td>URL:</td><td>" . $url ."</td</tr>".
+            	"</table></div>".
+            	"<div style='margin-top:30px;'>Copy carefully the data you need. This page is only visible once.</div>".
+            	"</div>";
+
+        	// delete entry
+        	//$db->query("DELETE FROM ".$pre."otv WHERE id = '".intval($_GET['item_id'])."'");
+        }
+    } else {
+        $html = "Not a valid page!";
+    }
+} else {
+    $html = "Not a valid page!";
+}
+
+echo $html;
\ No newline at end of file

programming_language: PHP
filename: find.queries.php
change_type: ModificationType.MODIFY
diff: @@ -32,6 +32,7 @@ $db->connect();
 
 //Columns name
 $aColumns = array('id', 'label', 'description', 'tags', 'id_tree', 'folder', 'login');
+$aSortTypes = array('ASC', 'DESC');
 
 //init SQL variables
 $sOrder = $sLimit = "";
@@ -80,9 +81,10 @@ if (isset($_GET['iDisplayStart']) && $_GET['iDisplayLength'] != '-1') {
 }
 
 //Ordering
-if (isset($_GET['iSortCol_0'])) {
+if (isset($_GET['iSortCol_0']) && in_array($_GET['iSortCol_0'], $aSortTypes)) {
     $sOrder = "ORDER BY  ";
     for ($i=0; $i<intval($_GET['iSortingCols']); $i++) {
+
         if (
             $_GET[ 'bSortable_'.filter_var($_GET['iSortCol_'.$i], FILTER_SANITIZE_NUMBER_INT)] == "true" &&
             preg_match("#^(asc|desc)\$#i", $_GET['sSortDir_'.$i])
@@ -96,8 +98,11 @@ if (isset($_GET['iSortCol_0'])) {
     if ($sOrder == "ORDER BY") {
             $sOrder = "";
     }
+} else if (!in_array($_GET['iSortCol_0'], $aSortTypes)) {
+	// possible attack - stop
+	echo '[{}]';
 }
-//echo $sOrder;
+
 /*
  * Filtering
  * NOTE this does not match the built-in DataTables filtering which does it

programming_language: PHP
filename: items.queries.php
change_type: ModificationType.MODIFY
diff: @@ -52,8 +52,6 @@ $pwComplexity = array(0 => array(0, $txt['complex_level0']),
     90 => array(90, $txt['complex_level6'])
    );
 
-$allowedTags = '<b><i><sup><sub><em><strong><u><br><br /><a><strike><ul><blockquote><blockquote><img><li><h1><h2><h3><h4><h5><ol><small><font>';
-
 //Class loader
 require_once $_SESSION['settings']['cpassman_dir'].'/sources/SplClassLoader.php';
 
@@ -2676,6 +2674,66 @@ if (isset($_POST['type'])) {
                 echo '{ "modified" : "0" }';
             }
             break;
+
+        /*
+        * CASE
+        * Check if Item has been changed since loaded
+        */
+        case "generate_OTV_url":
+            // Check KEY
+            if ($_POST['key'] != $_SESSION['key']) {
+                echo '[ { "error" : "key_not_conform" } ]';
+                break;
+            }
+
+            // delete all existing old otv codes
+            $rows = $db->fetchAllArray("SELECT id FROM ".$pre."otv WHERE timestamp < ".(time() - $k['otv_expiration_period']));
+            foreach ($rows as $reccord) {
+                $db->queryDelete(
+                    'otv',
+                    array(
+                        'id' => $reccord['id']
+                       )
+                );
+            }
+
+            // generate session
+            $pwgen = new SplClassLoader('Encryption\PwGen', '../includes/libraries');
+            $pwgen->register();
+            $pwgen = new Encryption\PwGen\pwgen();
+            $pwgen->setLength(20);
+            $pwgen->setSecure(true);
+            $otv_code = $pwgen->generate();
+
+            $newID = $db->queryInsert(
+                "otv",
+                array(
+                	'id' => null,
+                    'item_id' => intval($_POST['id']),
+                    'timestamp' => time(),
+                    'originator' => intval($_SESSION['user_id']),
+                    'code' => $otv_code
+                   )
+            );
+
+            $otv_session = array(
+                "code"      => $otv_code,
+                "item_id"   => $_POST['id'],
+                "stamp" => time(),
+                "otv_id"    => $newID
+            );
+
+            $url = $_SESSION['settings']['cpassman_url']."/index.php?otv=true&".http_build_query($otv_session);
+        	$exp_date = date($_SESSION['settings']['date_format']." ".$_SESSION['settings']['time_format'], time() + $k['otv_expiration_period']);
+
+        	echo '[{ "error" : "" , "url" : "'.addslashes(
+            	str_replace(
+            		array("#URL#", "#DAY#"),
+            		array($url, $exp_date),
+            		$txt['one_time_view_item_url_box']
+            	)
+            ).'" }]';
+            break;
     }
 }
 // Build the QUERY in case of GET

programming_language: PHP