关键要点
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中文网其他相关文章!