關鍵要點
hash_file()
函數可用於創建用於監控的文件結構配置文件。可以存儲每個文件的哈希值,以便以後進行比較以檢測任何更改。 file_path
存儲服務器上文件的路徑,file_hash
存儲文件的哈希值。 RecursiveDirectoryIterator
類可用於遍歷文件樹並收集用於比較的哈希值。然後可以使用這些哈希值更新 integrity_hashes
數據庫。可以使用 PHP 的 array_diff_assoc()
函數檢查差異,這有助於識別已添加、刪除或更改的文件。 應對網站管理中的各種情況
考慮一下在管理網站時如何解決以下情況:
更重要的是,您是否知道是否發生了這些情況之一?如果您的答案是否定的,請繼續閱讀。在本指南中,我將演示如何創建文件結構配置文件,該配置文件可用於監控文件的完整性。
確定文件是否已被更改的最佳方法是對其內容進行哈希處理。 PHP 提供了多種哈希函數,但對於此項目,我決定使用 hash_file()
函數。它提供了各種不同的哈希算法,如果我決定稍後進行更改,這將使我的代碼易於修改。哈希用於各種應用程序,從密碼保護到 DNA 測序。哈希算法通過將數據轉換為固定大小的可重複加密字符串來工作。它們的設計使得即使對數據進行輕微修改也應該產生非常不同的結果。當兩個或多個不同的數據產生相同的字符串結果時,則稱為“衝突”。每種哈希算法的強度可以通過其速度和衝突概率來衡量。在我的示例中,我將使用 SHA-1 算法,因為它速度快,衝突概率低,並且已被廣泛使用和充分測試。當然,歡迎您研究其他算法並使用任何您喜歡的算法。獲得文件的哈希值後,可以將其存儲起來以便以後進行比較。如果以後對文件進行哈希處理沒有返回與之前相同的哈希字符串,那麼我們就知道該文件已被更改。
數據庫
首先,我們需要佈局一個基本表來存儲文件的哈希值。我將使用以下模式:
CREATE TABLE integrity_hashes ( file_path VARCHAR(200) NOT NULL, file_hash CHAR(40) NOT NULL, PRIMARY KEY (file_path) );
file_path
存儲服務器上文件的路徑,由於該值始終是唯一的(因為兩個文件不能佔用文件系統中的同一位置),因此它是我們的主鍵。我將其最大長度指定為 200 個字符,這應該允許一些較長的文件路徑。 file_hash
存儲文件的哈希值,它將是一個 SHA-1 40 字符的十六進製字符串。
收集文件
下一步是構建文件結構的配置文件。我們定義要開始收集文件的路徑,並遞歸地迭代每個目錄,直到我們覆蓋了文件系統的整個分支,並可以選擇性地排除某些目錄或文件擴展名。我們在遍歷文件樹時收集所需的哈希值,然後將其存儲在數據庫中或用於比較。 PHP 提供了幾種遍歷文件樹的方法;為簡單起見,我將使用 RecursiveDirectoryIterator
類。
<?php define("PATH", "/var/www/"); $files = array(); // 要获取的扩展名,空数组将返回所有扩展名 $ext = array("php"); // 要忽略的目录,空数组将检查所有目录 $skip = array("logs", "logs/traffic"); // 构建配置文件 $dir = new RecursiveDirectoryIterator(PATH); $iter = new RecursiveIteratorIterator($dir); while ($iter->valid()) { // 跳过不需要的目录 if (!$iter->isDot() && !in_array($iter->getSubPath(), $skip)) { // 获取特定文件扩展名 if (!empty($ext)) { // PHP 5.3.4: if (in_array($iter->getExtension(), $ext)) { if (in_array(pathinfo($iter->key(), PATHINFO_EXTENSION), $ext)) { $files[$iter->key()] = hash_file("sha1", $iter->key()); } } else { // 忽略文件扩展名 $files[$iter->key()] = hash_file("sha1", $iter->key()); } } $iter->next(); }
請注意,我在 $skip
數組中兩次引用了相同的文件夾 logs
。僅僅因為我選擇忽略特定目錄並不意味著迭代器也會忽略所有子目錄,這取決於您的需求,這可能很有用或很煩人。 RecursiveDirectoryIterator
類使我們可以訪問多種方法:
valid()
檢查我們是否正在使用有效文件isDot()
確定目錄是否為“.”或“..”getSubPath()
返回文件指針當前所在的文件夾名稱key()
返回完整路徑和文件名next()
重新啟動循環還有更多可用的方法,但大多數情況下,上面列出的方法是我們所需的所有方法,儘管在 PHP 5.3.4 中添加了 getExtension()
方法,該方法返回文件擴展名。如果您的 PHP 版本支持它,您可以使用它來過濾不需要的條目,而不是我使用 pathinfo()
所做的操作。執行後,代碼應使用類似於以下內容的結果填充 $files
數組:
<code>Array ( [/var/www/test.php] => b6b7c28e513dac784925665b54088045cf9cbcd3 [/var/www/sub/hello.php] => a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa [/var/www/sub/world.php] => da39a3ee5e6b4b0d3255bfef95601890afd80709 )</code>
構建配置文件後,更新數據庫非常容易。
<?php $db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD); // 清除旧记录 $db->query("TRUNCATE integrity_hashes"); // 插入更新的记录 $sql = "INSERT INTO integrity_hashes (file_path, file_hash) VALUES (:path, :hash)"; $sth = $db->prepare($sql); $sth->bindParam(":path", $path); $sth->bindParam(":hash", $hash); foreach ($files as $path => $hash) { $sth->execute(); }
檢查差異
您現在知道如何構建目錄結構的新配置文件以及如何更新數據庫中的記錄。下一步是將其組合到某種實際應用程序中,例如帶有電子郵件通知的 cron 作業、管理界面或任何您喜歡的其他內容。如果您只想收集已更改的文件列表,而不在乎它們如何更改,那麼最簡單的方法是將數據從數據庫提取到類似於 $files
的數組中,然後使用 PHP 的 array_diff_assoc()
函數來去除不需要的內容。
CREATE TABLE integrity_hashes ( file_path VARCHAR(200) NOT NULL, file_hash CHAR(40) NOT NULL, PRIMARY KEY (file_path) );
在此示例中,$diffs
將填充任何發現的差異,或者如果文件結構完整,則它將是一個空數組。與 array_diff()
不同,array_diff_assoc()
將在比較中使用鍵,這在我們發生衝突時很重要,例如兩個空文件具有相同的哈希值。如果您想更進一步,您可以添加一些簡單的邏輯來準確確定文件是如何受到影響的,無論是被刪除、更改還是添加。
<?php define("PATH", "/var/www/"); $files = array(); // 要获取的扩展名,空数组将返回所有扩展名 $ext = array("php"); // 要忽略的目录,空数组将检查所有目录 $skip = array("logs", "logs/traffic"); // 构建配置文件 $dir = new RecursiveDirectoryIterator(PATH); $iter = new RecursiveIteratorIterator($dir); while ($iter->valid()) { // 跳过不需要的目录 if (!$iter->isDot() && !in_array($iter->getSubPath(), $skip)) { // 获取特定文件扩展名 if (!empty($ext)) { // PHP 5.3.4: if (in_array($iter->getExtension(), $ext)) { if (in_array(pathinfo($iter->key(), PATHINFO_EXTENSION), $ext)) { $files[$iter->key()] = hash_file("sha1", $iter->key()); } } else { // 忽略文件扩展名 $files[$iter->key()] = hash_file("sha1", $iter->key()); } } $iter->next(); }
當我們遍歷數據庫中的結果時,我們會進行多次檢查。首先,使用 array_key_exists()
檢查我們的數據庫中的文件路徑是否出現在 $files
中,如果沒有,則該文件必須已被刪除。其次,如果文件存在但哈希值不匹配,則該文件必須已被更改或未更改。我們將每個檢查存儲到名為 $tmp
的臨時數組中,最後,如果 $files
中的數量大於數據庫中的數量,那麼我們就知道那些剩餘的未檢查文件已被添加。完成後,$diffs
要么是一個空數組,要么包含以多維數組形式找到的任何差異,這可能如下所示:
<code>Array ( [/var/www/test.php] => b6b7c28e513dac784925665b54088045cf9cbcd3 [/var/www/sub/hello.php] => a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa [/var/www/sub/world.php] => da39a3ee5e6b4b0d3255bfef95601890afd80709 )</code>
為了以更用戶友好的格式顯示結果(例如管理界面),您可以例如遍歷結果並以項目符號列表的形式輸出它們。
<?php $db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD); // 清除旧记录 $db->query("TRUNCATE integrity_hashes"); // 插入更新的记录 $sql = "INSERT INTO integrity_hashes (file_path, file_hash) VALUES (:path, :hash)"; $sth = $db->prepare($sql); $sth->bindParam(":path", $path); $sth->bindParam(":hash", $hash); foreach ($files as $path => $hash) { $sth->execute(); }
此時,您可以提供一個鏈接來觸發使用新的文件結構更新數據庫的操作(在這種情況下,您可能選擇將 $files
存儲在會話變量中),或者如果您不批准差異,您可以根據需要處理它們。
總結
希望本指南能幫助您更好地理解文件完整性監控。在您的網站上安裝此類內容是一種寶貴的安全措施,您可以放心,您的文件將完全按照您的意圖保持不變。當然,不要忘記定期備份。以防萬一。
(此處應保留原文的FAQ部分,因為該部分內容與代碼部分無關,屬於補充說明,不屬於偽原創的範疇)
以上是PHP主|監視文件完整性的詳細內容。更多資訊請關注PHP中文網其他相關文章!