ASP.NET MVC 5 實作更安全的檔案上傳功能 (ASP.NET MVC Safer File Upload Implements)

2021-02-26

不當的檔案上傳功能,可能導致被植入後門程式(webshell)或者惡意的下載檔擴散影響至網站使用者,筆記 ASP.NET MVC 如何實作更安全的檔案上傳機制,同時也探討網頁伺服器可以在那些層面協助,讓網站應用程式更為安全 🐱‍💻

logo

說明

常見的資安弱點

  • 大量的檔案癱瘓應用程式伺服器儲存空間
  • 惡意使用者上傳後門 webshell (asp, php, etc)

網頁伺服器的聯合防禦

  • 確認 UploadFiles 資料夾 Handler 沒有提供 執行 權限
  • 要求篩選(Request Filtering) 設定副檔名白名單
  • 限定允許的 MIME(ContentType) 類型

上傳程式碼

Controller

[HttpPost]
public ActionResult Upload(HttpPostedFileBase file)
{

    var fileValid = true;
    // Limit File Szie Below : 5MB
    if (file.ContentLength <= 0 || file.ContentLength > 5242880) 
    {
        fileValid = false;
    }

    if (file.ContentType != "image/png" || file.ContentType != "image/jpeg")
    {
      fileValid = false;
    }

    if (fileValid == true)
    {
        string extension = Path.GetExtension(file.FileName);
        string fileName = $"{Guid.NewGuid()}{extension}";
        string savePath = Path.Combine(Server.MapPath("~/UploadFile"), fileName);
        file.SaveAs(savePath);
        ViewBag.Message = "檔案上傳成功。";
    }
    else
    {
        ViewBag.Message = "Upload Failed 😫";
    }
    return View();
}

其中存入伺服器資料夾的檔案名稱,使用 Guid.NewGuid() 來產生混淆,避免讓惡意上傳者直接猜到圖片生成的路徑。

此外如果限制上傳的檔案必須為圖片,可以在利用 System.Drawing.Image.FromStream 來檢查上傳的檔案是否符合:

[NonAction]
public bool CheckIsImage(Stream imageStream)
{
    bool check;
    try
    {
        System.Drawing.Image.FromStream(imageStream);
        check = true;
    }
    catch
    {
        check = false;
    }
    return check;
}

CheckIsImage 檢查的方式

if (!CheckIsImage(file.InputStream))
{
   fileValid = false;
}

View

@using (Html.BeginForm(
  "Upload", "Upload", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <div>
        @Html.TextBox("file", "", new { type = "file" }) <br />
        <input type="submit" value="Upload" /> <br />
        @ViewBag.Message
    </div>
}

注意: enctype 要使用 multipart/form-data

注意: File input 如果不是使用 Html.TextBox 要注意要主動給予 Name Attributes,且必須與 Controller 中 Action HttpPostedFileBase 所接收的參數名稱一致,否則會發現上傳但卻是 Null 的問題。

關於 enctype 的筆記,請參考 HTML Form Enctype

資安實踐

實作安全的下載機制

[NonAction]
public string FileNameMapping(string fileName)
{
    return "mapping.png";
}

public ActionResult Download(string fileName)
{
    var convertFileName = FileNameMapping(fileName);

    return File(
      Path.Combine(
        Server.MapPath("~/UploadFiles"), convertFileName)
        , "image/png"
      );
}

Download Action 必須要限制授權存取,並檢查存取者是否有要存取檔案的權限。

其中 FileNameMapping 需要利用資料庫對照表、Base64編碼,來轉換混淆後的檔案名稱回原本的檔案名稱,此外也可以藉由 FileNameMapping 思考是否會存在 Path Traversal 的問題。

避免檔案上傳資料夾被直接取用

web.config 可以設定隱藏區段的方式處理

<system.webServer>
  <security>
    <requestFiltering>
      <hiddenSegments>
        <add segment="UploadFiles" />
      </hiddenSegments>
    </requestFiltering>
  </security>
</system.webServer>

相關連結

ASP.NET MVC 5 實作更安全的檔案下載功能 (ASP.NET MVC Safer Downloads Implements)

參考資料

OWASP - File Upload Cheat Sheet

OWASP - Unrestricted File Upload