Skip to content

Commit 50b47ef

Browse files
Copilotnielsdrost7
andcommitted
Address code review feedback - improve path traversal detection and header sanitization
Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>
1 parent 8b64051 commit 50b47ef

2 files changed

Lines changed: 41 additions & 15 deletions

File tree

application/helpers/file_security_helper.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,15 @@ function validate_safe_filename(string $filename): array
3636
return ['valid' => false, 'hash' => $hash, 'error' => 'empty_filename'];
3737
}
3838

39-
// Check for path traversal sequences
40-
if (str_contains($filename, '../') || str_contains($filename, '..\\')) {
39+
// Check for path traversal sequences (including standalone ..)
40+
// Matches: ../, ..\, /.. and standalone ..
41+
if (str_contains($filename, '../') ||
42+
str_contains($filename, '..\\') ||
43+
str_contains($filename, '/..') ||
44+
str_contains($filename, '\\..') ||
45+
$filename === '..' ||
46+
str_starts_with($filename, '../') ||
47+
str_starts_with($filename, '..\\')) {
4148
return ['valid' => false, 'hash' => $hash, 'error' => 'path_traversal'];
4249
}
4350

@@ -91,9 +98,16 @@ function validate_file_in_directory(string $filePath, string $baseDirectory): bo
9198
*/
9299
function sanitize_filename_for_header(string $filename): string
93100
{
94-
// Remove all control characters (0x00-0x1F, 0x7F) and quotes
95-
// This prevents CRLF injection and response splitting attacks
96-
return preg_replace('/[\x00-\x1F\x7F"]/', '', $filename);
101+
// Remove all control characters (0x00-0x1F, 0x7F), quotes, and backslashes
102+
// This prevents CRLF injection and response splitting attacks, including backslash-based bypasses
103+
$sanitized = preg_replace('/[\x00-\x1F\x7F"\\\\]/', '', $filename);
104+
105+
// If sanitization results in empty string, use safe fallback
106+
if (empty($sanitized)) {
107+
return 'attachment.bin';
108+
}
109+
110+
return $sanitized;
97111
}
98112

99113
/**

application/modules/upload/controllers/Upload.php

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ public function delete_file(string $url_key): void
116116
public function get_file($filename): void
117117
{
118118
// Security: Removed urldecode() - CodeIgniter already handles URL decoding
119+
// First sanitize to handle the url_key_filename format
119120
$filename = $this->sanitize_file_name($filename);
120121

121122
$underscorePos = mb_strpos($filename, '_');
@@ -127,18 +128,29 @@ public function get_file($filename): void
127128

128129
$url_key = mb_substr($filename, 0, $underscorePos);
129130
$real_filename = mb_substr($filename, $underscorePos + 1);
130-
$fullPath = $this->get_target_file_path($url_key, $real_filename);
131131

132-
if ( ! file_exists($fullPath)) {
133-
log_message('debug', 'upload: File not found in uploads directory');
134-
$this->respond_message(404, 'upload_error_file_not_found', 'File not found');
135-
}
132+
// Security: Use comprehensive validation on the real filename component
133+
$validation = validate_file_access($real_filename, $this->targetPath);
136134

137-
// Security: Validate file is within allowed directory
138-
if (!validate_file_in_directory($fullPath, $this->targetPath)) {
139-
$filenameHash = hash_for_logging($filename);
140-
log_message('error', 'upload: Path traversal detected (hash: ' . $filenameHash . ')');
141-
$this->respond_message(403, 'upload_error_unauthorized_access', 'Unauthorized access');
135+
// For uploads, we need to check the actual path with url_key prefix
136+
if (!$validation['valid'] || $validation['error'] === 'file_not_found') {
137+
// Try with the url_key prefix (uploads use url_key_filename format)
138+
$fullPath = $this->get_target_file_path($url_key, $real_filename);
139+
140+
if (!file_exists($fullPath)) {
141+
log_message('debug', 'upload: File not found in uploads directory');
142+
$this->respond_message(404, 'upload_error_file_not_found', 'File not found');
143+
}
144+
145+
// Validate the actual path is within allowed directory
146+
if (!validate_file_in_directory($fullPath, $this->targetPath)) {
147+
$filenameHash = hash_for_logging($filename);
148+
log_message('error', 'upload: Path traversal detected (hash: ' . $filenameHash . ')');
149+
$this->respond_message(403, 'upload_error_unauthorized_access', 'Unauthorized access');
150+
}
151+
} else {
152+
// Use validated path from helper
153+
$fullPath = $this->get_target_file_path($url_key, $real_filename);
142154
}
143155

144156
$path_parts = pathinfo($fullPath);

0 commit comments

Comments
 (0)