بیشک اگر در سایت خود بخشی را برای دریافت فایلهای کاربر قرار داده باشید یکی از دغدغههای شما اعمال فیلتر و محدودیت روی نوع فایلهای آپلود شده توسط کاربران خواهد بود. ممکن است سیاست شما پذیرای فایل هایی با پسوند خاص (برای مثال فقط عکس) باشد ولی هیچ تضمینی وجود ندارد که فایلی با پسوند مورد نظر شما محتوایی مشابه با پسوند خود داشته باشد.
اگر بخواهیم دقیقتر به این موضوع نگاه کنیم فرض میکنیم شما در وب سایت خود قسمتی برای آپلود عکسهای کاربر قرار داده باشید و با مکانیزمی صحت نوع فایلهای آپلود شده توسط کاربر را بررسی کنید.
اگر شخصی به قصد تخریب و هدر دادن فضای ذخیره سازی شما فایلی با محتوایی غیر از عکس (برای مثال یک فایل اجرایی) را تغییر پسوند داده و به جای عکس آپلود کند چه اتفاقی میافتد؟!
دو دلیل مهم برای چک کردن محتوای فایل خواهیم داشت:
- جلوگیری از اتلاف حافظه ذخیره سازی (ممکن است شخص مهاجم هزاران فایل چند مگابایتی با محتوایی غیر از پسوند فایل بر روی سرور قرار دهد)
- جلوگیری از اختلال در نمایش فایلهای آپلود شده (بی شک عدم نمایش عکس در سایت چهره خوبی نخواهد داشت و ممکن است اعتبار سایت را زیر سوال ببرد)
همیشه برای چک کردن نوع فایل باید به دونکته توجه داشت: یکی آنکه پسوند فایل را حتما چک کنیم تا مطابق فیلتر و سیاست مورد انتظار ما باشد ، دوم اینکه به روشی که در ادامه توضیح خواهیم داد، با استفاده از محتوای باینری فایل، MimeType آنرا تشخیص دهیم.
برای اینکار ما از یک تابع API استفاده میکنیم که با استفاده از ۲۵۵ بایت ابتدایی محتوای فایل، نوع فایل را مشخص میکند. این تابع API که FindMimeFromData نام دارد، در فایل urlmon.dll قرار دارد و گویا توسط مرورگر IE برای چک کردن نوع فایل، بر اساس محتوای آن مورد استفاده قرار میگیرد. ما نیز از این تابع در دات نت بهره گرفته و با پاس دادن آرایهای از بایتها به آن نوع فایل خود را مشخص میکنیم.
کلاسی برای اینکار تهیه شده که در زیر مشاهده می کنید:
using System; using System.Runtime.InteropServices; using System.Reflection; namespace Parsnet.Core { public class MimeTypeDetector { [DllImport(@"urlmon.dll", CharSet = CharSet.Auto)] private extern static System.UInt32 FindMimeFromData( System.UInt32 pBC, [MarshalAs(UnmanagedType.LPStr)] System.String pwzUrl, [MarshalAs(UnmanagedType.LPArray)] byte[] pBuffer, System.UInt32 cbSize, [MarshalAs(UnmanagedType.LPStr)] System.String pwzMimeProposed, System.UInt32 dwMimeFlags, out System.UInt32 ppwzMimeOut, System.UInt32 dwReserverd ); public string GetMimeType(byte[] content) { var result = "unknown/unknown"; try { byte[] buffer = new byte[256]; var length = (content.Length > 256) ? 256 : content.Length; Array.Copy(content, buffer, length); System.UInt32 mimetype; FindMimeFromData(0, null, buffer, 256, null, 0, out mimetype, 0); System.IntPtr mimeTypePtr = new IntPtr(mimetype); result = Marshal.PtrToStringUni(mimeTypePtr); Marshal.FreeCoTaskMem(mimeTypePtr); } catch (Exception ex) { //Log.WriteError(MethodInfo.GetCurrentMethod(), ex); } return result; } } }
تنها نکته قابل توجه در این کد ایناست که در بدنهی متد GetMimeType اگر طول آرایه از ۲۵۶ بایت بیشتر شود، فقط از ۲۵۶ بایت ابتدایی آن استفاده میکنیم. در غیر اینصورت کل بایتهای آرایه، برای چک کردن محتوای فایل به کار گرفته میشوند. برای افزایش کارآیی، بهتر است هنگام فراخوانی این تابع، فقط ۲۵۶ بایت را به آن ارسال کنید. بخصوص اگر حجم فایلی که باید چک شود زیاد باشد.
نحوهی استفاده از کلاس فوق هم بسیار ساده است. برای مثال هنگام آپلود فایل در MVC میتوانیم از آن استفاده کنیم:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Upload(HttpPostedFileBase file) { //ابتدا با مکانیزم مورد نظر خود پسوند فایل را چک میکنیم اگر پسوند معتبری بود بعد محتوای فایل را چک میکنیم var data = new byte[256]; file.InputStream.Read(data, 0, 256); var detector = new Parsnet.Core.MimeTypeDetector(); var mimeType = detector.GetMimeType(data); if (CheckWhiteList(mimeType) == true) { file.SaveAs("yourpath"); } else { ModelState.AddModelError("InvalidFileContent", "فایل بارگزاری شده مورد پذیرش نیست."); } return View(); }
همیشه از یک مکانیزم دیگر برای اعتبارسنجی پسوند فایل استفاده کنید. برای اینکار میتوان با تلفیق روش فوق و یک روش دیگر کنترل اعتبارسنجی خوب ایجاد کرد.
منبع : .NET Tips