From 6f3fe8d933cf4d6a21c7100a6011bd2d48f0c62f Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 12 May 2020 13:24:51 -0400 Subject: [PATCH] validate folder names, handle missing files more gracefully --- Oqtane.Client/Modules/Admin/Files/Edit.razor | 14 ++++-- Oqtane.Server/Controllers/FileController.cs | 13 +++-- Oqtane.Server/Controllers/FolderController.cs | 47 ++++++++++++++---- .../Oqtane.Themes.BlazorTheme/Theme.css | 14 +++++- Oqtane.Server/wwwroot/images/error.png | Bin 0 -> 1623 bytes Oqtane.Server/wwwroot/js/interop.js | 40 +++++++++++---- Oqtane.Shared/Shared/Constants.cs | 1 + 7 files changed, 99 insertions(+), 30 deletions(-) create mode 100644 Oqtane.Server/wwwroot/images/error.png diff --git a/Oqtane.Client/Modules/Admin/Files/Edit.razor b/Oqtane.Client/Modules/Admin/Files/Edit.razor index 0083cce0..1d7ddc81 100644 --- a/Oqtane.Client/Modules/Admin/Files/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Files/Edit.razor @@ -149,10 +149,16 @@ { folder = await FolderService.AddFolderAsync(folder); } - - await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); - await logger.LogInformation("Folder Saved {Folder}", folder); - NavigationManager.NavigateTo(NavigateUrl()); + if (folder != null) + { + await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); + await logger.LogInformation("Folder Saved {Folder}", folder); + NavigationManager.NavigateTo(NavigateUrl()); + } + else + { + AddModuleMessage("An Error Was Encountered Saving The Folder", MessageType.Error); + } } else { diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 5a09d5a5..0c30bbdf 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -16,6 +16,7 @@ using System.Net; using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Repository; +using Microsoft.AspNetCore.Routing.Constraints; // ReSharper disable StringIndexOfIsCultureSpecific.1 @@ -396,12 +397,13 @@ namespace Oqtane.Controllers [HttpGet("download/{id}")] public IActionResult Download(int id) { + string errorpath = Path.Combine(GetFolderPath("images"), "error.png"); Models.File file = _files.GetFile(id); if (file != null) { if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.Permissions)) { - string filepath = Path.Combine(GetFolderPath(file.Folder) , file.Name); + string filepath = Path.Combine(GetFolderPath(file.Folder), file.Name); if (System.IO.File.Exists(filepath)) { byte[] filebytes = System.IO.File.ReadAllBytes(filepath); @@ -411,21 +413,24 @@ namespace Oqtane.Controllers { _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Does Not Exist {FileId} {FilePath}", id, filepath); HttpContext.Response.StatusCode = 404; - return null; + byte[] filebytes = System.IO.File.ReadAllBytes(errorpath); + return File(filebytes, "application/octet-stream", file.Name); } } else { _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access File {FileId}", id); HttpContext.Response.StatusCode = 401; - return null; + byte[] filebytes = System.IO.File.ReadAllBytes(errorpath); + return File(filebytes, "application/octet-stream", file.Name); } } else { _logger.Log(LogLevel.Error, this, LogFunction.Read, "File Not Found {FileId}", id); HttpContext.Response.StatusCode = 404; - return null; + byte[] filebytes = System.IO.File.ReadAllBytes(errorpath); + return File(filebytes, "application/octet-stream", "error.png"); } } diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index 68a01e23..34c13a1b 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -10,7 +10,6 @@ using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; -using System.IO; namespace Oqtane.Controllers { @@ -106,13 +105,23 @@ namespace Oqtane.Controllers } if (_userPermissions.IsAuthorized(User,PermissionNames.Edit, permissions)) { - if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null) + if (FolderPathValid(folder)) { - Folder parent = _folders.GetFolder(folder.ParentId.Value); - folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\"); + if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null) + { + Folder parent = _folders.GetFolder(folder.ParentId.Value); + folder.Path = Utilities.PathCombine(parent.Path, folder.Name); + } + folder.Path = Utilities.PathCombine(folder.Path, "\\"); + folder = _folders.AddFolder(folder); + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder); + } + else + { + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder); + HttpContext.Response.StatusCode = 401; + folder = null; } - folder = _folders.AddFolder(folder); - _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", folder); } else { @@ -131,13 +140,23 @@ namespace Oqtane.Controllers { if (ModelState.IsValid && _userPermissions.IsAuthorized(User, EntityNames.Folder, folder.FolderId, PermissionNames.Edit)) { - if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null) + if (FolderPathValid(folder)) { - Folder parent = _folders.GetFolder(folder.ParentId.Value); - folder.Path = Utilities.PathCombine(parent.Path, folder.Name,"\\"); + if (string.IsNullOrEmpty(folder.Path) && folder.ParentId != null) + { + Folder parent = _folders.GetFolder(folder.ParentId.Value); + folder.Path = Utilities.PathCombine(parent.Path, folder.Name); + } + folder.Path = Utilities.PathCombine(folder.Path, "\\"); + folder = _folders.UpdateFolder(folder); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder); + } + else + { + _logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Name Not Valid {Folder}", folder); + HttpContext.Response.StatusCode = 401; + folder = null; } - folder = _folders.UpdateFolder(folder); - _logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", folder); } else { @@ -191,5 +210,11 @@ namespace Oqtane.Controllers HttpContext.Response.StatusCode = 401; } } + + private bool FolderPathValid(Folder folder) + { + // prevent folder path traversal and reserved devices + return (!folder.Name.Contains("\\") && !folder.Name.Contains("/") && !Constants.ReservedDevices.Split(',').Contains(folder.Name.ToUpper())); + } } } diff --git a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css index a9e5c15e..fba5b909 100644 --- a/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css @@ -101,6 +101,12 @@ flex-direction: row; } + .app-logo { + display: block; + margin-left: auto; + margin-right: auto; + } + .breadcrumbs { position: fixed; left: 275px; @@ -163,7 +169,13 @@ } } -@media (max-width: 767px) { +@media (max-width: 767px) { + .app-logo { + height: 80px; + display: flex; + align-items: center; + } + .breadcrumbs { position: fixed; top: 150px; diff --git a/Oqtane.Server/wwwroot/images/error.png b/Oqtane.Server/wwwroot/images/error.png new file mode 100644 index 0000000000000000000000000000000000000000..0095d2f13c1c5683e5d8dd61c835fc4e0c40268d GIT binary patch literal 1623 zcmV-d2B`UoP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1?)*gK~#8N?V8Jv z6jc<)t9u$|p(KhTN-#*GVgw%;jV{zEj698xkPsKfjd5Yz_!qcx>(;~=H5lWQ>$Jox1l-*PW_GU}WT$ zAJI00h|xMMvVldJB-3c+=`+J_%Gl1c=J-sM%aX2XY%al zdF@<%Cl@TJJ&he^ti>Gv$g%KzZhW9)Ur0diMFey!<`rlL%fdlKuf>is-s9M}Xg`PT zNE9sUi`RrKa?!)EOz{C>+=cKN5^-P3C%O^Z&Q}jbp~yvx;#RqEX@zk^v3iOm+#7qk z7b;r$CQX2YIB?!Y_9+|2BOKSbgw~m`9owzYcD{xKS{coD-bEpxGGQ!XTw@Aw>>VUw z8@3yv?R@bJ5JfIZ2UHf)1$j0?67IqFLVP@i36MoD3dt%PVcb?TE-Z&$6~>N=;D1pA zW*5O$FbTaXi~~^wTOrJZxfaHODT1wFJaa9K162fDAta&O!Z>h6uoc2`=%z3ZWD#ry zlh93J?CT=va_9tMCUjC5`??4|55I)RJKzcU7)e-<%@eP0vXj^0y_lOkE)j=#{k;mC z84Ku@ukVWBN)o*sI~P%%`0CC8?96+9+lvTn5BA2`Q!c)TFTh`+F@$315;T?~z9Y<} zXR&z+88-o5;4;Xp10-NSw(KR)D;yvJH)FHJ>l^B~BG@aW|D3iHudnZZ3HAy>SC)8v zeOCl~h4i1(ENy-FOR$&76EFJimte18QO^^vukU^d_6k)IOl+2TeN_=uR8<6FRRm#P z#;PKi*nr>R{qWnkm5o2)gV6W;Jp2V8fS<*!Y>dH&;HPox9|T=l;`Mc11btK6!V8C+ z7VF@5I@`cRZKLlKSidd!MrRwCs5|Mq18d)`gqf)A^nDU*3`cb)D76Tlgqs-nUF;~< zferQtI2*sgyXo^9wt+o@9f7{`&W4HFN}qi;u!n24Xfubc67~l)sXMU~*yGJ+{QW{Y z33=j0$3@UBrwt7Fe&UP!(f$at75-#9fr+|kr|K+RV8F)6MB| z8;#n>Sd*KcVJ7u1`no?c6W_vx^K`^)Ddz0e^{Vs$%CnlN~({|Pz!ft{l>}P0F-^RN2wgm2h%V;`l?CY#?&+iv# zQoZj4_gg`~m*Hi!BNBF$alNle$P=%xqdmbh8Q3+d_{*_AfeWC6t>augRXMkoAnFA3 z4o$>QAtZiUmq4HXM*4cq1bwg9(*6MJwTm%|Q_xq?z6Y`HNUg#yqV0VZzr5LuTw2Cr zAs$KYJIc95jBl)2;`K#gN61-FBx8!d0;{0&V89p9r)V{vxt6}p4*SMWD8bjznPh=W zC-^Lk>wmTF1PXI$xWr~+LEtjPyZ*17cEzv;dEZZ_o4MlkO?6TP74HX3Z@{#8u>$Vp zyuruU66CqqS7BPbaMN=?eOF@RF*x?~8xxflFI)z@Iqy!fQ2bKFzr=A`yfCJHN#7#k zMe!oYjjzL6(9VIapfH17Ly(J;C$uI$b1Ke<2D_RXat6n{VGubR@hq}0_g%t$U!;9a zeCAY`^j-A50h=dYU-9n*xv5XS$gZ$|;*PU9|8 zi5DhmP!M!wiPu*ZK}A(X5LQJH=4GrZf{7gj1YKF;^;JbsQB@IyRS|@F85^V}P)ZSW zzim+9exk?{udjF!tkt4NvF_0JI}XD~V4iq=#lI7Tqf=AO;mOJ9B93b$WVqRkMzIa7 zQ*QGA_Hi5y4Mj)m^_u@O%M&k(e&dEkqpzX3+0 Vx%U~ByY>J8002ovPDHLkV1jsB6LSCn literal 0 HcmV?d00001 diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index 50c8aa9b..5f41709c 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -81,14 +81,26 @@ window.interop = { if (link.href !== url) { link.setAttribute('href', url); } - if (type !== "" && link.type !== type) { - link.setAttribute('type', type); + if (type !== "") { + if (link.type !== type) { + link.setAttribute('type', type); + } + } else { + link.removeAttribute('type'); } - if (integrity !== "" && link.integrity !== integrity) { - link.setAttribute('integrity', integrity); + if (integrity !== "") { + if (link.integrity !== integrity) { + link.setAttribute('integrity', integrity); + } + } else { + link.removeAttribute('integrity'); } - if (crossorigin !== "" && link.crossOrigin !== crossorigin) { - link.setAttribute('crossorigin', crossorigin); + if (crossorigin !== "") { + if (link.crossOrigin !== crossorigin) { + link.setAttribute('crossorigin', crossorigin); + } + } else { + link.removeAttribute('crossorigin'); } } }, @@ -126,11 +138,19 @@ window.interop = { if (script.src !== src) { script.src = src; } - if (integrity !== "" && script.integrity !== integrity) { - script.setAttribute('integrity', integrity); + if (integrity !== "") { + if (script.integrity !== integrity) { + script.setAttribute('integrity', integrity); + } + } else { + script.removeAttribute('integrity'); } - if (crossorigin !== "" && script.crossorigin !== crossorigin) { - script.setAttribute('crossorigin', crossorigin); + if (crossorigin !== "") { + if (script.crossOrigin !== crossorigin) { + script.setAttribute('crossorigin', crossorigin); + } + } else { + script.removeAttribute('crossorigin'); } } else { diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index c50347e2..30ca2950 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -43,5 +43,6 @@ public const string ImageFiles = "jpg,jpeg,jpe,gif,bmp,png"; public const string UploadableFiles = "jpg,jpeg,jpe,gif,bmp,png,mov,wmv,avi,mp4,mp3,doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,nupkg"; + public const string ReservedDevices = "CON,NUL,PRN,COM1,COM2,COM3,COM4,COM5,COM6,COM7,COM8,COM9,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9"; } }