Series bảo mật trong ASP.NET MVC - 4: Upload các tệp tin có hại Bảo mật


Qua 3 bài trước chúng ta đã từng tìm hiểu về cách bảo vệ tất cả các trường nhập liệu. Nhưng chúng ta đã bỏ qua một trường chính là trường File Upload mà chúng ta cần bảo vệ và kiểm tra kỹ càng, control này bị hầu hết các hacker khai thác để upload các tệp tin có hại. Các hacker có thể thay đổi đuôi của tệp tin (ví dụ tuto.exe sang tuto.jpeg] sau đó các đoạn mã có hạo có thể được upload như một file ảnh. Hầu hết các developer chỉ nhìn vào đuôi file và lưu chúng vào thư mục hoặc database nhưng thực tế có thể là loại file khác.

https://www.codeproject.com/KB/aspnet/1116318/Fig29.jpg

Giải pháp:-

  1. Đầu tiên chúng ta cần validate file tải lên.
  2. Thứ 2 là chỉ cho phép truy cập các file extension cụ thể
  3. Kiểm tra file header.

Đầu tiên thêm mới các file upload vào view.

Thêm upload control

https://www.codeproject.com/KB/aspnet/1116318/Fig30.jpg

Chúng ta đã thêm file upload control vào View sau đó validate nó ở nút submit.

Validate file trong action method

Đầu tiên chúng ta kiểm tra thuộc tính Content-Length nếu là 0 sau đó chúng ta không upload file  lên.

Nếu Content-Length lớn hơn 0 thì chúng ta sẽ đọc tên file, loại file và kích thước file.

 [HttpPost]
        [ValidateAntiForgeryToken]
        publicActionResult Index(EmployeeDetailEmployeeDetail)
        {
            if (ModelState.IsValid)
            {
                HttpPostedFileBase upload = Request.Files["upload"];
                if (upload.ContentLength == 0)
                {
                    ModelState.AddModelError("File", "Please Upload your file");
                }
                elseif(upload.ContentLength > 0)
                       {
                    stringfileName = upload.FileName; // getting File Name

                    stringfileContentType = upload.ContentType; // getting ContentType

                    byte[] tempFileBytes = newbyte[upload.ContentLength]; // getting filebytes

                    var data = upload.InputStream.Read(tempFileBytes, 0, Convert.ToInt32(upload.ContentLength));

                    var types = MvcSecurity.Filters.FileUploadCheck.FileType.Image;  // Setting Image type

                    var result = FileUploadCheck.isValidFile(tempFileBytes, types, fileContentType); // Validate Header

                    if (result == true)
                    {
                        intFileLength = 1024 * 1024 * 2; //FileLength 2 MB
                        if (upload.ContentLength > FileLength)
                        {
                            ModelState.AddModelError("File", "Maximum allowed size is: " + FileLength + " MB");
                        }
                        else
                        {
                            stringdemoAddress = Sanitizer.GetSafeHtmlFragment(EmployeeDetail.Address);
                            dbcon.EmployeeDetails.Add(EmployeeDetail);
                            dbcon.SaveChanges();
                            return View();
                        }
                    }
                }
            }
            return View(EmployeeDetail);
        }

Các validate cơ bản đã xong, hãy validate file upload được viết trong một class static với tên là FileUploadCheckin, class này có cá phương thức để kiểm tra các loại file khác nhau. Tôi sẽ chỉ cho bạn làm sao để validate các file image và chỉ cho phép file image mà thôi.

FileUploadCheck Class

https://www.codeproject.com/KB/aspnet/1116318/Fig31.jpg

Ảnh chụp trên có một enum là ImageFileExtension chứa danh sách các đuôi cho phép định dạng ảnh và các file type cho phép.

private enum ImageFileExtension
        {
            none = 0,
            jpg = 1,
            jpeg = 2,
            bmp = 3,
            gif = 4,
            png = 5
        }
        public enum FileType
        {
            Image = 1,
            Video = 2,
            PDF = 3,
            Text = 4,
            DOC = 5,
            DOCX = 6,
            PPT = 7,
        }

 

Nếu đã qua được các validate căn bản chúng ta sẽ gọi phương thức ValidFileMethod theo byte, loại file, FileContentType

  public static bool isValidFile(byte[] bytFile, FileType flType, String FileContentType)
        {
            bool isvalid = false;
            if (flType == FileType.Image)
            {
                isvalid = isValidImageFile(bytFile, FileContentType);//we are going call this method
            }
            else if (flType == FileType.Video)
            {
                isvalid = isValidVideoFile(bytFile, FileContentType);
            }
            else if (flType == FileType.PDF)
            {
                isvalid = isValidPDFFile(bytFile, FileContentType);
            }
            return isvalid;
        }

Sau khi gọi phương thức isValidFile sẽ gọi dựa trên loại file.

Nếu loại file là image thì sẽ gọi đến phương thức isValidImageFile, nếu là video thì sẽ gọi phương thức isValidVideoFile, và tương tự như thế với PDF.

Dưới đây là đoạn code kiểm tra của phương thức isValidImageFile

Trong phương thức này chúng ta sẽ giới hạn các đuôi cho phép upload hình ảnh như [jpg, jpeg, png, bmp, gif]

