Check file format signature with .net core 2.1 api

Viewed 2179

There are plenty of discussions around that question across the Internet. example 1 example 2 but I didn't find any answers address to .net core.

So, does anyone know the proper approach to checked file format signature, in order to prevent a user from uploading a masqueraded file?

2 Answers

Microsoft has an excellent article on file uploads which is a highly recommended read - see: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-5.0

The article covers topics such as checking file signatures, allowed extensions, matching extension with file signatures, renaming files, storing files and more.

In that article, they also explain how to check file signatures, which is very similar to the answer you already provided. However, it goes a bit deeper and some file types can have multiple signatures:

FileHelper.cs (full file here: https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/mvc/models/file-uploads/samples/3.x/SampleApp/Utilities/FileHelpers.cs):

    private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
    {
        { ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
        { ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
        { ".jpeg", new List<byte[]>
            {
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
            }
        },
        { ".jpg", new List<byte[]>
            {
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
            }
        },
        { ".zip", new List<byte[]> 
            {
                new byte[] { 0x50, 0x4B, 0x03, 0x04 }, 
                new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
                new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
                new byte[] { 0x50, 0x4B, 0x05, 0x06 },
                new byte[] { 0x50, 0x4B, 0x07, 0x08 },
                new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
            }
        },
    };

            // File signature check
            // --------------------
            // With the file signatures provided in the _fileSignature
            // dictionary, the following code tests the input content's
            // file signature.
            var signatures = _fileSignature[ext];
            var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

            return signatures.Any(signature => 
                headerBytes.Take(signature.Length).SequenceEqual(signature));

For more signatures - please see https://www.filesignatures.net/ and https://www.garykessler.net/library/file_sigs.html

Also, you can easily check the signature of any file yourself using a hexadecimal file viewer. E.g. in Windows 10, using PowerShell you can simply write the following command in a PowerShell prompt (https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/format-hex?view=powershell-7.1):

PS> format-hex c:\myfile.gif

format-hex output for gif example file

Translated to C# that gives:

new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 } // 'GIF89a'
new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 } // 'GIF87a'
new byte[] { 0x47, 0x49, 0x46, 0x38 } // 'GIF8' (detect above two signatures by only looking at the first four bytes)

I didn't find any 'lib/nuget/class' specific for .net core that might make our life easier. So, I returned to a common approach which is comparing byte file header with examples.

private readonly Dictionary<string, byte[]> _mimeTypes = new Dictionary<string, byte[]>
    {
        {"image/jpeg", new byte[] {255, 216, 255}},
        {"image/jpg", new byte[] {255, 216, 255}},
        {"image/pjpeg", new byte[] {255, 216, 255}},
        {"image/apng", new byte[] {137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82}},
        {"image/png", new byte[] {137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82}},
        {"image/bmp", new byte[] {66, 77}},
        {"image/gif", new byte[] {71, 73, 70, 56}},
    };

private bool ValidateMimeType(byte[] file, string contentType)
    {
        var imageType = _mimeTypes.SingleOrDefault(x => x.Key.Equals(contentType));

        return file.Take(imageType.Value.Length).SequenceEqual(imageType.Value);
    }
Related