[ASP.net MVC/資安] 如何防範Path Traversal目錄瀏覽漏洞

How to fix ASP.net MVC Path Traversal

前言

最近碰上資安弱點掃描,ASP.net MVC網站被掃出Path Traversal目錄瀏覽漏洞

才知道原來以往組字串來存取檔案的方式是有風險的Orz

有漏洞風險的程式碼如下

        public ActionResult TestFile(string fileName)
        {
            //App_Data目錄
            string dirPath = Server.MapPath("~/App_Data");
            //組路徑字串
            string filePath = Path.Combine(dirPath, fileName);
            if (System.IO.File.Exists(filePath))//檔案存在的話
            {
                //原本預期下載Excel檔
                return File(filePath, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); 
            }
            else
            {
                return Content("not exists");
            }

        }

由前端輸入到後端的參數fileName沒有過濾的話,駭客可以輸入fileName=../Web.config來取得Web.config

※雖然IIS預設啟用要求篩選,用戶無法在URL輸入路徑直接下載.config檔,但如果是透過這種駭客手法,就可以由Server端允許回傳.config檔下載

不只下載,如果程式是執行System.IO.File.Delete(filePath);的話,下場也很可怕

遇到這種有漏洞的程式碼,弱點掃描工具大部份會在

System.IO.File.Exists()或System.IO.File.Delete()這兩個方法標記有弱點

ASP.net MVC該如何修正它呢?

實作

先示範第一種錯誤改法

            //App_Data目錄
            string dirPath = Server.MapPath("~/App_Data");
            DirectoryInfo dir = new DirectoryInfo(dirPath);

            /*改用DirectoryInfo的searchPattern找檔案*/
            FileInfo file = dir.EnumerateFiles(fileName, SearchOption.TopDirectoryOnly).FirstOrDefault();
            if (file != null && file.Exists)
            {
                //原本預期下載Excel檔
                return File(file.FullName, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);

            }
            else
            {
                return Content("not exists");
            }

雖然前端無法輸入「../」了

但卻可以向下搜尋

以下是我認為正確的防範寫法,但最後一種才能通過工具檢測(工具也有誤判的時候呀~)

1.對fileName只取檔名

             string dirPath = Server.MapPath("~/App_Data");
            /*只取檔名,輸入字串就無法向上向下走訪目錄*/
            fileName = System.IO.Path.GetFileName(fileName);
            string filePath = System.IO.Path.Combine(dirPath, fileName);
        
            if (System.IO.File.Exists(filePath))
            {
                //原本預期下載Excel檔
                return File(filePath, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); 
            }
            else
            {
                return Content("not exists");
            }

2.組出來的filePath再比對一次目錄路徑

            string dirPath = Server.MapPath("~/App_Data"); 
            //組檔案路徑字串
            string filePath = System.IO.Path.Combine(dirPath, fileName);
            /*判斷是否為App_Data目錄*/
            if (Path.GetDirectoryName(filePath) == dirPath &&  System.IO.File.Exists(filePath))
            {
                //原本預期下載Excel檔
                return File(filePath, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); 
            }
            else
            {
                return Content("not exists");
            }

3.列舉該目錄全部檔案再比對檔名來挑檔(這樣寫可以通過CHECKMARX資安檢測)

 public ActionResult TestFile(string fileName)
 {
            //App_Data目錄
            string dirPath = Server.MapPath("~/App_Data");
            DirectoryInfo dir = new DirectoryInfo(dirPath);
              
            //列舉全部檔案再比對檔名
            FileInfo file  = dir.EnumerateFiles()
                                .FirstOrDefault(m=>m.Name==fileName);
          
            if (file!=null && file.Exists)//檔案存在的話
            {
                //原本預期下載Excel檔
                return File(file.FullName, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Name);
            }
            else
            {
                return Content("not exists");
            }
             
  }

以上是我try error無數次情況下,試出檢測工具的接受度XD

Is Path Traversal Vulnerabilities possible in my below code? StackOverFlow討論上

也有人指出用Path.GetInvalidFileNameChars()方法來判斷輸入參數有沒有包含斜線

這也是一種辦法(但能不能通過工具檢測有待試試)

不過如果前端一定要傳給後端包含斜線的字串(例如:/Upload/image.jpg),該網友的辦法就無法寫出正常功能

這種案例在Server端得一定加上Path.GetFileName(fileName),只取檔名,但因為只這樣不會通過工具檢測

所以還得搭配上述第3種寫法,完整程式碼如下:

 //參數imgSrc由前端輸入包含斜線的字串
 public ActionResult TestFile(string imgSrc)
 {
            //App_Data目錄
            string dirPath = Server.MapPath("~/App_Data");
            DirectoryInfo dir = new DirectoryInfo(dirPath);
            //只取檔名
            string fileName = Path.GetFileName(imgSrc);
            //列舉全部檔案再比對檔名
            FileInfo file  = dir.EnumerateFiles()
                                .FirstOrDefault(m=>m.Name==fileName);
          
            if (file!=null && file.Exists)//檔案存在的話
            {
                return Content("exists");
            }
            else
            {
                return Content("not exists");
            }
             
  }

最後附上資安弱掃工具CHECKMARX的說明