界: Time and State

分散式運算與時間和狀態相關。也就是說,為了使多個元件進行通訊,必須共用狀態,並且這一切都需要時間。

大多數的程式設計師將他們的工作擬人化。他們想採用一種控制執行緒來執行整個程式,就像他們必須自己完成這項工作一樣。但是,現代的電腦可以非常快速地切換工作,並且在多核心多 CPU 或分散式系統中,兩個事件可能恰好同時發生。瑕疵急於填補程式設計師在程式執行模型與實際情況之間的差距。這些瑕疵與執行緒、處理序、時間和資訊之間的意外互動有關。這些互動透過共用狀態發生:信號、變數、檔案系統,以及基本上任何可以儲存資訊的項目。

Insecure Temporary File

Abstract
建立和使用不安全的暫存檔案會使得應用程式和系統資料產生安全性弱點,容易受到攻擊。
Explanation
應用程式需要非常頻繁地呼叫暫存檔案,許多種不同的機制都是為了在 C 程式庫或 Windows(R) API 中建立暫存檔案而存在的。大多函數很容易受到多種形式的攻擊。
範例:以下程式碼會在對從網路收集的直接資料進行處理之前,使用暫存檔案進行儲存。


...
if (tmpnam_r(filename)){
FILE* tmp = fopen(filename,"wb+");
while((recv(sock,recvbuf,DATA_SIZE, 0) > 0)&&(amt!=0))
amt = fwrite(recvbuf,1,DATA_SIZE,tmp);
}
...


這程式碼並不容易被注意到,但卻容易受到很多不同的攻擊,因為它建立暫存檔案的方式並不安全。以下各節將詳述由此函數及其它內容所造成的安全弱點。大部分臭名昭著與暫存檔案建立有關的安全問題已在 Unix 作業系統上出現,但是 Windows 應用程式同樣存在著這樣的風險。此章節討論的主題為在 Unix 和 Windows 上建立暫存檔案。

不同系統間使用的方法和運作方式可能是不同的,但是各自造成的基本風險則相差不大。請參閱「建議事項」一節,以瞭解核心安全語言的相關資訊及安全建立暫存檔案的相關建議方法。

設計用來幫助建立暫存檔案的函數可依據其是否是提供檔案名稱或實際開啟新檔,而分成兩個部分。

群組 1 -「唯一的」檔案名稱:

第一組 C 程式庫和 WinAPI 函數是用來幫助建立暫存檔案,其透過產生唯一的檔案名稱來建立要被某程式打開的新暫存檔案。這部分包含了如 tmpnam()tempnam()mktemp() 這樣的 C 程式庫函數以及與其等價的以 _ (下劃線) 開頭的 C++ 文件和 Windows API 中的 GetTempFileName() 函數。這組函數在檔案名稱選擇方面很可能會有 Race Condition 的麻煩。雖然函數保證在選取檔案時,其檔案名稱是唯一的,但是沒有機制能防止其他處理或攻擊者在選取檔案後和應用程式試圖開啟檔案之前的這段時間內,建立一個相同檔案名稱。相比一個由其他程式呼叫相同函數所引發的合法衝突,攻擊者非常有可能建立一個惡意的衝突,因為由這些函數建立的檔案名稱沒有充分的隨機化來使猜測更加困難。

如果建立了特定名稱的檔案,那麼根據這個檔案的開啟方式,現有內容或存取權限都可能保持不變。如果現有檔案內容實質上是惡意的,攻擊者就可以在應用程式從暫存檔案讀取資料時向程式注入危險資料。如果攻擊者預先建立了一個能輕鬆獲取存取權限的檔案,那麼被應用程式儲存在暫存檔案裏的資料將被存取、修改或毀壞。在 Unix 系統上,如果攻擊者預先定義了一個檔案作為另一個重要檔案的連接,那麼更狡猾的攻擊也可能存在。那麼,如果應用程式從檔案截取資料或將資料寫入檔案,它將不知不覺地為攻擊者執行惡意操作。如果程式是以提升過的權限執行,那將是極其嚴重的威脅。

