From fbda618fbbd12c000897130c83cdaca8e4756554 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 5 Apr 2026 17:57:36 -0400 Subject: [PATCH] Fix path traversal, closed-connection, and ticket ID validation bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - download_attachment.php: path traversal check used strpos() without trailing DIRECTORY_SEPARATOR, allowing /uploads_evil/* to pass when upload dir is /uploads — now checks realPath + DIRECTORY_SEPARATOR prefix - bulk_operation.php: $conn->close() was called before StatsModel($conn) construction; moved close() inside each branch to after all DB use - upload_attachment.php: ticket ID validated as /^\d{9}$/ (exactly 9 digits) breaking all tickets below ID 1,000,000,000 — changed to /^\d+$/ for any positive integer Co-Authored-By: Claude Sonnet 4.6 --- api/bulk_operation.php | 4 ++-- api/download_attachment.php | 2 +- api/upload_attachment.php | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/bulk_operation.php b/api/bulk_operation.php index 27fd7cb..0d42085 100644 --- a/api/bulk_operation.php +++ b/api/bulk_operation.php @@ -101,9 +101,8 @@ if (!$operationId) { // Process the bulk operation $result = $bulkOpsModel->processBulkOperation($operationId); -$conn->close(); - if (isset($result['error'])) { + $conn->close(); echo json_encode([ 'success' => false, 'error' => $result['error'] @@ -112,6 +111,7 @@ if (isset($result['error'])) { // Invalidate stats cache so dashboard tiles reflect changes immediately require_once dirname(__DIR__) . '/models/StatsModel.php'; (new StatsModel($conn))->invalidateCache(); + $conn->close(); $message = "Bulk operation completed: {$result['processed']} succeeded, {$result['failed']} failed"; if ($inaccessibleCount > 0) { diff --git a/api/download_attachment.php b/api/download_attachment.php index 27b9496..35763ed 100644 --- a/api/download_attachment.php +++ b/api/download_attachment.php @@ -73,7 +73,7 @@ try { $realUploadDir = realpath($uploadDir); $realFilePath = realpath($filePath); - if ($realFilePath === false || $realUploadDir === false || strpos($realFilePath, $realUploadDir) !== 0) { + if ($realFilePath === false || $realUploadDir === false || strpos($realFilePath, $realUploadDir . DIRECTORY_SEPARATOR) !== 0) { http_response_code(403); header('Content-Type: application/json'); echo json_encode(['success' => false, 'error' => 'Access denied']); diff --git a/api/upload_attachment.php b/api/upload_attachment.php index 0412148..ebc6172 100644 --- a/api/upload_attachment.php +++ b/api/upload_attachment.php @@ -41,8 +41,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { ResponseHelper::error('Ticket ID is required'); } - // Validate ticket ID format (9-digit number) - if (!preg_match('/^\d{9}$/', $ticketId)) { + // Validate ticket ID format (positive integer) + if (!preg_match('/^\d+$/', $ticketId)) { ResponseHelper::error('Invalid ticket ID format'); } @@ -86,8 +86,8 @@ if (empty($ticketId)) { ResponseHelper::error('Ticket ID is required'); } -// Validate ticket ID format (9-digit number) -if (!preg_match('/^\d{9}$/', $ticketId)) { +// Validate ticket ID format (positive integer) +if (!preg_match('/^\d+$/', $ticketId)) { ResponseHelper::error('Invalid ticket ID format'); }