CVE-2021-21352: Use of Insufficiently Random Values (CWE-330)
value: Anuko Time Tracker is an open source, web-based time tracking application written in PHP. In TimeTracker before version 1.19.24.5415 tokens used in password reset feature in Time Tracker are based on system time and, therefore, are predictable. This opens a window for brute force attacks to guess user tokens and, once successful, change user passwords, including that of a system administrator. This vulnerability is pathced in version 1.19.24.5415 (started to use more secure tokens) with an additional improvement in 1.19.24.5416 (limited an available window for brute force token guessing).
CWE description: The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
CVSS v3 base score: 9.1
Published: 2021-03-03T01:15Z
Repository: https://github.com/anuko/timetracker
Language: PHP
Files changed: WEB-INF/templates/report.tpl, initialize.php, password_reset.php
Commit date: 2021-02-27 16:00:53 +0000
Commit message: Fixed a critical security vulnerability with password resets.

Diff: diff --git a/WEB-INF/templates/report.tpl b/WEB-INF/templates/report.tpl
index f7ef4ff2..0a13ea83 100644
--- a/WEB-INF/templates/report.tpl
+++ b/WEB-INF/templates/report.tpl
@@ -115,10 +115,11 @@
         {if $bean->getAttribute('chip')}<td></td>{/if}
         {if $bean->getAttribute('chinvoice')}<td></td>{/if}
         {if $bean->getAttribute('chtimesheet')}<td></td>{/if}
         {if $bean->getAttribute('chfiles')}<td></td>{/if}
         {if $use_checkboxes}<td></td>{/if}
+        {* <td></td>{* column for edit icons *}
       </tr>
       <tr><td>&nbsp;</td></tr>
       {/if}
     {$first_pass = false}
     {/if}
@@ -169,10 +170,11 @@
     {/if}
     {if $use_checkboxes}
       {if 1 == $item.type}<td bgcolor="white"><input type="checkbox" name="log_id_{$item.id}"></td>{/if}
       {if 2 == $item.type}<td bgcolor="white"><input type="checkbox" name="item_id_{$item.id}"></td>{/if}
     {/if}
+     {* <td><a href="time_edit.php?id={$item.id}"><img class="table_icon" alt="{$i18n.label.edit}" src="img/icon-edit.png"></a></td>{* column for edit icons *}
       </tr>
     {if $note_on_separate_row && $bean->getAttribute('chnote') && $item.note}
       <tr>
         <td class="cellRightAligned">{$i18n.label.note}:</td>
         <td colspan="{$colspan}">{$item.note|escape}</td>
@@ -218,10 +220,11 @@
     {if $bean->getAttribute('chip')}<td></td>{/if}
     {if $bean->getAttribute('chinvoice')}<td></td>{/if}
     {if $bean->getAttribute('chtimesheet')}<td></td>{/if}
     {if $bean->getAttribute('chfiles')}<td></td>{/if}
     {if $use_checkboxes}<td></td>{/if}
+    {* <td></td>{* column for edit icons *}
       </tr>
   {/if}
   <!-- print totals -->
       <tr><td>&nbsp;</td></tr>
       <tr class="rowReportSubtotal">
@@ -259,10 +262,11 @@
     {if $bean->getAttribute('chip')}<td></td>{/if}
     {if $bean->getAttribute('chinvoice')}<td></td>{/if}
     {if $bean->getAttribute('chtimesheet')}<td></td>{/if}
     {if $bean->getAttribute('chfiles')}<td></td>{/if}
     {if $use_checkboxes}<td></td>{/if}
+    {* <td></td>{* column for edit icons *}
       </tr>
 {/if}
     </table>
   </td>
 </tr>
diff --git a/initialize.php b/initialize.php
index 5323d00b..e2aeb6c8 100644
--- a/initialize.php
+++ b/initialize.php
@@ -11,11 +11,11 @@ error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT); // & ~E_DEPRECATED);
 
 // Disable displaying errors on screen.
 ini_set('display_errors', 'Off');
 
 // require_once('init_auth.php');
-define("APP_VERSION", "1.19.23.5414");
+define("APP_VERSION", "1.19.24.5415");
 define("APP_DIR", dirname(__FILE__));
 define("LIBRARY_DIR", APP_DIR."/WEB-INF/lib");
 define("TEMPLATE_DIR", APP_DIR."/WEB-INF/templates");
 // Date format for database and URI parameters.
 define('DB_DATEFORMAT', '%Y-%m-%d');
diff --git a/password_reset.php b/password_reset.php
index 5beafb7f..53099b9a 100644
--- a/password_reset.php
+++ b/password_reset.php
@@ -44,11 +44,14 @@ if ($request->isPost()) {
     if (ttUserHelper::recentRefExists($user->id)) $err->add($i18n->get('error.access_denied'));
   }
 
   if ($err->no()) {
     // Prepare and save a temporary reference for user.
-    $temp_ref = md5(uniqid());
+    $cryptographically_strong = true;
+    $random_bytes = openssl_random_pseudo_bytes(16, $cryptographically_strong);
+    if ($random_bytes === false) die ("openssl_random_pseudo_bytes function call failed...");
+    $temp_ref = bin2hex($random_bytes);
     ttUserHelper::saveTmpRef($temp_ref, $user->id);
 
     $user_i18n = null;
     if ($user->lang != $i18n->lang) {
       $user_i18n = new I18n();

Vulnerable code: define("APP_VERSION", "1.19.23.5414");
    $temp_ref = md5(uniqid());
Fixed code:         {* <td></td>{* column for edit icons *}
     {* <td><a href="time_edit.php?id={$item.id}"><img class="table_icon" alt="{$i18n.label.edit}" src="img/icon-edit.png"></a></td>{* column for edit icons *}
    {* <td></td>{* column for edit icons *}
    {* <td></td>{* column for edit icons *}
define("APP_VERSION", "1.19.24.5415");
    $cryptographically_strong = true;
    $random_bytes = openssl_random_pseudo_bytes(16, $cryptographically_strong);
    if ($random_bytes === false) die ("openssl_random_pseudo_bytes function call failed...");
    $temp_ref = bin2hex($random_bytes);