using Core.Enumerations; using DomainResults.Common; using ExtensionMethods; namespace FileSecurityService { /// /// /// public interface IFileSecurityService { /// /// /// /// /// /// /// (MediaTypes?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType); } public class FileSecurityService : IFileSecurityService { /// /// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b /// private readonly Dictionary _data; /// /// /// public FileSecurityService() { _data = new Dictionary() { #region Documents { ".pdf", new FileSignature(new List { "0,25504446" }, "application/pdf", MediaTypes.Document) }, { ".rtf", new FileSignature(new List { "0,7B5C72746631" }, "application/rtf", MediaTypes.Document) }, { ".doc", new FileSignature(new List { "0,0D444F43", "0,CF11E0A1B11AE100", "0,D0CF11E0A1B11AE1", "0,DBA52D00", "512,ECA5C100" }, "application/msword", MediaTypes.Document) }, { ".xls", new FileSignature(new List { "512,0908100000060500", "0,D0CF11E0A1B11AE1", "512,FDFFFFFF04", "512,FDFFFFFF20000000" }, "application/vnd.ms-excel", MediaTypes.Document) }, { ".ppt", new FileSignature(new List { "512,006E1EF0", "512,0F00E803", "512,A0461DF0", "0,D0CF11E0A1B11AE1", "512,FDFFFFFF04" }, "application/vnd.ms-powerpoint", MediaTypes.Document) }, { ".docx", new FileSignature(new List { "0,504B030414000600" }, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", MediaTypes.Document) }, { ".xlsx", new FileSignature(new List { "0,504B030414000600" }, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", MediaTypes.Document) }, { ".pptx", new FileSignature(new List { "0,504B030414000600" }, "application/vnd.openxmlformats-officedocument.presentationml.presentation", MediaTypes.Document) }, #endregion #region Images { ".jpg", new FileSignature(new List { "0,FFD8" }, "image/jpeg", MediaTypes.Image) }, { ".jpeg", new FileSignature(new List { "0,FFD8" }, "image/jpeg", MediaTypes.Image) }, { ".jpe", new FileSignature(new List { "0,FFD8" }, "image/jpeg", MediaTypes.Image) }, { ".jfif", new FileSignature(new List { "0,FFD8" }, "image/jpeg", MediaTypes.Image) }, { ".png", new FileSignature(new List { "0,89504E470D0A1A0A" }, "image/png", MediaTypes.Image) }, { ".webp", new FileSignature(new List { "0,52494646" }, "image/webp", MediaTypes.Image) } #endregion }; } /// /// Don't rely on or trust the FileName property without validation. /// /// /// /// /// public (MediaTypes?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType) { var data = _data.SingleOrDefault(x => x.Key == Path.GetExtension(fileName).ToLower()).Value; if (data == null) return IDomainResult.NotFound(); // check content type if (contentType != data.ContentType) return IDomainResult.Failed(); // check signatures foreach (var signature in data.Signatures) { var splitString = signature.Split(","); var offset = splitString[0].ToInt(); var signBytes = Enumerable.Range(0, splitString[1].Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(splitString[1].Substring(x, 2), 16)) .ToArray(); var sample = new byte[signBytes.Length]; Buffer.BlockCopy(bytes, offset, sample, 0, signBytes.Length); int x = sample.Length ^ signBytes.Length; // https://www.syncfusion.com/succinctly-free-ebooks/application-security-in-net-succinctly/comparing-byte-arrays for (int i = 0; i < sample.Length && i < signBytes.Length; ++i) { x |= sample[i] ^ signBytes[i]; } if (x == 0) return IDomainResult.Success(data.MediaType); } return IDomainResult.Failed(); } } }