一、簡介
臨時文件,顧名思義是臨時產生的文件,且文件的生命周期很短。
然而,很多應用的運行都離不開臨時文件,臨時文件在我們電腦上無處不在,主要有以下幾種形式的臨時文件:
1.文件或圖形編輯程序,所生成的中間文件
2.數據庫查詢時,生成的臨時緩存文件,提供之前的結果數據而,以減少再次訪問數據庫的代價;通常用於遠程數據庫或遠程xml的服務
3.文件被上傳後在服務端的臨時儲存,其文件名為php的全局變量$_FILES['userfile']['tmp_name']的值
4.在http請求中,用於存放session的臨時文件,這些文件名通常就是sessionid(如 sess_7483ae44d51fe21353afb671d13f7199)
5.在不同應用或相同應用傳遞數據,而對方要求基於文件的輸入,此時用臨時文件存放數據
二、臨時文件的安全特征
臨時文件的最大特征就是它的非持久性,除此之外,從安全性的角度,可以從以下幾個方面關注臨時文件的其它特點或風險:
1.臨時文件的位置
臨時文件通常被創建並存放在默認的路徑,在一個典型的Linux系統中,至少有兩個目錄或分區保持著臨時文件。其中之一是/tmp目錄,再者是/var/tmp。在更新的Linux內核的系統中,還可能有/dev/shm,它是用tmpfs文件系統裝載的。有時臨時文件,也可能放在用戶home目錄下的隱藏子目錄中。使用默認臨時文件目錄的好處在於,系統進程可以方便查找和讀寫。
然而,默認臨時文件的存放目錄可能成為損害系統安全的僵屍和rootkit的溫床。這是因為在多數情況下,任何人(或任何進程)都可以向這些目錄寫入東西,有不安全的許可問題。比如我們都知道sticky bit,該位可以理解為防刪除位。如果希望用戶能夠添加文件但同時不能刪除文件, 則可以對文件使用sticky bit位。設置該位後,就算用戶對目錄具有寫權限,也不能刪除該文件。多數Linux發行版本在臨時目錄上設置sticky位,這意味著用戶A不能清除屬於用戶B的一個文件,反之亦然。但是,根據文件自身的許可,用戶A有可能查看並修改那個文件的內容。
2.臨時文件的持久性
前面提到臨時文件是非持久的,在程序結束時,會被刪除,但有的時候臨時文件也會被迫持久保存了,沒有被刪除,如:
2.1 應用程序在關閉前崩潰了,還沒有機會刪除臨時文件
2.2 應用程序還跑著,但操作系統崩潰了
2.3 文件復制過程中由於空間問題而復制失敗,導致中間文件沒有刪除
2.4 操作系統進程通常會定期清空的默認臨時文件目錄,但可能因為某些原因,而刪除失敗
2.5 寫得不好的應用程序,可能忽略或者忘記了刪除臨時文件
3.臨時文件的風險性
無用的臨時文件像幽靈一樣存在你的服務器上,一方面占用硬盤,另一方面,可以被其它人非法使用,存著如下一些風險:
3.1 可見性
眾所周知,將私有數據公開很有風險。一旦用戶通過某些手段(如shell或者ftp)竊取了你的臨時文件,就可以獲取到用戶或企業的私有數據,從而對你造成影響。
例如:臨時文件2011_Confidential_Sales_Strategies.tmp,可能暴露你們公司2011年的商業策略,這對你的競爭對手來說,將很有用處;而對於session劫持者來說,存放用戶session信息的臨時文件sess_95971078f4822605e7a18c612054f658非常關鍵。
除此之外,還有別的情況臨時文件可能會被偷窺,如:一個拼寫檢查的服務,返回結果的url是:http://bad.example.com/spellcheck.php?tmp_file=spellcheck46 ,攻擊者分析你的url參數後使用http://bad.example.com/spellcheck.php?tmp_file=spellcheck45 就可以訪問到前一個用戶的驗證結果了。
3.2 可執行性
通常臨時文件是不可執行,但如果攻擊者上傳了一個php腳本到你的臨時目錄,而且通過某種方式執行了它,那可能造成悲劇了。
3.3 臨時文件被劫持
攻擊者可能為了自己的目的,而劫持你的臨時文件。他可能替換你的臨時文件,也可能在你的臨時文件後面追加一些信息。
劫持臨時文件的目的包括:
(1)讓你的應用程序處理他的數據,而不是你自己的數據
(2)暴露隱私數據,比如系統的密碼文件,或者其它php安全模式不能正常讀的文件
(3)刪除數據,阻礙請求的正常進行
(4)創建並輸出虛假的數據,破壞請求的結果
(5)通過提供虛假的數據,對使用數據進行下一步處理的應用程序造成破壞
(6)將你的輸出重定向到其它地方,可以方便攻擊者訪問或者覆蓋系統文件
劫持通常與競爭條件相關。當兩個不同的進程操作同一個文件的時候,就可能產生競爭條件。例如,一個讀進程和一個寫進程同時操作一段數據,當寫進程只完成了一部分的時候,讀進程已經完成,這樣讀的到內容一部分是新的,一部分是舊的,也就是我們常說的讀髒數據。
臨時文件的劫持,在一定程度上會造成競爭條件,除非劫持者准確的把握時間和位置,否則就會造成此類安全問題。
三、預防臨時文件被惡意使用
前面我們介紹了臨時文件的概念,以及臨時文件被惡用可能帶來的危害,這個部分主要介紹一些策略來預防臨時文件被惡意利用,以及減少其帶來的危害。
1.調整存放位置
防止臨時文件被惡意利用的最重要,也是最簡單的一步就是讓你的臨時文件目錄以及名字不容易被猜到。任何對臨時文件的惡意利用,攻擊者都必須知道臨時文件的名字和路徑,因此你應該盡可能的讓他難以猜到你的臨時文件名字及路徑。
建議你在臨時文件目錄的選擇時,還是將你的臨時文件放在默認的目錄下吧,這樣系統進程可以方便找到以及讀寫。而把精力花費放在為文件名想個合適的難猜的名字。
php的tempnam()函數,可以創建一個臨時文件,並且其自動生成的文件名不會與當前目錄下的其它文件名沖突,此函數創建的文件默認權限是600,即rw——-。
例如
$filename = tempnam( ‘..', ‘myTempfile');
運行後可能生成一個名為myTempfile1af的文件,當第二次運行的時候就生成了名為myTempfile1b0的文件名。
也許一些編程實踐指南會建議你在使用tempnam()生成文件的時候,用一些有意義的前綴來命名,這樣能通過文件名看出文件中包含的數據或者需要此數據的應用,但從安全性的角度來看最好不要這樣,這樣等於為攻擊者指明了方向。
這裡介紹一種方法,即能有一定意義的前綴同時也讓攻擊者不那麼好猜,如下:
<?php // define the parts of the filename define(‘TMP_DIR','/tmp/'); $prefix = ‘skiResort'; // construct the filename $tempFilename = uniqid( $prefix, TRUE ); // create the file touch( $tempFilename ); // restrict permissions chmod ( $tempFilename, 0600 ); // now work with the file // … assuming data in $value file_put_contents( $tempFilename, $value ); // … // when done with temporary file, delete it unlink ( $tempFilename ); ?>
這個腳本通過uniqid()函數,生成的文件名格式為:/tmp/skiResort392942668f9b396c08.03510070,並通過chmod將文件的權限設置為600。
如果你需要與其它應用共享信息,比如用戶密碼或運行時生成的隨機token,這裡你可能需要對文件名加密,只有知道這個密鑰的應用程序才能讀取或修改文件內容。
如下是一個簡單的生成加密文件名文件的示例:
<?php $pathPrefix = ‘/tmp/skiResort'; // for demonstration, construct a secret here $secret = ‘Today is ‘ . date( “l, d F.” ); $randomPart = sha1( $secret ); $tempFilename = $pathPrefix . $randomPart; touch( $tempFilename ); chmod ( $tempFilename, 0600 ); // now work with the file // … assuming data in $value file_put_contents( $tempFilename, $value ); // … // when done with temporary file, delete it unlink ( $tempFilename ); ?>
2.約束訪問權限
為了降低臨時文件被執行或劫持的可能性,需要設置臨時文件和臨時文件目錄的訪問權限。通常情況下,將臨時文件的權限設置為rw——-,臨時文件目錄的權限設置為rwx——。
此外,也可以通過設置apache的配置文件來限制訪問(只有你將臨時文件放在www目錄下的時候),如下:
order deny,allow deny from all
3.只寫已知文件
既然你是臨時文件的創建者和作者,那你應該隨時知道哪些文件存在,文件裡有哪些內容。前面提到的方法,只是讓臨時文件劫持更困難,但不能完全杜絕劫持者替換文件或者在文件後面追加一些內容的可能,所以在你創建或寫文件時,需要仔細檢查文件內容是否滿足要求。
當你使用w+的方式,創建了一個文件,在你開始寫之前,這個文件應該為空,如下
<?php if ( filesize( $tempFilename ) === 0 ) { // write to the file } else { exit ( “$tempFilename is not empty.\nStart over again.”); } ?>
如果文件不為空,可能你創建的有問題,也有可能劫持者在你創建與寫文件的這個時間段內作了手腳。
還有可能,你第一次成功寫入了臨時文件,但在你後面的寫的過程中,劫持者對這個臨時文件進行了一些操作,這種情況可以通過檢驗碼的方式來檢查,如下:
<?php // write something to the file; then hash it $hashnow = sha1_file( $tempFilename ); $_SESSION['hashnow'] = $hashnow; // later, get ready to write again $hashnow = sha1_file( $tempFilename ); if ( $hashnow === $_SESSION['hashnow'] ) { // write to the file again // get and save a new hash $hashnow = sha1_file( $tempFilename ); $_SESSION['hashnow'] = $hashnow; } else { exit ( “Temporary file contains unexpected contents.\nStart over again.”); } ?>
4.只讀已知文件
與只寫已知文件類似,在讀文件前需要檢查檢驗碼是否一致,防止臨時文件被篡改。除此之外,如果你使用了openssl,可以在寫文件的時候,將合法證書放在文件的末尾,這樣的讀的時候可以先檢查文件末尾是否存在合法的證書;如果你沒有使用openssl,也可以寫入一段特定的算法生成的token,原理類似。
5.檢查上傳的文件
判斷文件是否是通過 HTTP POST 上傳的
bool is_uploaded_file ( string $filename )
如果 filename 所給出的文件是通過 HTTP POST 上傳的則返回 TRUE。這可以用來確保惡意的用戶無法欺騙腳本去訪問本不能訪問的文件,例如 /etc/passwd。 如果上傳的文件有可能會造成對用戶或本系統的其他用戶顯示其內容的話,這種檢查顯得格外重要。
為了能使 is_uploaded_file() 函數正常工作,必須指定類似$_FILES['userfile']['tmp_name'] 的變量,而不是從客戶端上傳的文件名 $_FILES['userfile']['name']。需要注意的是is_uploaded_file返回false,不一定是上傳文件被劫持了,也有可能是文件太大或者上傳部分等,這些可以通過$_FILES['userfile']['error']查看。