CVE-2014-3774: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Multiple cross-site scripting (XSS) vulnerabilities in items.php in TeamPass before 2.1.20 allow remote attackers to inject arbitrary web script or HTML via the group parameter, which is not properly handled in a (1) hid_cat or (2) open_folder form element, or (3) id parameter, which is not properly handled in the open_id form element.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Published: 2014-08-07T11:13Z
Repository: https://github.com/nilsteampassnet/TeamPass
Language: PHP
Files changed: changelog.md, includes/include.php, includes/language/english.php, index.php, items.load.php, items.php, otv.php, sources/find.queries.php, sources/items.queries.php
Commit date: 2014-04-26 11:52:49 +0200
Commit message: Several security improvements
New feature: one time item view for anonymous user

Diff: diff --git a/changelog.md b/changelog.md
index c5df2aa2..7faadb1c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -18,10 +18,11 @@
  #527 - Potential security bug
  #485 - CVS Import on V 2.1.19 quotes problems
  #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 '['
  #447 - fix for PHP Fatal error: Cannot redeclare getBits()
  #442 - problem edit folder
diff --git a/includes/images/globe-share.png b/includes/images/globe-share.png
new file mode 100644
index 00000000..e931afcc
Binary files /dev/null and b/includes/images/globe-share.png differ
diff --git a/includes/include.php b/includes/include.php
index f11eef1f..e2f960b6 100644
--- a/includes/include.php
+++ b/includes/include.php
@@ -20,15 +20,18 @@ $k['one_month_seconds'] = 2592000;
 $k['image_file_ext'] = array('jpg', 'gif', 'png', 'jpeg', 'tiff', 'bmp');
 $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(
     'manage_users' => 'users.php',
     'manage_folders' => 'folders.php',
diff --git a/includes/language/english.php b/includes/language/english.php
index ef72a93f..584a0708 100644
--- a/includes/language/english.php
+++ b/includes/language/english.php
@@ -4,10 +4,12 @@ if (!isset($_SESSION['settings']['cpassman_url'])) {
     $TeamPass_url = '';
 } else {
     $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";
 
 $txt['settings_attachments_encryption'] = "Enable encryption of Items attachments";
 $txt['settings_attachments_encryption_tip'] = "THIS OPTION COULD BREAK EXISTING ATTACHMENTS, please read carefully the next. If enabled, Items attachments are stored encrypted on the server. The ecryption uses the SALT defined for Teampass. This requieres more server ressources. WARNING: once you change strategy, it is mandatory to run the script to adapt existing attachments. See tab 'Specific Actions'.";
diff --git a/index.php b/index.php
index 695b579e..700210e4 100644
--- a/index.php
+++ b/index.php
@@ -8,11 +8,11 @@ session_start();
 <?php
 /**
  *
  * @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
  *
  * This library is distributed in the hope that it will be useful,
@@ -49,19 +49,16 @@ $db->register();
 $db = new Database\Core\DbCore($server, $user, $pass, $database, $pre);
 $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(
             "valeur"
         ),
@@ -89,16 +86,20 @@ if (!isset($_SESSION['user_id']) && !isset($_POST['language'])) {
     } elseif (isset($_SESSION['settings']['default_language'])) {
         $_SESSION['user_language'] = $_SESSION['settings']['default_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';
 ?>
 
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
@@ -206,21 +207,24 @@ if (isset($_SESSION['login'])) {
                 </button>
             </div>
         </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>
                 <dd>
                     <ul>
                     '.$languagesDropmenu.'
                     </ul>
                 </dd>
             </dl>
-        </div>
+        </div>';
+}
+echo '
     </div>';
 
 /* LAST SEEN */
 echo '
     <div style="display:none;" id="div_last_items" class="ui-state-active ui-corner-all">
@@ -297,11 +301,13 @@ if (isset($_SESSION['autoriser']) && $_SESSION['autoriser'] == true && isset($_G
             <br />
             <button title="'.$txt['link_copy'].'" style="margin-bottom:5px;" id="menu_button_copy_link" class="copy_clipboard"><img src="includes/images/target.png" id="div_copy_link" alt="" /></button>
             <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 />
             <button style="margin-bottom:5px;" id="menu_button_notify" class=""><img src="includes/images/alarm-clock.png" id="div_notify" alt="" /></button>';
     }
@@ -456,13 +462,18 @@ if (isset($_SESSION['validite_pw']) && $_SESSION['validite_pw'] == true && !empt
     $_SESSION['error']['code'] = ERR_SESS_EXPIRED;
     $_SESSION['initial_url'] = substr($_SERVER["REQUEST_URI"], strpos($_SERVER["REQUEST_URI"], "index.php?"));
     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';
     }
 } elseif (empty($_SESSION['user_id']) && isset($_GET['action']) && $_GET['action'] == "password_recovery") {
diff --git a/items.load.php b/items.load.php
index 8ad8c0b5..48b52033 100644
--- a/items.load.php
+++ b/items.load.php
@@ -160,11 +160,11 @@ function ListerItems(groupe_id, restricted, start)
         $("#selected_items").val("");
         $("#hid_cat").val(groupe_id);
         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();
 
         //ajax query
@@ -195,11 +195,11 @@ function ListerItems(groupe_id, restricted, start)
                     $('#menu_button_add_item').attr('disabled', 'disabled');
                     $("#items_list_loader, #div_loading").hide();
                 } 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();
                 } else if ($("#user_is_read_only").val() == 1 && data.recherche_group_pf == 0) {
                     //readonly user
@@ -1113,13 +1113,13 @@ function AfficherDetailsItem(id, salt_key_required, expired_item, restricted, di
                         }
                         //$("#menu_button_show_pw, #menu_button_copy_pw, #menu_button_copy_login, #menu_button_copy_link, #menu_button_history").prop("disabled", false);
 
                         // 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
                         if (data.to_be_deleted != 0 && data.to_be_deleted != null && data.to_be_deleted != "not_enabled") {
                             $('#item_extra_info').html("<i><img src=\'&nbsp;<?php echo $_SESSION['settings']['cpassman_url'];?>/includes/images/information-white.png\'><?php echo addslashes($txt['automatic_deletion_activated']);?></i>");
@@ -2598,6 +2598,32 @@ function aes_encrypt(text)
 
 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>
diff --git a/items.php b/items.php
index 45741676..89230706 100644
--- a/items.php
+++ b/items.php
@@ -12,13 +12,13 @@
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  */
 
 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...');
 }
 
 /* do checks */
@@ -72,24 +72,24 @@ foreach ($rows as $reccord) {
 }
 // Build list of visible folders
 $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" />
 <input type="hidden" name="random_id" id="random_id" />
 <input type="hidden" id="edit_wysiwyg_displayed" value="" />
 <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" />
 <input type="hidden" id="item_editable" />
 <input type="hidden" id="timestamp_item_displayed" />
@@ -116,11 +116,11 @@ if (isset($_COOKIE['jstree_select']) && !empty($_COOKIE['jstree_select'])) {
     $firstGroup = str_replace("#li_", "", $_COOKIE['jstree_select']);
 } else {
     $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">';
 // MAIN ITEMS TREE
 echo '
diff --git a/otv.php b/otv.php
new file mode 100644
index 00000000..3ab1ec37
--- /dev/null
+++ b/otv.php
@@ -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
diff --git a/sources/find.queries.php b/sources/find.queries.php
index 0d36b834..51ec69f3 100644
--- a/sources/find.queries.php
+++ b/sources/find.queries.php
@@ -30,10 +30,11 @@ $db->register();
 $db = new Database\Core\DbCore($server, $user, $pass, $database, $pre);
 $db->connect();
 
 //Columns name
 $aColumns = array('id', 'label', 'description', 'tags', 'id_tree', 'folder', 'login');
+$aSortTypes = array('ASC', 'DESC');
 
 //init SQL variables
 $sOrder = $sLimit = "";
 $sWhere = "id_tree IN(".implode(', ', $_SESSION['groupes_visibles']).")";    //limit search to the visible folders
 
@@ -78,13 +79,14 @@ $sLimit = "";
 if (isset($_GET['iDisplayStart']) && $_GET['iDisplayLength'] != '-1') {
     $sLimit = "LIMIT ". filter_var($_GET['iDisplayStart'], FILTER_SANITIZE_NUMBER_INT) .", ". filter_var($_GET['iDisplayLength'], FILTER_SANITIZE_NUMBER_INT)."";
 }
 
 //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])
         ) {
             $sOrder .= "".$aColumns[ filter_var($_GET['iSortCol_'.$i], FILTER_SANITIZE_NUMBER_INT) ]." "
@@ -94,12 +96,15 @@ if (isset($_GET['iSortCol_0'])) {
 
     $sOrder = substr_replace($sOrder, "", -2);
     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
  * word by word on any field. It's possible to do here, but concerned about efficiency
  * on very large tables, and MySQL's regex functionality is very limited
diff --git a/sources/items.queries.php b/sources/items.queries.php
index 23aeb299..1cac4620 100644
--- a/sources/items.queries.php
+++ b/sources/items.queries.php
@@ -50,12 +50,10 @@ $pwComplexity = array(0 => array(0, $txt['complex_level0']),
     70 => array(70, $txt['complex_level4']),
     80 => array(80, $txt['complex_level5']),
     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';
 
 // Connect to mysql server
 $db = new SplClassLoader('Database\Core', '../includes/libraries');
@@ -2674,10 +2672,70 @@ if (isset($_POST['type'])) {
                 echo '{ "modified" : "1" }';
             } else {
                 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
 if (isset($_GET['type'])) {
     switch ($_GET['type']) {

Vulnerable code:  * @version       2.1.19
    /*$dataLanguage =
        $db->fetchRow(
            "SELECT valeur FROM ".$pre."misc
            WHERE type = 'admin' AND intitule = 'default_language'"
        );*/
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';
// Load CORE
require_once $_SESSION['settings']['cpassman_dir'].'/sources/core.php';
echo '
        </div>
            <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>';
    if (isset($_GET['session']) && !empty($_GET['session'])) {
        include 'otv.php?session='.$_GET['session'];
    } else {
        $('#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_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_share").attr('disabled', 'disabled');
            		    	$("#menu_button_share").prop("disabled", false);
    !isset($_SESSION['CPM']) || $_SESSION['CPM'] != 1 || 
    !isset($_SESSION['user_id']) || empty($_SESSION['user_id']) || 
    !isset($_SESSION['key']) || empty($_SESSION['key'])) 
<input type="hidden" name="hid_cat" id="hid_cat" value="', isset($_GET['group']) ? $_GET['group'] : "", '" />
<input type="hidden" name="input_list_roles" id="input_list_roles" value="'.htmlentities($listRoles, ENT_QUOTES, 'UTF-8').'" />
<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" name="jstree_group_selected" id="jstree_group_selected" value="'.$firstGroup.'" />';
if (isset($_GET['iSortCol_0'])) {
//echo $sOrder;
$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>';

Fixed code:  New: one time view page for anonymous user
$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_VALID_SESSION', "1004");
$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#";
 * @version       2.1.20
// Load CORE
require_once $_SESSION['settings']['cpassman_dir'].'/sources/core.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';

if (!isset($_GET['otv'])) {
	echo '
        </div>';
}
echo '
            <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($_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 {
        $('#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');
                    $("#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');
            		        $("#menu_button_share, #menu_button_otv").attr('disabled', 'disabled');
            		    	$("#menu_button_share, #menu_button_otv").prop("disabled", false);

/*
* 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"
   );
}
    !isset($_SESSION['CPM']) || $_SESSION['CPM'] != 1 ||
    !isset($_SESSION['user_id']) || empty($_SESSION['user_id']) ||
    !isset($_SESSION['key']) || empty($_SESSION['key']))
<input type="hidden" name="hid_cat" id="hid_cat" value="', isset($_GET['group']) ? htmlspecialchars($_GET['group']) : "", '" />
<input type="hidden" name="input_list_roles" id="input_list_roles" value="'.htmlspecialchars(htmlentities($listRoles, ENT_QUOTES, 'UTF-8')).'" />
<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" name="jstree_group_selected" id="jstree_group_selected" value="'.htmlspecialchars($firstGroup).'" />';
<?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;
$aSortTypes = array('ASC', 'DESC');
if (isset($_GET['iSortCol_0']) && in_array($_GET['iSortCol_0'], $aSortTypes)) {

} else if (!in_array($_GET['iSortCol_0'], $aSortTypes)) {
	// possible attack - stop
	echo '[{}]';


        /*
        * 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;