最後,最好的情況就是呼叫 open() 函數並使用 O_CREATO_EXCL 旗標,或者呼叫 CreateFile() 函數並使用 CREATE_NEW 屬性來開啟檔案。如此一來,如果檔案已經存在,那麼檔案建立就會失敗,也可以防範以上所描述的攻擊類型。然而,如果攻擊者可以準確預測一個檔案序列的檔案名稱,那麼應用程式將被強制禁止開啟所需的臨時儲存空間,進而引發 Denial of Service (DoS) 攻擊。如果使用由這些函數產生的一小部分亂數來選擇檔案名稱,那麼這種類型的攻擊將不難實現。

群組 2 -「唯一的」檔案:

C 程式庫函數的第二部分是藉由產生唯一的檔案名稱且開啟該檔案,來解決一些與暫存檔案有關的安全問題。這部分包含了像 tmpfile() 這樣的 C 函數和與之對應的以 _ (下劃線) 開頭的 C++ 函數,以及表現更為出色的 C 程式庫函數 mkstemp()

類似 tmpfile() 的函數構成唯一的檔案名稱,並藉由與傳遞 "wb+" 標記時 fopen() 使用的相同方法開啟檔案,就像讀/寫模式下的二位元檔一樣。如果檔案已存在,tmpfile() 將把檔案大小縮小為 0,藉此緩和前面提到的有關存在於唯一檔案名稱選擇與隨之打開選取檔案之間的 Race Condition 的安全問題。然而,這個運作方式顯然不能解決函數的安全性問題。首先,攻擊者可能使用可能由 tmpfile().開啟的檔案所保存的寬鬆存取權限來預先建立檔案。其次,在 Unix 系統上,如果攻擊者預先建立了一個檔案作為其他重要檔案的一個連接,應用程式可能會使用其被提升的權限去縮小該檔案,這樣就能按照攻擊的意願執行破壞。最後,如果 tmpfile() 建立了一個新檔案,那麼套用在此檔案上的存取權限在不同的作業系統間是不同的;如此一來,應用程式就極易受到攻擊,而攻擊者甚至不需要預先猜測到將被使用的檔案名稱也可進行攻擊。

最後,mkstemp() 函數是建立暫存檔案的安全方式。它將以一個使用者結合一組隨機生成的字元提供的檔案名稱範本的方式,來建立並開啟唯一的檔案。如果它不能建立此檔案,它將回傳 -1 表示失敗。在先進的系統上,是使用模式0600 來開啟檔案,這就意味著這個檔案將免受干擾,除非使用者改變其存取權限。然而,mkstemp() 仍然會受到使用能夠預先推測的檔案名稱的威脅,而且如果攻擊是藉由猜測或預先建立將要使用的檔案名稱的檔案導致 mkstemp() 函數失效的話,將使應用程式極易受到 Denial of Service 攻擊。
References
[1] B. Schneier Yarrow: A secure pseudorandom number generator
[2] CryptLib
[3] Crypto++
[4] BeeCrypt
[5] OpenSSL
[6] CryptoAPI: CryptGenRandom() Microsoft
[7] RtlGenRandom() Microsoft
[8] .NET System.Security.Cryptography: Random Number Generation Microsoft
desc.semantic.cpp.insecure_temporary_file
Abstract
建立和使用不安全的暫存檔案會使得應用程式和系統資料產生安全性弱點,容易受到攻擊。
Explanation
應用程式需要非常頻繁地使用暫存檔案,許多不同機制都是為了建立暫存檔案而存在的。大多函數很容易受到多種形式的攻擊。
範例:以下程式碼使用暫存檔案在它被處理之前用來儲存從網路上收集到的中間資料。


...
try:
tmp_filename = os.tempnam()
tmp_file = open(tmp_filename, 'w')
data = s.recv(4096)
while True:
more = s.recv(4096)
tmp_file.write(more)
if not more:
break
except socket.timeout:
errMsg = "Connection timed-out while connecting"
self.logger.exception(errMsg)
raise Exception
...


這程式碼並不容易被注意到,但卻容易受到很多不同的攻擊,因為它建立暫存檔案的方式並不安全。以下各節將詳述由此函數及其它內容所造成的安全弱點。大部分臭名昭著與暫存檔案建立有關的安全問題已在 Unix 作業系統上出現,但是 Windows 應用程式同樣存在著這樣的風險。

