CVE-2019-12277 (published 2019-05-22T15:29Z)

Blogifier 2.3 before 2019-05-11 does not properly restrict APIs, as demonstrated by missing checks for .. in a pathname.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
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.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/blogifierdotnet/Blogifier
commit_details: repo_url: https://github.com/blogifierdotnet/Blogifier
author: rxtur
author_date: 2019-05-10 23:59:29-05:00
msg: Tightening security in APIs
repository: repo_url: https://github.com/blogifierdotnet/Blogifier
repo_name: blogifierdotnet/Blogifier
description: Blogifier is an open-source publishing platform Written in ASP.NET and Blazor WebAssembly. With Blogifier make a personal blog or a website.
repo_language: C#
owner: blogifierdotnet
file_changes: filename: AssetsController.cs
change_type: ModificationType.MODIFY
diff: @@ -7,6 +7,7 @@ using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
 using System.Linq;
+using Microsoft.AspNetCore.Authorization;
 
 namespace Core.Api
 {
@@ -40,20 +41,20 @@ namespace Core.Api
             {
                 if (filter == "filterImages")
                 {
-                    items = await _store.Find(a => a.AssetType == AssetType.Image, pager);
+                    items = await _store.Find(a => a.AssetType == AssetType.Image, pager, "", !User.Identity.IsAuthenticated);
                 }
                 else if (filter == "filterAttachments")
                 {
-                    items = await _store.Find(a => a.AssetType == AssetType.Attachment, pager);
+                    items = await _store.Find(a => a.AssetType == AssetType.Attachment, pager, "", !User.Identity.IsAuthenticated);
                 }
                 else
                 {
-                    items = await _store.Find(null, pager);
+                    items = await _store.Find(null, pager, "", !User.Identity.IsAuthenticated);
                 }
             }
             else
             {
-                items = await _store.Find(a => a.Title.Contains(search), pager);
+                items = await _store.Find(a => a.Title.Contains(search), pager, "", !User.Identity.IsAuthenticated);
             }
 
             if (page < 1 || page > pager.LastPage)
@@ -110,11 +111,12 @@ namespace Core.Api
         }
 
         /// <summary>
-        /// Upload file(s) to user data store
+        /// Upload file(s) to user data store, authentication required
         /// </summary>
         /// <param name="files">Selected files</param>
         /// <returns>Success or internal error</returns>
         [HttpPost("upload")]
+        [Authorize]
         public async Task<IActionResult> Upload(ICollection<IFormFile> files)
         {
             try
@@ -137,6 +139,7 @@ namespace Core.Api
         /// <param name="url">Relative URL of the file to remove</param>
         /// <returns></returns>
         [HttpDelete("remove")]
+        [Authorize]
         public IActionResult Remove(string url)
         {
             try

programming_language: C#
filename: CoreAPI.xml
change_type: ModificationType.MODIFY
diff: @@ -24,7 +24,7 @@
         </member>
         <member name="M:Core.Api.AssetsController.Upload(System.Collections.Generic.ICollection{Microsoft.AspNetCore.Http.IFormFile})">
             <summary>
-            Upload file(s) to user data store
+            Upload file(s) to user data store, authentication required
             </summary>
             <param name="files">Selected files</param>
             <returns>Success or internal error</returns>

programming_language: C#
filename: StringExtensions.cs
change_type: ModificationType.MODIFY
diff: @@ -212,6 +212,22 @@ namespace Core
             return text;
         }
 
+        public static string SanitizePath(this string str)
+        {
+            if (str.Contains("..") || str.Contains("//"))
+                throw new ApplicationException("Invalid directory path");
+
+            return str;
+        }
+
+        public static string SanitizeFileName(this string str)
+        {
+            if (str.Contains("..") || str.Contains("//") || str.Count(x => x == '.') > 1)
+                throw new ApplicationException("Invalid file name");
+
+            return str;
+        }
+
         #endregion
     }
 }
\ No newline at end of file

programming_language: C#
filename: StorageService.cs
change_type: ModificationType.MODIFY
diff: @@ -32,7 +32,7 @@ namespace Core.Services
 
         string GetHtmlTemplate(string template);
 
-        Task<IEnumerable<AssetItem>> Find(Func<AssetItem, bool> predicate, Pager pager, string path = "");
+        Task<IEnumerable<AssetItem>> Find(Func<AssetItem, bool> predicate, Pager pager, string path = "", bool sanitize = false);
 
         Task Reset();
     }
@@ -254,7 +254,7 @@ namespace Core.Services
             }
         }
 
-        public async Task<IEnumerable<AssetItem>> Find(Func<AssetItem, bool> predicate, Pager pager, string path = "")
+        public async Task<IEnumerable<AssetItem>> Find(Func<AssetItem, bool> predicate, Pager pager, string path = "", bool sanitize = false)
         {
             var skip = pager.CurrentPage * pager.ItemsPerPage - pager.ItemsPerPage;
             var files = GetAssets(path);
@@ -267,6 +267,14 @@ namespace Core.Services
 
             var page = items.Skip(skip).Take(pager.ItemsPerPage).ToList();
 
+            if (sanitize)
+            {
+                foreach (var p in page)
+                {
+                    p.Path = "";
+                }
+            }
+
             return await Task.FromResult(page);
         }
 
@@ -331,6 +339,8 @@ namespace Core.Services
 
         void VerifyPath(string path)
         {
+            path = path.SanitizePath();
+
             if (!string.IsNullOrEmpty(path))
             {
                 var dir = Path.Combine(Location, path);
@@ -373,7 +383,7 @@ namespace Core.Services
                 Random rnd = new Random();
                 fileName = fileName.Replace("mceclip0", rnd.Next(100000, 999999).ToString());
             }
-            return fileName;
+            return fileName.SanitizeFileName();
         }
 
         string GetUrl(string path, string root)
@@ -444,7 +454,7 @@ namespace Core.Services
 
             title = title.Replace(" ", "-");
 
-            return title.Replace("/", "");
+            return title.Replace("/", "").SanitizeFileName();
         }
 
         List<AssetItem> MapFilesToAssets(IList<string> assets)

programming_language: C#