CVE-2023-32322: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Ombi is an open source application which allows users to request specific media from popular self-hosted streaming servers. Versions prior to 4.38.2 contain an arbitrary file read vulnerability where an Ombi administrative user may access files available to the Ombi server process on the host operating system. Ombi administrators may not always be local system administrators and so this may violate the security expectations of the system. The arbitrary file read vulnerability was present in `ReadLogFile` and `Download` endpoints in `SystemControllers.cs` as the parameter `logFileName` is not sanitized before being combined with the `Logs` directory. When using `Path.Combine(arg1, arg2, arg3)`, an attacker may be able to escape to folders/files outside of `Path.Combine(arg1, arg2)` by using ".." in `arg3`. In addition, by specifying an absolute path for `arg3`, `Path.Combine` will completely ignore the first two arguments and just return just `arg3`. This vulnerability can lead to information disclosure. The Ombi `documentation` suggests running Ombi as a Service with Administrator privileges. An attacker targeting such an application may be able to read the files of any Windows user on the host machine and certain system files. This issue has been addressed in commit `b8a8f029` and in release version 4.38.2. Users are advised to upgrade. There are no known workarounds for this vulnerability. This issue is also tracked as GHSL-2023-088.

CWE description: 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.
CVSS v3 base score: 4.9
Published: 2023-05-18T17:15Z
Repository: https://github.com/Ombi-app/Ombi
Language: C#
Files changed: src/Ombi/Controllers/V2/SystemController.cs
Commit date: 2023-05-17 08:46:19 +0100
Commit message: Merge pull request from GHSA-28j3-84m7-gpjp


Diff: diff --git a/src/Ombi/Controllers/V2/SystemController.cs b/src/Ombi/Controllers/V2/SystemController.cs
index 6b76d053d..b2aed3f44 100644
--- a/src/Ombi/Controllers/V2/SystemController.cs
+++ b/src/Ombi/Controllers/V2/SystemController.cs
@@ -42,27 +42,35 @@ namespace Ombi.Controllers.V2
 
             return Ok(files);
         }
 
         [HttpGet("logs/{logFileName}")]
-        public async Task<IActionResult> ReadLogFile(string logFileName, CancellationToken token)
+        public async Task<IActionResult> ReadLogFile(string logFileName)
         {
-            var logFile = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs", logFileName);
-            using (var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
-            using (StreamReader reader = new StreamReader(fs))
+            var logsFolder = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs");
+            var files = Directory.EnumerateFiles(logsFolder);
+            var matchingFile = files.FirstOrDefault(x => Path.GetFileName(x).Equals(logFileName));
+            if (matchingFile != null)
             {
+                using var fs = new FileStream(matchingFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+                using StreamReader reader = new(fs);
                 return Ok(await reader.ReadToEndAsync());
             }
+            return NotFound();
         }
 
         [HttpGet("logs/download/{logFileName}")]
-        public IActionResult Download(string logFileName, CancellationToken token)
+        public IActionResult Download(string logFileName)
         {
-            var logFile = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs", logFileName);
-            using (var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
-            using (StreamReader reader = new StreamReader(fs))
+            var logsFolder = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs");
+            var files = Directory.EnumerateFiles(logsFolder);
+            var matchingFile = files.FirstOrDefault(x => Path.GetFileName(x).Equals(logFileName));
+            if (matchingFile != null)
             {
+                using var fs = new FileStream(matchingFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+                using StreamReader reader = new(fs);
                 return File(reader.BaseStream, "application/octet-stream", logFileName);
             }
+            return NotFound();
         }
     }
 }
\ No newline at end of file

Vulnerable code:         public async Task<IActionResult> ReadLogFile(string logFileName, CancellationToken token)
            var logFile = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs", logFileName);
            using (var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            using (StreamReader reader = new StreamReader(fs))
        public IActionResult Download(string logFileName, CancellationToken token)
            var logFile = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs", logFileName);
            using (var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            using (StreamReader reader = new StreamReader(fs))
Fixed code:         public async Task<IActionResult> ReadLogFile(string logFileName)
            var logsFolder = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs");
            var files = Directory.EnumerateFiles(logsFolder);
            var matchingFile = files.FirstOrDefault(x => Path.GetFileName(x).Equals(logFileName));
            if (matchingFile != null)
                using var fs = new FileStream(matchingFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                using StreamReader reader = new(fs);
            return NotFound();
        public IActionResult Download(string logFileName)
            var logsFolder = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs");
            var files = Directory.EnumerateFiles(logsFolder);
            var matchingFile = files.FirstOrDefault(x => Path.GetFileName(x).Equals(logFileName));
            if (matchingFile != null)
                using var fs = new FileStream(matchingFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                using StreamReader reader = new(fs);
            return NotFound();