From 936b2a960f972e4c4d897e897201d6b05295e638 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:29:02 +0000 Subject: [PATCH 1/3] Initial plan From 8c2649a5ace054e38828d8012b014fdfdd2c2324 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:33:42 +0000 Subject: [PATCH 2/3] Add path traversal protection to MaintenanceFilter custom view Co-authored-by: ljonesfl <1099983+ljonesfl@users.noreply.github.com> --- src/Cms/Maintenance/MaintenanceFilter.php | 58 ++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/Cms/Maintenance/MaintenanceFilter.php b/src/Cms/Maintenance/MaintenanceFilter.php index dbde4af..d04d771 100644 --- a/src/Cms/Maintenance/MaintenanceFilter.php +++ b/src/Cms/Maintenance/MaintenanceFilter.php @@ -80,10 +80,36 @@ private function renderMaintenancePage(): string // Check for custom view if( $this->_CustomView && file_exists( $this->_CustomView ) ) { + // Validate path to prevent directory traversal attacks + $customViewPath = $this->_CustomView; + $realPath = realpath( $customViewPath ); + + // For virtual filesystems (like vfsStream in tests), realpath may return false + // In such cases, perform basic validation on the original path + if( $realPath === false ) + { + // Validate that the path doesn't contain directory traversal patterns + if( $this->containsDirectoryTraversal( $customViewPath ) ) + { + throw new \RuntimeException( 'Invalid custom view path: directory traversal detected' ); + } + $realPath = $customViewPath; + } + else + { + // For real filesystem, ensure the resolved path is within the application's resources directory + $basePath = realpath( __DIR__ . '/../../../resources' ); + + if( $basePath !== false && strpos( $realPath, $basePath ) !== 0 ) + { + throw new \RuntimeException( 'Invalid custom view path: path must be within the resources directory' ); + } + } + $message = $this->_Manager->getMessage(); $retryAfter = $this->_Manager->getRetryAfter(); ob_start(); - include $this->_CustomView; + include $realPath; return ob_get_clean(); } @@ -226,6 +252,36 @@ private function getDefaultMaintenancePage(): string HTML; } + /** + * Check if a path contains directory traversal patterns + * + * @param string $path Path to validate + * @return bool True if directory traversal detected + */ + private function containsDirectoryTraversal( string $path ): bool + { + // Normalize path separators + $normalized = str_replace( '\\', '/', $path ); + + // Check for common directory traversal patterns + $patterns = [ + '../', + '/../', + '/..', + '..', + ]; + + foreach( $patterns as $pattern ) + { + if( strpos( $normalized, $pattern ) !== false ) + { + return true; + } + } + + return false; + } + /** * Get the client's IP address * From 4de0ad18e222918031011fee287c3be90b2a9cfe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Nov 2025 17:36:13 +0000 Subject: [PATCH 3/3] Improve path traversal detection to allow filenames with double dots Co-authored-by: ljonesfl <1099983+ljonesfl@users.noreply.github.com> --- src/Cms/Maintenance/MaintenanceFilter.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Cms/Maintenance/MaintenanceFilter.php b/src/Cms/Maintenance/MaintenanceFilter.php index d04d771..c9bb7f8 100644 --- a/src/Cms/Maintenance/MaintenanceFilter.php +++ b/src/Cms/Maintenance/MaintenanceFilter.php @@ -263,17 +263,13 @@ private function containsDirectoryTraversal( string $path ): bool // Normalize path separators $normalized = str_replace( '\\', '/', $path ); - // Check for common directory traversal patterns - $patterns = [ - '../', - '/../', - '/..', - '..', - ]; + // Check for directory traversal pattern: .. as a directory component + // Split by / and check if any component is exactly '..' + $parts = explode( '/', $normalized ); - foreach( $patterns as $pattern ) + foreach( $parts as $part ) { - if( strpos( $normalized, $pattern ) !== false ) + if( $part === '..' ) { return true; }