提供資源存取的協定(例如 HTTP 或其他檔案分享協定)會用某種識別碼來指定你想存取的資源,可能是檔案路徑,也可能是其他唯一識別碼。應用程式必須先解析這個識別碼才能存取目標資源;成功就回傳資源內容,否則協定拋出錯誤。
在處理資源識別碼的過程中,有數種漏洞可能發生。值得把所有可能性都測過一遍,並仔細觀察應用程式的回應。
正規化(Canonicalization)#
當資源識別碼是一組階層式的資源與目錄清單時,我們通常稱它為路徑(path)。作業系統一般會定義用兩個點(..)來表示上層目錄的相對路徑關係。存取檔案之前,作業系統必須根據這些相對路徑資訊把檔案找出來。
一個很天真的遠端檔案協定,可能會直接拿遠端使用者給的路徑、與基底目錄串接後丟給作業系統——這就是正規化漏洞(canonicalization vulnerability)。

圖表 9-4:正常的路徑正規化操作與有漏洞的版本比較
def send_file_to_client()
{
string name = read_string(); /* 從網路讀取檔名 */
// Concatenate name from client with base path
string fullPath = "/files" + name; /* 直接串接固定基底路徑 */
int fd = open(fullPath, READONLY); /* 交給 OS 解析相對路徑成分 */
// Read file to memory
byte data[] read_to_end(fd);
// Send to client
write_bytes(data, len(data));
}程式把用戶端送來的字串與固定基底路徑串接成完整路徑,希望藉此把存取限制在檔案系統的一小塊區域。但檔案是由作業系統開啟的,路徑中若含有相對成分就會被解析掉。
只要看到這一連串操作,你就找到了一個正規化漏洞。攻擊者送出一個相對路徑,讓作業系統把它解析到基底目錄之外的檔案,敏感檔案就被洩漏出去了。
即使應用程式在把路徑交給作業系統之前做了某些檢查,它的檢查方式也必須完全比對得上作業系統詮釋這個字串的方式。
舉例來說,Microsoft Windows 同時接受反斜線(
\)與正斜線(/)作為路徑分隔符。若應用程式只檢查 Windows 標準的反斜線,漏洞依然存在。
正規化漏洞若出現在檔案上傳協定上,後果比下載嚴重得多。能夠上傳檔案到應用程式所在系統、又能指定任意路徑,等於大幅降低攻陷系統的門檻——例如上傳腳本或其他可執行內容,再設法讓系統執行它,就導向遠端程式碼執行。
詳盡錯誤訊息(Verbose Errors)#
當應用程式找不到請求的資源時,通常會回傳某些錯誤資訊。那可以只是一個錯誤碼,也可以是「什麼東西不存在」的完整描述——但它不該洩漏超過必要範圍的資訊。當然,現實往往不是如此。
若應用程式在回報「資源不存在」時,把該資源在本機端的資訊塞進錯誤訊息裡,就存在一個簡單的漏洞。
def send_file_to_client_with_error()
{
string name = read_string();
// Concatenate name from client with base path
string fullPath = "/files" + name;
if(!exist(fullPath))
{
write_error("File " + fullPath + " doesn't exist"); /* 把完整本機路徑吐給用戶端 */
}
else
{
write_file_to_client(fullPath);
}
}這段程式會洩漏基底路徑在本機檔案系統上的位置。這個路徑可以搭配其他漏洞,換取對系統更多的存取權。此外,如果資源目錄位在某個使用者的家目錄下,錯誤訊息連帶也洩漏了執行該應用程式的使用者是誰。