不同系統間使用的方法和運作方式可能是不同的,但是各自造成的基本風險則相差不大。請參閱「建議事項」一節,以瞭解核心安全語言的相關資訊及安全建立暫存檔案的相關建議方法。

設計用來幫助建立暫存檔案的函數可依據其是否是提供檔案名稱或實際開啟新檔,而分成兩個部分。

群組 1 -「唯一的」檔案名稱:

第一組函數是用來幫助建立暫存檔案,這組函數透過產生唯一的檔案名稱來建立稍後程式會開啟的新暫存檔案。這組函數在檔案名稱選擇方面很可能會有 Race Condition 的麻煩。雖然函數保證在選取檔案時,其檔案名稱是唯一的,但是沒有機制能防止其他處理或攻擊者在選取檔案後和應用程式試圖開啟檔案之前的這段時間內,建立一個相同檔案名稱。相比一個由其他程式呼叫相同函數所引發的合法衝突,攻擊者非常有可能建立一個惡意的衝突,因為由這些函數建立的檔案名稱沒有充分的隨機化來使猜測更加困難。

如果建立了特定名稱的檔案,那麼根據這個檔案的開啟方式,現有內容或存取權限都可能保持不變。如果現有檔案內容實質上是惡意的,攻擊者就可以在應用程式從暫存檔案讀取資料時向程式插入危險資料。如果攻擊者預先建立了一個能輕鬆獲取存取權限的檔案,那麼被應用程式儲存在暫存檔案裏的資料將被存取、修改或毀壞。在 Unix 系統上,如果攻擊者預先定義了一個檔案做為另一個重要檔案的連接,那麼更狡猾的攻擊也可能存在。那麼,如果應用程式從檔案截取資料或將資料寫入檔案,它將不知不覺地為攻擊者執行惡意操作。如果程式是以提升過的權限執行,那將是極其嚴重的威脅。

最後,最好的情況就是呼叫 open() 函數並使用 os.O_CREATos.O_EXCL 旗標來開啟檔案。如此一來,如果檔案已經存在,那麼檔案建立就會失敗,也可以防範以上所描述的攻擊類型。然而,如果攻擊者可以準確預測一個檔案序列的檔案名稱,那麼應用程式將被強制禁止開啟所需的臨時儲存空間,進而引發 Denial of Service (DoS) 攻擊。如果使用由這些函數產生的一小部分亂數來選擇檔案名稱,那麼這種類型的攻擊將不難實現。

群組 2 -「唯一的」檔案:

函數的第二部分是藉由產生唯一的檔案名稱並開啟該檔案,來嘗試解決一些與暫存檔案有關的安全問題。這一部分包含諸如 tmpfile() 的函數。

類似 tmpfile() 的函數構成唯一的檔案名稱,並藉由與傳遞 "wb+" 標記時 open() 使用的相同方法開啟檔案,就像讀/寫模式下的二位元檔一樣。如果檔案已存在,tmpfile() 將把檔案大小縮小為 0,藉此緩和前面提到的有關存在於唯一檔案名稱選擇與隨之打開選取檔案之間的 Race Condition 的安全問題。然而,這個運作方式顯然不能解決函數的安全性問題。首先,攻擊者可能使用可能由 tmpfile().開啟的檔案所保存的寬鬆存取權限來預先建立檔案。其次,在 Unix 系統上,如果攻擊者預先建立了一個檔案作為其他重要檔案的一個連接,應用程式可能會使用其被提升的權限去縮小該檔案,這樣就能按照攻擊的意願執行破壞。最後,如果 tmpfile() 建立了一個新檔案,那麼套用在此檔案上的存取權限在不同的作業系統間是不同的;如此一來,應用程式就極易受到攻擊,而攻擊者甚至不需要預先猜測到將被使用的檔案名稱也可進行攻擊。
References
[1] B. Schneier Yarrow: A secure pseudorandom number generator
[2] Python Library Reference: os Python
[3] Python Library Reference: tempfile Python
[4] Symlink race WikiPedia
[5] Time of check to time of use WikiPedia
desc.semantic.python.insecure_temporary_file