@@ -770,7 +770,60 @@ function sse_validate_file_extension($filePath) {
770770 * @return string|false Resolved file path or false on failure.
771771 */
772772function sse_resolve_nonexistent_file_path ($ normalizedFilePath ) {
773- // Security: Get WordPress upload directory as our base validation point
773+ $ uploadInfo = sse_get_upload_directory_info ();
774+ if ($ uploadInfo === false ) {
775+ return false ;
776+ }
777+
778+ return sse_build_validated_file_path ($ normalizedFilePath , $ uploadInfo );
779+ }
780+
781+ /**
782+ * Builds validated file path from components.
783+ *
784+ * @param string $normalizedFilePath The normalized file path.
785+ * @param array $uploadInfo Upload directory information.
786+ * @return string|false Real file path on success, false on failure.
787+ */
788+ function sse_build_validated_file_path ($ normalizedFilePath , $ uploadInfo ) {
789+ $ parentDir = dirname ($ normalizedFilePath );
790+ $ filename = basename ($ normalizedFilePath );
791+
792+ if (!sse_validate_parent_directory_safety ($ parentDir , $ uploadInfo ['basedir ' ])) {
793+ return false ;
794+ }
795+
796+ return sse_construct_final_file_path ($ parentDir , $ filename , $ uploadInfo ['realpath ' ]);
797+ }
798+
799+ /**
800+ * Constructs final file path after validation.
801+ *
802+ * @param string $parentDir Parent directory path.
803+ * @param string $filename File name.
804+ * @param string $uploadRealPath Upload directory real path.
805+ * @return string|false Final file path on success, false on failure.
806+ */
807+ function sse_construct_final_file_path ($ parentDir , $ filename , $ uploadRealPath ) {
808+ $ realParentDir = sse_resolve_parent_directory ($ parentDir , $ uploadRealPath );
809+ if ($ realParentDir === false ) {
810+ return false ;
811+ }
812+
813+ $ sanitizedFilename = sse_sanitize_filename ($ filename );
814+ if ($ sanitizedFilename === false ) {
815+ return false ;
816+ }
817+
818+ return trailingslashit ($ realParentDir ) . $ sanitizedFilename ;
819+ }
820+
821+ /**
822+ * Gets WordPress upload directory information with validation.
823+ *
824+ * @return array|false Upload directory info array or false on failure.
825+ */
826+ function sse_get_upload_directory_info () {
774827 $ uploadDir = wp_upload_dir ();
775828 if (!isset ($ uploadDir ['basedir ' ]) || empty ($ uploadDir ['basedir ' ])) {
776829 sse_log ('Could not determine WordPress upload directory for validation ' , 'error ' );
@@ -783,102 +836,74 @@ function sse_resolve_nonexistent_file_path($normalizedFilePath) {
783836 return false ;
784837 }
785838
786- // Security: Only allow paths within WordPress upload directory (strict allowlist)
787- $ parentDir = dirname ($ normalizedFilePath );
788- $ filename = basename ($ normalizedFilePath );
789-
790- // Pre-validate that parent directory path looks safe before any filesystem operations
839+ return array (
840+ 'basedir ' => $ uploadDir ['basedir ' ],
841+ 'realpath ' => $ wpUploadDir
842+ );
843+ }
844+
845+ /**
846+ * Validates parent directory path safety.
847+ *
848+ * @param string $parentDir The parent directory path.
849+ * @param string $uploadDir The upload directory path.
850+ * @return bool True if safe, false otherwise.
851+ */
852+ function sse_validate_parent_directory_safety ($ parentDir , $ uploadDir ) {
853+ // Pre-validate that parent directory path looks safe
791854 if (strpos ($ parentDir , '.. ' ) !== false || strpos ($ parentDir , 'wp-config ' ) !== false ) {
792855 sse_log ('Rejected unsafe parent directory path: ' . $ parentDir , 'security ' );
793856 return false ;
794857 }
795858
796- // Security: Ensure parent directory is within WordPress upload directory
859+ // Ensure parent directory is within WordPress upload directory
797860 $ normalizedParentDir = wp_normalize_path ($ parentDir );
798- $ normalizedUploadDir = wp_normalize_path ($ uploadDir[ ' basedir ' ] );
861+ $ normalizedUploadDir = wp_normalize_path ($ uploadDir );
799862
800863 if (strpos ($ normalizedParentDir , $ normalizedUploadDir ) !== 0 ) {
801864 sse_log ('Parent directory not within WordPress upload directory: ' . $ parentDir , 'security ' );
802865 return false ;
803866 }
804867
805- // Now it's safe to check if directory exists (after validation)
806- if ( !is_dir ( $ parentDir ) || !is_readable ( $ parentDir ) ) {
868+ return true ;
869+ }
870+
871+ /**
872+ * Resolves and validates parent directory.
873+ *
874+ * @param string $parentDir The parent directory path.
875+ * @param string $uploadDir The upload directory path.
876+ * @return string|false Real parent directory path or false on failure.
877+ */
878+ function sse_resolve_parent_directory ($ parentDir , $ uploadDir ) {
879+ if (!is_dir ($ parentDir ) || !is_readable ($ parentDir )) {
807880 sse_log ('Parent directory validation failed: ' . $ parentDir , 'security ' );
808881 return false ;
809882 }
810883
811- $ realParentDir = realpath ( $ parentDir );
812- if ( $ realParentDir === false || strpos ($ realParentDir , $ wpUploadDir ) !== 0 ) {
884+ $ realParentDir = realpath ($ parentDir );
885+ if ($ realParentDir === false || strpos ($ realParentDir , $ uploadDir ) !== 0 ) {
813886 sse_log ('Parent directory real path validation failed ' , 'security ' );
814887 return false ;
815888 }
816889
817- // Sanitize filename to prevent directory traversal
818- $ filename = sanitize_file_name ( $ filename );
819- if (strpos ($ filename , '.. ' ) !== false || strpos ($ filename , '/ ' ) !== false || strpos ($ filename , '\\' ) !== false ) {
820- sse_log ('Filename contains invalid characters: ' . $ filename , 'security ' );
821- return false ;
822- }
823-
824- return trailingslashit ( $ realParentDir ) . $ filename ;
890+ return $ realParentDir ;
825891}
826892
827893/**
828- * Resolves real file path, handling non-existent files securely .
894+ * Sanitizes filename to prevent directory traversal .
829895 *
830- * @param string $normalizedFilePath The normalized file path .
831- * @return string|false Real file path on success, false on failure.
896+ * @param string $filename The filename to sanitize .
897+ * @return string|false Sanitized filename or false on failure.
832898 */
833- function sse_resolve_file_path ($ normalizedFilePath ) {
834- // Security: Only allow files with safe extensions and within WordPress directory
835- $ allowedExtensions = array ('zip ' , 'sql ' );
836- $ fileExtension = strtolower (pathinfo ($ normalizedFilePath , PATHINFO_EXTENSION ));
837-
838- if (!in_array ($ fileExtension , $ allowedExtensions , true )) {
839- sse_log ('Rejected file access - invalid extension: ' . $ fileExtension , 'security ' );
899+ function sse_sanitize_filename ($ filename ) {
900+ $ filename = sanitize_file_name ($ filename );
901+ if (strpos ($ filename , '.. ' ) !== false || strpos ($ filename , '/ ' ) !== false || strpos ($ filename , '\\' ) !== false ) {
902+ sse_log ('Filename contains invalid characters: ' . $ filename , 'security ' );
840903 return false ;
841904 }
842905
843- $ realFilePath = realpath ($ normalizedFilePath );
844-
845- // If realpath fails for the file (doesn't exist), validate parent directory more securely
846- if ($ realFilePath === false ) {
847- $ parentDir = dirname ($ normalizedFilePath );
848- $ filename = basename ($ normalizedFilePath );
849-
850- // Security: Additional validation to prevent SSRF - ensure parent is within WordPress
851- $ wpContentDir = realpath (WP_CONTENT_DIR );
852- if ($ wpContentDir === false ) {
853- sse_log ('Could not resolve WP_CONTENT_DIR for security validation ' , 'error ' );
854- return false ;
855- }
856-
857- // Validate parent directory exists, is readable, and is within WP_CONTENT_DIR
858- if ( !is_dir ( $ parentDir ) || !is_readable ( $ parentDir ) ) {
859- sse_log ('Parent directory validation failed: ' . $ parentDir , 'security ' );
860- return false ;
861- }
862-
863- $ realParentDir = realpath ( $ parentDir );
864- if ( $ realParentDir === false || strpos ($ realParentDir , $ wpContentDir ) !== 0 ) {
865- sse_log ('Parent directory not within WordPress content directory ' , 'security ' );
866- return false ;
867- }
868-
869- $ realFileDir = $ realParentDir ;
870-
871- // Sanitize filename to prevent directory traversal
872- $ filename = sanitize_file_name ( $ filename );
873- if (strpos ($ filename , '.. ' ) !== false || strpos ($ filename , '/ ' ) !== false || strpos ($ filename , '\\' ) !== false ) {
874- sse_log ('Filename contains invalid characters: ' . $ filename , 'security ' );
875- return false ;
876- }
877-
878- $ realFilePath = trailingslashit ( $ realFileDir ) . $ filename ;
879- }
880-
881- return $ realFilePath ;
906+ return $ filename ;
882907}
883908
884909/**
0 commit comments