Fix path traversal, closed-connection, and ticket ID validation bugs

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 17:57:36 -04:00
parent 01f2dac2d6
commit fbda618fbb
3 changed files with 7 additions and 7 deletions
+2 -2
View File
@@ -101,9 +101,8 @@ if (!$operationId) {
// Process the bulk operation // Process the bulk operation
$result = $bulkOpsModel->processBulkOperation($operationId); $result = $bulkOpsModel->processBulkOperation($operationId);
$conn->close();
if (isset($result['error'])) { if (isset($result['error'])) {
$conn->close();
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'error' => $result['error'] 'error' => $result['error']
@@ -112,6 +111,7 @@ if (isset($result['error'])) {
// Invalidate stats cache so dashboard tiles reflect changes immediately // Invalidate stats cache so dashboard tiles reflect changes immediately
require_once dirname(__DIR__) . '/models/StatsModel.php'; require_once dirname(__DIR__) . '/models/StatsModel.php';
(new StatsModel($conn))->invalidateCache(); (new StatsModel($conn))->invalidateCache();
$conn->close();
$message = "Bulk operation completed: {$result['processed']} succeeded, {$result['failed']} failed"; $message = "Bulk operation completed: {$result['processed']} succeeded, {$result['failed']} failed";
if ($inaccessibleCount > 0) { if ($inaccessibleCount > 0) {
+1 -1
View File
@@ -73,7 +73,7 @@ try {
$realUploadDir = realpath($uploadDir); $realUploadDir = realpath($uploadDir);
$realFilePath = realpath($filePath); $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); http_response_code(403);
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(['success' => false, 'error' => 'Access denied']); echo json_encode(['success' => false, 'error' => 'Access denied']);
+4 -4
View File
@@ -41,8 +41,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
ResponseHelper::error('Ticket ID is required'); ResponseHelper::error('Ticket ID is required');
} }
// Validate ticket ID format (9-digit number) // Validate ticket ID format (positive integer)
if (!preg_match('/^\d{9}$/', $ticketId)) { if (!preg_match('/^\d+$/', $ticketId)) {
ResponseHelper::error('Invalid ticket ID format'); ResponseHelper::error('Invalid ticket ID format');
} }
@@ -86,8 +86,8 @@ if (empty($ticketId)) {
ResponseHelper::error('Ticket ID is required'); ResponseHelper::error('Ticket ID is required');
} }
// Validate ticket ID format (9-digit number) // Validate ticket ID format (positive integer)
if (!preg_match('/^\d{9}$/', $ticketId)) { if (!preg_match('/^\d+$/', $ticketId)) {
ResponseHelper::error('Invalid ticket ID format'); ResponseHelper::error('Invalid ticket ID format');
} }