How to Securely Verify and Validate Image Uploads in ASP.NET and ASP.NET MVC
One of the more interesting things I had to do as part of building XAPFest was handle bulk image uploads for screenshots for applications and user / app icons. Most of the challenges here are UI-centric ones (which I resolved using jQuery File-Upload) but the one security challenge that remains outstanding is ensuring that the content uploaded to your servers is safe for your users to consume.
Fortunately this problem isn't too hard to solve and doesn't require much code in C#.
Flawed Approaches to Verifying Image Uploads
Here's what I usually see when developers try to allow only web-friendly image uploads:
- File extension validation (i.e. only allow images with .png, .jp[e]g, and .gif to be uploaded) and
- MIME type validation.
So what's wrong with these techniques? The issue is that both the file extension and MIME type can be spoofed, so there's no guarantee that a determined hacker might not take a js. file, slap an extra .png extension somewhere in the mix and spoof the MIME type.
Stronger Approach to Verifying Image Uploads: GDI+ Format Checking
Every file format has to follow a particular codec / byte order convention in order to be read and executed by software. This is as true for proprietary formats like .pptx as it is for .png and .gif.
You can use these codecs to your advantage and quickly tell if a file is really what it says it is - you quickly check the contents of the file against the supported formats' codecs to see if the content fits into any of those specifications.
Luckily GDI+ (System.Drawing.Imaging), the graphics engine which powers Windows, has some super-simple functions we can use to perform this validation. Here's a bit of source you can use to validate a file against PNG, JPEG, and GIF formats:
using System.Drawing.Imaging; using System.IO; using System.Drawing; namespace XAPFest.Providers.Security { /// /// Utility class used to validate the contents of uploaded files /// public static class FileUploadValidator { public static bool FileIsWebFriendlyImage(Stream stream) { try { //Read an image from the stream... var i = Image.FromStream(stream); //Move the pointer back to the beginning of the stream stream.Seek(0, SeekOrigin.Begin); if (ImageFormat.Jpeg.Equals(i.RawFormat)) return true; return ImageFormat.Png.Equals(i.RawFormat) || ImageFormat.Gif.Equals(i.RawFormat); } catch { return false; } } } }
All this code does is read the Stream object returned for each posted file into an Image object, and then verifies that the Image supports one of three supported codecs[footnote:The other important catch to note here is that I move the Stream's pointer back to the front of the stream, so it can be read again by the caller which passed the reference to this function.].
This source code has not been tested by security experts, so use it at your own risk.
If you have any questions about how this code works or want to learn more, please drop me a line in the comments below or on Twitter.
Bonus: How Do I Make Sure Files Are below [X] Filesize?
Since I had this source code lying around anyway, I thought I would share it:
public static bool FileIsWebFriendlyImage(Stream stream, long size) { return stream.Length <= size && FileIsWebFriendlyImage(stream); } }
Super-simple, like I said, but it gets the job done. Express the maximum allowable size as a long and compare it against the length of the stream