diff --git a/.gitignore b/.gitignore index dd0bf48d..74d875e4 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ setup/custom/* api/v1/custom/* !api/v1/custom/readme.php .zed + +*.patch diff --git a/itflow-share-view-toctou.patch b/itflow-share-view-toctou.patch deleted file mode 100644 index c299c31d..00000000 --- a/itflow-share-view-toctou.patch +++ /dev/null @@ -1,143 +0,0 @@ -diff --git a/functions/security.php b/functions/security.php -index cda79fc..24c9632 100644 ---- a/functions/security.php -+++ b/functions/security.php -@@ -225,3 +225,24 @@ zgjRYR/zGN5l+az6RB3+0mJRdZdv/y2aRkBlwTxx2gOrPbQAco4a/IOmkE3EbHe7 - - return false; - } -+ -+// Atomically claim one view against a shared item's view limit. -+// Returns true only if this request won the view; false means the share is -+// inactive, expired, or out of views. The UPDATE is the claim, so concurrent -+// requests cannot all pass - call this before any shared content is disclosed. -+function claimSharedItemView($item_id) { -+ global $mysqli; -+ -+ $item_id = intval($item_id); -+ -+ mysqli_query($mysqli, "UPDATE shared_items -+ SET item_views = item_views + 1 -+ WHERE item_id = $item_id -+ AND item_active = 1 -+ AND item_expire_at > NOW() -+ AND (COALESCE(item_view_limit, 0) = 0 OR item_views < item_view_limit)" -+ ); -+ -+ // -1 (query error) and 0 (limit reached / revoked / expired) both deny -+ return mysqli_affected_rows($mysqli) === 1; -+} -diff --git a/guest/guest_download_file.php b/guest/guest_download_file.php -index 22610f3..c6cd12b 100644 ---- a/guest/guest_download_file.php -+++ b/guest/guest_download_file.php -@@ -56,6 +56,12 @@ if (isset($_GET['id']) && isset($_GET['key'])) { - exit("Item cannot be viewed at this time (No file, may have been deleted)."); - } - -+ // Claim the view before the file is served. The checks above stay as a -+ // fast path for messaging - this UPDATE is what enforces the limit. -+ if (!claimSharedItemView($item_id)) { -+ exit("Item cannot be viewed at this time (view limit exceeded)."); -+ } -+ - $file_name = escapeSql($file_row['file_name']); - $file_reference_name = escapeSql($file_row['file_reference_name']); - $client_id = intval($file_row['file_client_id']); -@@ -67,10 +73,6 @@ if (isset($_GET['id']) && isset($_GET['key'])) { - header('Content-Disposition: attachment; filename=' . $file_name); - readfile($file_path); - -- // Update file view count -- $new_item_views = $item_views + 1; -- mysqli_query($mysqli, "UPDATE shared_items SET item_views = $new_item_views WHERE item_id = $item_id"); -- - //Logging - logAudit("Share", "View", "Downloaded shared file $file_name via link", $client_id); - -diff --git a/guest/guest_post.php b/guest/guest_post.php -index 7cec8a9..6ab945d 100644 ---- a/guest/guest_post.php -+++ b/guest/guest_post.php -@@ -13,6 +13,10 @@ session_start(); - - require_once "../includes/inc_set_timezone.php"; // Must be included after session_start to work - -+// logAudit() reads these globals - without them guest audit rows have no IP -+$session_ip = escapeSql(getIP()); -+$session_user_agent = escapeSql($_SERVER['HTTP_USER_AGENT']); -+ - if (isset($_GET['accept_quote'], $_GET['url_key'])) { - - $quote_id = intval($_GET['accept_quote']); -diff --git a/guest/guest_view_item.php b/guest/guest_view_item.php -index e6b0417..68f87fc 100644 ---- a/guest/guest_view_item.php -+++ b/guest/guest_view_item.php -@@ -128,6 +128,14 @@ if ($item_type == "Document") { - exit(); - } - -+ // Claim the view before any content is disclosed -+ if (!claimSharedItemView($item_id)) { -+ echo "
Item cannot be viewed at this time. Check with the person that sent you this link to ensure it is correct and has not expired.
"; -+ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; -+ -+ exit(); -+ } -+ - $doc_title = escapeHtml($doc_row['document_name']); - $doc_title_escaped = escapeSql($doc_row['document_name']); - $doc_content = $purifier->purify($doc_row['document_content']); -@@ -135,10 +143,6 @@ if ($item_type == "Document") { - echo "

$doc_title

"; - echo "
$doc_content
"; - -- // Update document view count -- $new_item_views = $item_views + 1; -- mysqli_query($mysqli, "UPDATE shared_items SET item_views = $new_item_views WHERE item_id = $item_id"); -- - // Logging - $name = mysqli_real_escape_string($mysqli, $doc_title); - logAudit("Share", "View", "Viewed shared $item_type $doc_title_escaped via link", $client_id); -@@ -176,6 +180,14 @@ if ($item_type == "Document") { - exit(); - } - -+ // Claim the view before the credential is decrypted or rendered -+ if (!claimSharedItemView($item_id)) { -+ echo "
Item cannot be viewed at this time. Check with the person that sent you this link to ensure it is correct and has not expired.
"; -+ require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/footer.php'; -+ -+ exit(); -+ } -+ - $credential_id = intval($credential_row['credential_id']); - $credential_name = escapeHtml($credential_row['credential_name']); - $credential_uri = escapeHtml($credential_row['credential_uri']); -@@ -254,10 +266,6 @@ if ($item_type == "Document") { - -