CVE-2021-23407 (C#, published 2021-07-14T17:15Z)
Description: value: This affects the package elFinder.Net.Core from 0 and before 1.2.4. The user-controlled file name is not properly sanitized before it is used to create a file system path.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/trannamtrung1st/elFinder.Net.Core
Files changed:
elFinder.Net.Core/Demos/elFinder.Net.AdvancedDemo/Startup.cs
elFinder.Net.Core/Demos/elFinder.Net.Demo31/Startup.cs
elFinder.Net.Core/elFinder.Net.Core/Connector.cs
elFinder.Net.Core/elFinder.Net.Core/Exceptions/InvalidDirNameException.cs
elFinder.Net.Core/elFinder.Net.Core/Exceptions/InvalidFileNameException.cs
elFinder.Net.Core/elFinder.Net.Core/Models/Command/PutCommand.cs
elFinder.Net.Core/elFinder.Net.Core/Models/PathInfo.cs
elFinder.Net.Core/elFinder.Net.Core/Models/Response/ErrorResponse.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/Extensions/IDirectoryExtensions.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/Extensions/IFileExtensions.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/FileSystemDirectory.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/FileSystemDriver.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/FileSystemFile.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/Helpers/PathHelper.cs
elFinder.Net.Core/elFinder.Net.Drivers.FileSystem/IZipDownloadPathProvider.cs
Commit message: Finish security patch 1.2.4


Vulnerable code:
            return PathHelper.GetFullPath(basePath, path);
            return PathHelper.GetFullPath(basePath, path);
﻿namespace elFinder.Net.Core
            if (!Directory.Exists(Path.Combine(directory.Parent.FullName, newName)))
            if (!File.Exists(Path.Combine(file.DirectoryName, newName)))
            var newPath = PathHelper.GetFullPath(Parent.FullName, newName);
                var newDir = new FileSystemDirectory(Path.Combine(targetPath.Directory.FullName, cmd.Name), volume);
                var newDir = new FileSystemDirectory(Path.Combine(targetPath.Directory.FullName, dirName), volume);
            var newFile = new FileSystemFile(Path.Combine(targetPath.Directory.FullName, cmd.Name), volume);
                    string uploadFullName = Path.Combine(dest.FullName, Path.GetFileName(formFile.FileName));
                            var fullBakName = Path.Combine(uploadFileInfo.Parent.FullName, backupName);
                            uploadFullName = Path.Combine(uploadFileInfo.DirectoryName, newName);
                    var newDest = Path.Combine(dstPath.Directory.FullName, src.Directory.Name);
                        var fullBakName = Path.Combine(newDestDir.Parent.FullName, backupName);
                    var newDest = Path.Combine(dstPath.Directory.FullName, file.Name);
                        var fullBakName = Path.Combine(newDestFile.Parent.FullName, backupName);
                    var newDest = Path.Combine(src.Directory.Parent.FullName, newName);
            var directoryInfo = targetPath.Directory;
            var archivePath = Path.Combine(directoryInfo.FullName, filename);
                fromPath = Path.Combine(fromPath, Path.GetFileNameWithoutExtension(targetPath.File.Name));
                    string fullName = PathHelper.GetFullPathNormalized(fromPath, entry.FullName);
            var memStream = new MemoryStream();

            string newPath = Path.Combine(newDir, file.Name);
                    newPath = Path.Combine(newDir, newName);
            string newPath = Path.Combine(newDir, file.Name);
                    newPath = Path.Combine(newDir, newName);
                    var newDir = new FileSystemDirectory(Path.Combine(currentNewDest.FullName, dir.Name), directory.Volume);
                    var newDir = new FileSystemDirectory(Path.Combine(currentNewDest.FullName, dir.Name), srcDir.Volume);
            var newPath = PathHelper.GetFullPath(Parent.FullName, newName);
﻿using System.IO;
        public static string GetFullPath(params string[] paths)
            var fullPath = Path.GetFullPath(Path.Combine(paths));
        public static string GetFullPathNormalized(params string[] paths)
            var fullPath = Path.GetFullPath(Path.Combine(paths)).TrimEnd(Path.DirectorySeparatorChar);
            return fullPath.TrimEnd(Path.DirectorySeparatorChar);
﻿using System.IO;
            var tempFile = Path.GetTempFileName();

            return Task.FromResult(Path.Combine(tempDirPath, archiveFileKey));

Fixed code:
            return PathHelper.GetFullPath(Path.Combine(basePath, path));
using System.IO;
            return PathHelper.GetFullPath(Path.Combine(basePath, path));
                            if (putCmd.Encoding == "hash")
                            {
                                putCmd.ContentPath = await ParsePathAsync(putCmd.Content, cancellationToken: cancellationToken);
                                if (putCmd.ContentPath.IsDirectory)
                                    throw new NotFileException();
                            }

﻿using elFinder.Net.Core.Models.Response;

namespace elFinder.Net.Core.Exceptions
{
    public class InvalidDirNameException : ConnectorException
    {
        public InvalidDirNameException()
        {
            ErrorResponse = new ErrorResponse(this)
            {
                error = ErrorResponse.InvalidDirName
            };
        }
    }
}
﻿using elFinder.Net.Core.Models.Response;

namespace elFinder.Net.Core.Exceptions
{
    public class InvalidFileNameException : ConnectorException
    {
        public InvalidFileNameException()
        {
            ErrorResponse = new ErrorResponse(this)
            {
                error = ErrorResponse.InvalidFileName
            };
        }
    }
}

        public PathInfo ContentPath { get; set; }
﻿using elFinder.Net.Core.Exceptions;

namespace elFinder.Net.Core

            if (!volume.Own(FileSystem)) throw new PermissionDeniedException("Volume must own this path");

            if (!volume.Own(FileSystem)) throw new PermissionDeniedException("Volume must own this path");
        public const string InvalidFileName = "errInvName";
        public const string InvalidDirName = "errInvDirname";
using elFinder.Net.Drivers.FileSystem.Helpers;
            if (!Directory.Exists(PathHelper.SafelyCombine(directory.Parent.FullName, directory.Parent.FullName, newName)))
using elFinder.Net.Drivers.FileSystem.Helpers;
            if (!File.Exists(PathHelper.SafelyCombine(file.DirectoryName, file.DirectoryName, newName)))
            var newPath = PathHelper.GetFullPath(PathHelper.SafelyCombine(Parent.FullName, Parent.FullName, newName));
        private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars();
            if (!IsObjectNameValid(cmd.Name))
                throw new InvalidDirNameException();

                var newDir = new FileSystemDirectory(PathHelper.SafelyCombine(targetPath.Directory.FullName,
                    targetPath.Directory.FullName, cmd.Name), volume);
                var newDir = new FileSystemDirectory(PathHelper.SafelyCombine(targetPath.Directory.FullName,
                    targetPath.Directory.FullName, dirName), volume);
            if (!IsObjectNameValid(cmd.Name))
                throw new InvalidFileNameException();

            var newFile = new FileSystemFile(PathHelper.SafelyCombine(targetPath.Directory.FullName,
                targetPath.Directory.FullName, cmd.Name), volume);
            if (!IsObjectNameValid(cmd.Name))
                if (targetPath.IsDirectory)
                    throw new InvalidDirNameException();
                else throw new InvalidFileNameException();

            if (cmd.Name.Any(name => !IsObjectNameValid(name)))
                throw new InvalidFileNameException();

            if (cmd.Renames.Any(name => !IsObjectNameValid(name)))
                throw new InvalidFileNameException();

            if (!IsObjectNameValid(cmd.Suffix))
                throw new InvalidFileNameException();

                    string uploadFullName = PathHelper.SafelyCombine(dest.FullName, dest.FullName, Path.GetFileName(formFile.FileName));
                            var fullBakName = PathHelper.SafelyCombine(uploadFileInfo.Parent.FullName, uploadFileInfo.Parent.FullName, backupName);
                            uploadFullName = PathHelper.SafelyCombine(uploadFileInfo.DirectoryName, uploadFileInfo.DirectoryName, newName);
            if (cmd.Renames.Any(name => !IsObjectNameValid(name)))
                throw new InvalidFileNameException();

            if (!IsObjectNameValid(cmd.Suffix))
                throw new InvalidFileNameException();

                    var newDest = PathHelper.SafelyCombine(dstPath.Directory.FullName, dstPath.Directory.FullName, src.Directory.Name);
                        var fullBakName = PathHelper.SafelyCombine(newDestDir.Parent.FullName, newDestDir.Parent.FullName, backupName);
                    var newDest = PathHelper.SafelyCombine(dstPath.Directory.FullName, dstPath.Directory.FullName, file.Name);
                        var fullBakName = PathHelper.SafelyCombine(newDestFile.Parent.FullName, newDestFile.Parent.FullName, backupName);
                    var newDest = PathHelper.SafelyCombine(src.Directory.Parent.FullName, src.Directory.Parent.FullName, newName);
            if (!IsObjectNameValid(cmd.Name))
                throw new InvalidFileNameException();

            var directory = targetPath.Directory;
            var archivePath = PathHelper.SafelyCombine(directory.FullName, directory.FullName, filename);
                fromPath = PathHelper.SafelyCombine(fromPath, fromPath, Path.GetFileNameWithoutExtension(targetPath.File.Name));
                    string fullName = PathHelper.GetFullPathNormalized(
                        PathHelper.SafelyCombine(fromPath, fromPath, entry.FullName));
            else if (cmd.Encoding == "hash")
            {
                Func<Task<Stream>> openStreamFunc = async () => await cmd.ContentPath.File.OpenReadAsync(cancellationToken: cancellationToken);

                OnBeforeWriteStream?.Invoke(this, (openStreamFunc, targetFile));
                using (var readStream = await openStreamFunc())
                {
                    await targetFile.OverwriteAsync(readStream, cancellationToken: cancellationToken);
                }
                OnAfterWriteStream?.Invoke(this, (openStreamFunc, targetFile));
            }
            if (!IsObjectNameValid(cmd.DownloadFileName))
                throw new InvalidFileNameException();

            if (!tempFileInfo.Exists) throw new PermissionDeniedException($"Malformed key");

            var memStream = new MemoryStream();
        protected bool IsObjectNameValid(string name)
        {
            return name == null || !name.Any(ch => InvalidFileNameChars.Contains(ch));
        }

            string newPath = PathHelper.SafelyCombine(newDir, newDir, file.Name);
                    newPath = PathHelper.SafelyCombine(newDir, newDir, newName);
            string newPath = PathHelper.SafelyCombine(newDir, newDir, file.Name);
                    newPath = PathHelper.SafelyCombine(newDir, newDir, newName);
                    var newDir = new FileSystemDirectory(PathHelper.SafelyCombine(
                        currentNewDest.FullName, currentNewDest.FullName, dir.Name), currentNewDest.Volume);
                    var newDir = new FileSystemDirectory(PathHelper.SafelyCombine(
                        currentNewDest.FullName, currentNewDest.FullName, dir.Name), currentNewDest.Volume);
            var newPath = PathHelper.GetFullPath(PathHelper.SafelyCombine(Parent.FullName, Parent.FullName, newName));
﻿using elFinder.Net.Core.Exceptions;
using System.IO;
        private static readonly char[] SeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };

        public static string GetFullPath(string path)
            var fullPath = Path.GetFullPath(path);
        public static string GetFullPathNormalized(string path)
            var fullPath = Path.GetFullPath(path).TrimEnd(SeparatorChars);
            return fullPath.TrimEnd(SeparatorChars);
        }

        public static string SafelyCombine(string fromParent, params string[] paths)
        {
            var finalPath = Path.GetFullPath(Path.Combine(paths))
                .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);

            if (!finalPath.StartsWith(fromParent.TrimEnd(SeparatorChars) + Path.DirectorySeparatorChar))
                throw new PermissionDeniedException("Path must be inside parent");

            return finalPath;
﻿using elFinder.Net.Core.Exceptions;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
        private readonly HMAC _hmac = new HMACSHA256();
        private static readonly string Postfix = '_' + nameof(elFinder);

            var bytes = Encoding.UTF8.GetBytes(Path.GetTempFileName() + Guid.NewGuid().ToString());
            var tempFile = Path.Combine(Path.GetTempPath(),
                BitConverter.ToString(_hmac.ComputeHash(bytes)).Replace("-", string.Empty) + Postfix);
            if (Path.IsPathRooted(archiveFileKey)) throw new PermissionDeniedException("Malformed key");

            var fullPath = Path.GetFullPath(Path.Combine(tempDirPath, archiveFileKey));
            if (!fullPath.StartsWith(tempDirPath.EndsWith($"{Path.DirectorySeparatorChar}")
                ? tempDirPath : (tempDirPath + Path.DirectorySeparatorChar)))
                throw new PermissionDeniedException("Malformed key");

            var fileName = Path.GetFileName(fullPath);
            if (!fileName.EndsWith(Postfix))
                throw new PermissionDeniedException("Malformed key");

            return Task.FromResult(fullPath);