目錄遍歷(Path Traversal)攻擊是透過相對路徑的方式來跨目錄的方式,藉機取得Server上原本是不公開的檔案。用比喻的方式來說的話,就大概是類似:
你授權請助理小姐去書桌第一格抽屜(目錄)拿開會要用的報告(公開的檔案),但助理小姐知道第二格抽屜(目錄)有500萬現金,雖然你沒授權他可以開,在你也沒有上鎖的情況下,助理小姐就自己打開第二格抽屜(目錄)拿走了500萬(非公開的檔案)。
上面這個範例,大概就是目錄遍歷(Path Traversal)攻擊的方式。
絕對路徑 & 相對路徑
前面有提到,目錄遍歷(Path Traversal)攻擊是透過相對路徑的方式來跨目錄取得Server上原本是不公開的檔案。那我們就有必要先來了解一下什麼是絕對路徑根相對路徑。假設我有一個應用程式的路徑為D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\wwwroot\
。
絕對路徑
_hostEnv.WebRootPath
//D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\wwwroot\
相對路徑
Path.GetFullPath(Path.Combine(_hostEnv.WebRootPath, "/"))
//回到根目錄:D:\
Path.GetFullPath(Path.Combine(_hostEnv.WebRootPath, "./"))
//回到當前目錄:D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\wwwroot\
Path.GetFullPath(Path.Combine(_hostEnv.WebRootPath, "../"))
//回到上層目錄:D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\
Path.GetFullPath(Path.Combine(_hostEnv.WebRootPath, "../"))
//回到上上層目錄:D:\Homer\Project\DesignAuditRise\
Path.GetFullPath(Path.Combine(_hostEnv.WebRootPath, "./assets"))
//到下層目錄:D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\wwwroot\assets
從上面可以看到,我們不必透過完整的路徑。透過./, ../, ../../, ./xxx就可以到達我們所要到達的目錄位置。
目錄遍歷(Path Traversal)
看完了相對路徑的用法之後,我們現在就來做個情境來模擬目錄遍歷攻擊。情境大概是這樣:我們有一個WebAPI,接收前端給我們的參數{fileName},到後端之後再把指定目錄跟{fileName}Path.Combine起來。取得完整檔案路徑後,再把結果傳回前端,給使用者下載。
前端程式(Angular)
GetFile()
{
let fileName = `TemplateExcel.xlsx`;
//let fileName = `../../Content/Template/TemplateExcel.xlsx`;
//let fileName = `..%2F..%2FContent%2FTemplateFile%2FTemplateExcel.xlsx`;
//let fileName = `..%2F..%2Fappsettings.Production.json`;
//let fileName = `~%2FTemplateExcel.xlsx`;
this._rdaService.GetFile(fileName).subscribe({
next: (res)=>
{
this._rdaService.downloadFile(
`${fileName}`,
res,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
this._salService.showSwal("", "Excel下載完成.", "success");
},
error: (err: HttpErrorResponse)=>{
this._salService.showSwal("", `Excel下載失敗.`, "error");
}
});
}
GetFile(fileName: string): Observable<any>
{
const url = `${environment.apiBaseUrl}/{Web API的ControllerName}/GetFile/${fileName}`;
const options:any = this.generatePostOptions();
options.responseType = 'arraybuffer';
return this.httpClient.get<any>(url, options)
}
public downloadFile(name: string, data: any, type: string)
{
const blob = new Blob([data], { type: type });
const url = window.URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = url;
link.download = name;
link.click();
link.remove();
}
後端程式
[HttpGet]
[Route("{fileName}")]
public async Task<IActionResult> GetFile(string fileName)
{
var baseDirect = $"{_hostEnv.ContentRootPath}//Content//TemplateFile";
var decodeFileName = HttpUtility.UrlDecode(fileName);
var filePath = Path.Combine(baseDirect, decodeFileName);
var fullPath = Path.GetFullPath(filePath);
var mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
byte[] fileBytes = System.IO.File.ReadAllBytes(filePath);
return File(fileBytes, mimeType);
}
我們看一下Excel的完整檔案路徑為:
D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\Content\TemplateFile\TemplateExcel.xlsx。
知道了相對路徑的用法後,我們就可以用它來玩一些有趣的東西。
我們先來測試正常情況,fileName參數傳入TemplateExcel.xlsx
。看來是沒有問題的,TemplateExcel.xlsx也成功地下載完成。
現在假設我們知道Excel檔的完整路徑,然後參數用相對路徑的方式來試試看。fileName參數傳入../../Content/TemplateFile/TemplateExcel.xlsx
。結果卻是回應404找不到網頁。
接著我們再用經過Encode的網址,進行API呼叫測試。可以看到Reqest透過了正確的路由找到了我們的GetFile這支API,而且參數也成功取得了。
透過HttpUtility.UrlDecode把我們的fileName參數進行Decode。可以看到經過Decode後,就是我們熟悉的目錄格式了。
Url Encode:..%2F..%2FContent%2FTemplateFile%2FTemplateExcel.xlsx
Url Decode:../../Content/TemplateFile/TemplateExcel.xlsx
接著透過Path.Combine組合出完整的相對路徑。
再透過Path.GetFullPath轉換成絕對路徑。可以看到最後的路徑跟Excel檔原本的位置是一樣的。API也成功回傳檔案了。
那這樣我們參數傳TemplateExcel.xlsx跟傳相對路徑../../Content/TemplateFile/TemplateExcel.xlsx的差別在哪裡呢?
- TemplateExcel.xlsx
很單純的就是baseDirect D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\Content\TemplateFile加上TemplateExcel.xlsx組成一個絕對路徑
- ../../Content/TemplateFile/TemplateExcel.xlsx
目錄遍歷(Path Traversal)攻擊範例
知道了如何用相對路徑的方式來目錄遍歷後,就可以用這方式進入到原本沒有開放的目錄,取得不該被下載的檔案。現在假設我們知道有一個AppSettings檔的完整路徑在D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\appsettings.Production.json,然後傳入相對路徑參數..%2F..%2Fappsettings.Production.json來看看會發生什麼可怕的事情!!
哇靠!居然就這樣取得到appsettings.Production.json的絕對路徑了。
用瀏覽器call API看看,居然也成功把appsettings.Production.json檔案給下載下來了。
你各位寫C#的也知道appsettings裡面會放一些連線字串等等機密的東西。就這樣簡單的被下載走了。
目錄遍歷(Path Traversal)攻擊,就是這麼簡單。
如何避免目錄遍歷(Path Traversal)
這邊提供幾種方法
- 替資料夾目錄加上權限限制
- 檢查Url.Decode後的參數是否包含敏感字元:"/", @"\", "$", "..", "?"
- 檢查檔案路徑格式:譬如檔案路徑結尾的副檔名只能為.xlsx
另外也可以檢查Path.GetFullPath後的結果是否包含baseDirectory。以我們上面的例子來看
原本的baseDirectory為:D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\Content\TemplateFile\
經過目錄遍歷串改後的完整路徑變成:D:\Homer\Project\DesignAuditRise\DesignAuditRise.Web\appsettings.Production.json
var baseDirect = $"{_hostEnv.ContentRootPath}//Content//TemplateFile";
var decodeFileName = System.Web.HttpUtility.UrlDecode(fileName);
var filePath = Path.Combine(baseDirect, decodeFileName);
var fullPath = Path.GetFullPath(filePath);
if (!fullPath.StartsWith(baseDirect))
{
throw new Exception("err directory path.");
}
很明顯的最後fullPath的結果並不包含原本的baseDirectory,表示有可能被竄改過了baseDirect
,這時就能拋出錯誤,來避免掉攻擊。
總而言之,目錄遍歷(Path Traversal)是個簡單又實用的攻擊?
Ref:
1.[ASP.NET] Server.MapPath() 實體路徑 虛擬路徑
2.網站安全🔒 目錄遍歷 Path Traversal 攻擊手法
3.絕對路徑、相對路徑的練習(C++)
4..NET Path Traversal Guide: Examples and Prevention