Làm việc với phương thức isValidImageFile

Khi chúng ta pass qua bytes và FileContentType phương thức này sẽ kiểm tra FileContentType

Sau đó nó sẽ kiểm tra các bytes quy định header của file được tải lên đúng với loại file.

 public static bool isValidImageFile(byte[] bytFile, String FileContentType)
        {
            bool isvalid = false;

            byte[] chkBytejpg = { 255, 216, 255, 224 };
            byte[] chkBytebmp = { 66, 77 };
            byte[] chkBytegif = { 71, 73, 70, 56 };
            byte[] chkBytepng = { 137, 80, 78, 71 };


            ImageFileExtensionimgfileExtn = ImageFileExtension.none;

            if (FileContentType.Contains("jpg") | FileContentType.Contains("jpeg"))
            {
                imgfileExtn = ImageFileExtension.jpg;
            }
            else if (FileContentType.Contains("png"))
            {
                imgfileExtn = ImageFileExtension.png;
            }
            else if (FileContentType.Contains("bmp"))
            {
                imgfileExtn = ImageFileExtension.bmp;
            }
            else if (FileContentType.Contains("gif"))
            {
                imgfileExtn = ImageFileExtension.gif;
            }

            if (imgfileExtn == ImageFileExtension.jpg || imgfileExtn == ImageFileExtension.jpeg)
            {
                if (bytFile.Length >= 4)
                {
                    int j = 0;
                    for (Int32 i = 0; i <= 3; i++)
                    {
                        if (bytFile[i] == chkBytejpg[i])
                        {
                            j = j + 1;
                            if (j == 3)
                            {
                                isvalid = true;
                            }
                        }
                    }
                }
            }


            if (imgfileExtn == ImageFileExtension.png)
            {
                if (bytFile.Length >= 4)
                {
                    int j = 0;
                    for (Int32 i = 0; i <= 3; i++)
                    {
                        if (bytFile[i] == chkBytepng[i])
                        {
                            j = j + 1;
                            if (j == 3)
                            {
                                isvalid = true;
                            }
                        }
                    }
                }
            }


            if (imgfileExtn == ImageFileExtension.bmp)
            {
                if (bytFile.Length >= 4)
                {
                    int j = 0;
                    for (Int32 i = 0; i <= 1; i++)
                    {
                        if (bytFile[i] == chkBytebmp[i])
                        {
                            j = j + 1;
                            if (j == 2)
                            {
                                isvalid = true;
                            }
                        }
                    }
                }
            }

            if (imgfileExtn == ImageFileExtension.gif)
            {
                if (bytFile.Length >= 4)
                {
                    int j = 0;
                    for (Int32 i = 0; i <= 1; i++)
                    {
                        if (bytFile[i] == chkBytegif[i])
                        {
                            j = j + 1;
                            if (j == 3)
                            {
                                isvalid = true;
                            }
                        }
                    }
                }
            }

            return isvalid;
        }

Gọi phương thức isValid file

Chúng ta sẽ gọi FileUploadCheck.isValidFile và đưa tham số File Bytes, Types, FileContentType.

Phương thức này sẽ trả về giá trị Boolean nếu file validate true và ngược lại là false.

 string fileName = upload.FileName; // getting File Name

        string fileContentType = upload.ContentType; // getting ContentType

        byte[] tempFileBytes = new byte[upload.ContentLength]; // getting filebytes

        var data = upload.InputStream.Read(tempFileBytes, 0, Convert.ToInt32(upload.ContentLength));

        var types = MvcSecurity.Filters.FileUploadCheck.FileType.Image; // Setting Image type

        var result = FileUploadCheck.isValidFile(tempFileBytes, types, fileContentType); //Calling isValidFile method

 

 Sau khi hiểu đoạn code bạn sẽ nhìn một demo sau

Form dưới đây hiển thị nhập Employee với upload control

Chúng ta sẽ nhập các form dưới đây và chọn 1 file valid

https://www.codeproject.com/KB/aspnet/1116318/Fig32.jpg

Chọn một file .jpg và kiểm tra

Chọn 1 ảnh .jpg từ ổ đĩa

https://www.codeproject.com/KB/aspnet/1116318/Fig33.jpg

https://www.codeproject.com/KB/aspnet/1116318/Fig34.jpg

Debug phương thức

Trong phần này chúng ta đã đăng Employee với 1 file chúng ta có thể thấy các validate cơ bản.

https://www.codeproject.com/KB/aspnet/1116318/Fig35.jpg

Sau khi submit form để lưu dữ liệu, hiển thị giá trị thực của file đã upload.

https://www.codeproject.com/KB/aspnet/1116318/Fig36.jpg

Hình chụp của class FileUploadCheck trong khi phương thức isValidFile được gọi.

Trong phần này gọi phương thức isValidfile nó sẽ goi phương thức khác theo FileContentType.

https://www.codeproject.com/KB/aspnet/1116318/Fig37.jpg

Phương thức isValidImageFile trong khi kiểm tra các byte header.

Trong phương thức này nó sẽ kiểm tra các byte đầu tiên quy định kiểu file upload là image.

https://www.codeproject.com/KB/aspnet/1116318/Fig38.jpg



Trích nguồn từ: (codeproject.com)