reactredux/webapi/Services/FileSecurityService/FileSecurityService.cs

181 lines
5.2 KiB
C#

using DomainResults.Common;
using ExtensionMethods;
namespace FileSecurityService {
/// <summary>
///
/// </summary>
public interface IFileSecurityService {
/// <summary>
///
/// </summary>
/// <param name="fileName"></param>
/// <param name="bytes"></param>
/// <param name="contentType"></param>
/// <returns></returns>
(FileCategory?, IDomainResult) CheckFileSignature(string fileName, byte[] bytes, string contentType);
}
public class FileSecurityService : IFileSecurityService {
/// <summary>
/// https://gist.github.com/qti3e/6341245314bf3513abb080677cd1c93b
/// </summary>
private readonly Dictionary<string, FileSignature> _data;
/// <summary>
///
/// </summary>
public FileSecurityService() {
_data = new Dictionary<string, FileSignature>() {
#region Documents
{
".pdf",
new FileSignature(new List<string> {
"0,25504446"
}, "application/pdf", FileCategory.Document)
},
{
".rtf",
new FileSignature(new List<string> {
"0,7B5C72746631"
}, "application/rtf", FileCategory.Document)
},
{
".doc",
new FileSignature(new List<string> {
"0,0D444F43",
"0,CF11E0A1B11AE100",
"0,D0CF11E0A1B11AE1",
"0,DBA52D00",
"512,ECA5C100"
}, "application/msword", FileCategory.Document)
},
{
".xls",
new FileSignature(new List<string> {
"512,0908100000060500",
"0,D0CF11E0A1B11AE1",
"512,FDFFFFFF04",
"512,FDFFFFFF20000000"
}, "application/vnd.ms-excel", FileCategory.Document)
},
{
".ppt",
new FileSignature(new List<string> {
"512,006E1EF0",
"512,0F00E803",
"512,A0461DF0",
"0,D0CF11E0A1B11AE1",
"512,FDFFFFFF04"
}, "application/vnd.ms-powerpoint", FileCategory.Document)
},
{
".docx",
new FileSignature(new List<string> {
"0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", FileCategory.Document)
},
{
".xlsx",
new FileSignature(new List<string> {
"0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", FileCategory.Document)
},
{
".pptx",
new FileSignature(new List<string> {
"0,504B030414000600"
}, "application/vnd.openxmlformats-officedocument.presentationml.presentation", FileCategory.Document)
},
#endregion
#region Images
{
".jpg",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".jpeg",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".jpe",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".jfif",
new FileSignature(new List<string> {
"0,FFD8"
}, "image/jpeg", FileCategory.Image)
},
{
".png",
new FileSignature(new List<string> {
"0,89504E470D0A1A0A"
}, "image/png", FileCategory.Image)
},
{
".webp",
new FileSignature(new List<string> {
"0,52494646"
}, "image/webp", FileCategory.Image)
}
#endregion
};
}
/// <summary>
/// Don't rely on or trust the FileName property without validation.
/// </summary>
/// <param name="fileName"></param>
/// <param name="bytes"></param>
/// <param name="contentType"></param>
/// <returns></returns>
public (FileCategory?, 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<FileCategory?>();
// check content type
if (contentType != data.ContentType)
return IDomainResult.Failed<FileCategory?>();
// 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.FileCategory);
}
return IDomainResult.Failed<FileCategory?>();
}
}
}