181 lines
5.2 KiB
C#
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?>();
|
|
}
|
|
}
|
|
} |