Skip to content

Commit 336df06

Browse files
committed
Fix static file serving security vulnerabilities
- Add URL decoding to prevent encoded path traversal attacks - Add null byte injection prevention - Normalize paths by removing consecutive slashes - Add explicit path traversal pattern rejection - Fix directory boundary check to prevent path escape - Add X-Content-Type-Options and Content-Length headers - Remove URL fragments for proper normalization
1 parent 3a50d43 commit 336df06

File tree

1 file changed

+30
-1
lines changed

1 file changed

+30
-1
lines changed

src/Server/HttpServer.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,27 @@ private function tryServeStaticFile(Request $request, Response $response): bool
298298
$uri = substr($uri, 0, $pos);
299299
}
300300

301+
// Remove fragment
302+
if (($pos = strpos($uri, '#')) !== false) {
303+
$uri = substr($uri, 0, $pos);
304+
}
305+
306+
// Decode URL-encoded characters to prevent encoded path traversal attacks
307+
$uri = rawurldecode($uri);
308+
309+
// Security: Check for null bytes and reject
310+
if (strpos($uri, "\0") !== false) {
311+
return false;
312+
}
313+
314+
// Normalize path separators and remove consecutive slashes
315+
$uri = preg_replace('#/+#', '/', $uri);
316+
317+
// Security: Reject URIs containing path traversal patterns
318+
if (strpos($uri, '..') !== false) {
319+
return false;
320+
}
321+
301322
// Get file extension
302323
$extension = pathinfo($uri, PATHINFO_EXTENSION);
303324

@@ -315,7 +336,13 @@ private function tryServeStaticFile(Request $request, Response $response): bool
315336
}
316337

317338
$realPath = realpath($filePath);
318-
if ($realPath === false || strpos($realPath, $this->realDocRoot) !== 0) {
339+
if ($realPath === false) {
340+
return false;
341+
}
342+
343+
// Security: Ensure realPath is within documentRoot with proper directory boundary check
344+
$realDocRootWithSeparator = rtrim($this->realDocRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
345+
if (strpos($realPath . DIRECTORY_SEPARATOR, $realDocRootWithSeparator) !== 0) {
319346
return false;
320347
}
321348

@@ -333,6 +360,8 @@ private function tryServeStaticFile(Request $request, Response $response): bool
333360
// Set response headers
334361
$response->status(200);
335362
$response->header('Content-Type', $this->staticFileExtensions[$extension]);
363+
$response->header('Content-Length', (string) strlen($content));
364+
$response->header('X-Content-Type-Options', 'nosniff');
336365

337366
// Add cache headers for static files
338367
$lastModified = filemtime($realPath);

0 commit comments

Comments
 (0)