网络安全日志记录
毫无疑问,对于保障网络系统安全而言,最重要的任务之一就是监控其活动。如今,大多数系统程序都与 syslogd 通信,记录重要事件(例如 su 请求)。此外,内核可以记录硬件故障和该级别的其他事件。当然,在监控可能的入侵或试图追踪入侵者用于破坏系统的路径时,日志可能是一种宝贵的资源。然而,显而易见的是,如果入侵者能够获得必要的权限,他们就可以操纵系统日志,从而完全愚弄任何追踪入侵的尝试,甚至达到难以发现入侵的极端程度。
多年前,当 syslogd 添加了通过网络将日志消息发送到另一台主机的能力时,朝着解决将系统日志保存在系统本身上的问题迈出了第一步。将所需的日志部分发送到另一台非常安全的主机(例如专用机器)实际上是解决问题的一半。使用像 UDP 这样的简单协议通过网络传输数据使得欺骗变得非常容易,从而使入侵者有可能添加恶意消息,甚至尝试创建拒绝服务攻击。此外,所有数据都以明文形式通过网络发送,使得嗅探变得简单,因此入侵者可以在尝试入侵之前获得有关系统的更多详细信息。
我们开发的解决此问题的方案是让标准 syslogd 工具使用安全外壳 (ssh) 包来转发日志,使用身份验证和加密,到另一台计算机。这样,我们想要记录的系统不需要特殊的更改,而将维护日志的系统也不需要运行另一个 syslogd。它将需要在普通用户帐户上运行 sshd(安全外壳守护程序)以及一些常用的系统工具。
我们将介绍两种技术上相似但概念上不同的方法,以及执行此重要任务的一些优点和缺点。第一种可以比作“推送”技术;也就是说,想要将其数据记录在外部机器上的机器实际上将强制数据发送到另一侧。第二种方法将使用“拉取”技术,因为远程客户端将尝试从所需的机器检索日志。
由于我们的解决方案使用标准的 syslog 工具来抓取消息,因此您需要一个支持将日志输出到命名管道的 syslog 守护程序。大多数最新的 syslog 守护程序都具有此功能(检查手册页以获得支持,或直接尝试)。非常重要的是,您的 syslogd 以非阻塞模式打开管道。
旧的 syslog 过去以阻塞模式打开管道,因此如果命名管道已满(在我们的上下文中,例如,如果连接中断了一段时间),那么整个 syslog 进程将停止,同时等待管道有一些可用空间。当然,这可能会产生严重的后果,因为所有日志记录,而不仅仅是远程日志记录,都将受到损害。作为检查您的 syslogd 是否正确处理管道的实用测试,配置整个系统,然后尝试写入管道直到达到最大尺寸,通常为 4KB。当连接断开时,检查 syslog 是否仍在记录某些内容,或者是否看起来已冻结。无论如何,我们建议获取您可以找到的最新版本(这也应该纠正其他错误)并将其安装在您的系统上(目前,最新版本是 1.3-31)。更高级的 syslog-ng 包也应该与我们的解决方案一起使用。除此之外,您还需要 Perl 解释器、来自 textutils 包的 tail 命令,当然还需要安全外壳包。
这种“推送”解决方案背后的理论非常简单。一个 Perl 脚本将在后台运行,并将(使用 ssh)连接到我们希望保留日志副本的主机。然后,Perl 脚本将监视我们将创建的命名管道,并将从管道中获得的任何内容通过安全连接发送到另一侧,在那里我们将把所有内容放入一个文件中。指示 syslog 将所需的日志事件发送到我们的命名管道将使工作完成。
让我们简要技术描述一下源代码(见列表 1)。脚本启动后,将打开一个本地日志(在 $local_log 中定义)以报告故障(可以通过取消定义变量来禁用此功能),然后它将尝试连接到远程主机(在 $host 中定义)。连接子程序打开到远程端的 ssh 命令,指定要使用的用户名,是否使用压缩以及是否建立静默连接。如您所见,ssh 将要求远程端执行给定文件的 cat,因此所有内容都将存储在那里以供进一步使用。连接后,脚本将打开我们创建的本地命名管道,然后将所有内容转发到另一侧,cat 命令将在那里存储它。当然,如果安全连接由于某种原因而断开(从技术上讲,如果文件句柄上的打印失败),脚本将尝试重新建立连接。
作为最后一个功能,脚本将在每个定义的时间间隔向另一端发送时间戳,这对于跟踪最后一个消息实际来自主机的时间(如果发生严重事件)可能很有用。实际上,脚本可以在每次通过网络发送内容时检测到连接已断开。通过向 FIFO(即先进先出管道)发送时间戳,它将自动触发检查连接在当时是否处于活动状态。
即使这看起来像是最容易完成的步骤,也有一些您必须注意的事项,以便发现远程日志记录工具的入侵者无法将其用作闯入日志记录机器的方式。首先,您必须使用 ssh-keygen 实用程序在您从中发送日志的网络机器上生成密钥对。您不必为此密钥对(应仅用于日志记录程序)设置密码(只需在提示符下按两次回车键),因此脚本不必在每次启动时都提示您输入密码。
使用 ssh2,您必须指定您将使用这个新创建的私钥作为识别您自己的可能密钥。通过添加以 IdKey 语句开头的新行,后跟密钥名称,将其添加到您的 ~/.ssh2/identification 文件中。(或者,您可以强制脚本中的 ssh 命令行执行此操作。)现在,您必须将公钥传输到您要存储日志的系统,并将其添加到该用户的授权密钥文件中。(通常在 ssh1 中为 ~/.ssh/authorized_keys,其中每行包含一个密钥。在 ssh2 中,将密钥复制到您的 ~/.ssh2 目录,然后将条目添加到您的授权文件中,默认情况下为 ~/.ssh2/authorization,使用通常的语法 Key key_filename。)这指示该机器上的 sshd 验证任何使用与该公钥配对的私钥的人的身份,而无需进一步检查。
根据 ssh 配置,您通常应该只需要使用这个新生成的密钥登录一次。在第一次连接时,ssh 将必须将新主机的密钥添加到您的 known_hosts 文件中(默认情况下,这不是自动任务);它将提示您确认您是否要添加密钥。注意授权和密钥文件权限:它们必须仅对用户可读写——首先是出于安全原因,但也因为 ssh 将拒绝接受配置不佳的密钥的连接。如果发生任何问题,请尝试以详细模式登录,因为这将准确告诉您为什么不允许连接。
可以使用该密钥通过 ssh 完成完整的登录。这将非常危险,因此最后您必须对密钥的使用施加一些重要的限制。首先,使用 from= 指令指定可以从哪些主机使用此密钥,因此,如果有人窃取了私钥,他也无法从您指定的以外的任何地方登录。其次,使用 no-pty 禁止分配 pty。最后,将此密钥的使用限制为仅附加到日志文件;也就是说,Perl 脚本在远程机器上执行的命令。这是使用 command= 指令完成的,应该是
command="cat >>
此时,您可以非常确定除了附加到日志文件之外,什么也做不了,并且您可以在某些机器上使用您的普通帐户作为日志存储器,因为如果日志密钥受到 ssh 的正确限制,这不会让您面临任何危险(除了您的空间被日志填满之外)。在 ssh2 中,您只需添加命令限制选项,在您的授权文件中使用 Command 指令。查看手册页以获取有关语法的确切信息。
由于许多 sshd 安装在允许连接之前会检查 TCP wrappers 文件,请确保您希望从中连接的主机被允许这样做。作为用户,请确保您没有来自您正在记录的机器的任何 .shosts 或 .rhosts 文件。如果存在,一切似乎都可以正常工作(因为授权将通过这些文件完成,如果守护程序允许),但系统将非常不安全。
我们必须做的第一件事是创建 syslogd 和我们的脚本之间通信所需的命名管道。这很容易;使用 mkfifo 命令,以命名管道的完整路径名作为参数来创建它。现在您必须告诉 syslogd 它应该将哪种类型的消息发送到该管道,使用标准的 syslogd 语法
log_facility: | /
通过网络发送通常的授权任务(auth.* 和 authpriv.*)、严重消息 (*.crit 和 *.emerg) 以及重要的内核网络相关消息(特别是如果您正在过滤数据包或类似操作)将是一个好主意。选择所有需要的工具以全面了解系统状态非常重要。当然,这因系统而异(使用的工具也可能是编译的,因此依赖于发行版),因此请务必彻底测试。
完成所有步骤后,您必须启动脚本,将标准错误和输出重定向到 /dev/null,这样就不会显示 ssh 错误。然后,一旦它运行,重新启动 syslogd,以便它将重新读取其配置。如果一切都已正确完成,您现在应该在远程机器上拥有日志,并且您应该将脚本放在您的启动文件中,以便它将在启动时启动。该程序可以使用普通用户权限运行;它不需要任何特殊权限。只需确保它可以从为与 syslogd 通信而创建的命名管道读取和写入,并最终可以读取和写入为本地错误日志记录选择的文件。
第二种方法基于检索数据,即“拉取”,而不是从 syslog 接收数据。在此解决方案中,一个 Perl 脚本将在我们要存储日志的额外副本的机器上运行,它将连接到我们要监视的机器并从中获取日志。此任务将仅通过在日志文件上使用 tail 命令来完成,因此添加到日志文件中的所有内容也将显示在我们这边。
如您所见(在列表 2 中),此脚本与第一个脚本非常相似。它具有使用 ssh 连接到远程主机的常用过程、保存配置的变量和一些本地日志记录功能。由于脚本在保存日志的额外副本的机器上运行,因此脚本的日志也将存储在这台机器上。连接打开后,脚本将继续从 ssh 文件句柄读取并将所有内容打印到标准输出,这很可能在启动时重定向到文件。正如我们调用它一样,ssh 将在日志文件上执行 tail,因此每次向日志文件添加内容时,它都会被发送过来。如果发生错误,则脚本将尝试重置连接并记录错误。
ssh 的配置与之前的非常相似,但当然角色互换了。实际上,这里我们将必须从额外的日志记录机器连接到我们要记录的机器,因此您将必须在额外的机器上创建密钥并将公钥复制到另一台机器上的授权密钥。所有其他限制和提示应以与以前相同的方式使用。请记住,您现在必须将要执行的命令设置为新命令;也就是说,Perl 脚本中由 $cmd 变量定义的 tail。
在此示例中,您不必更改 syslog 中的任何内容,因为您将在已有的日志文件上使用 tail。无论如何,我们强烈建议创建一个新的日志文件(使用常用语法),其中包含需要在另一台机器上存储的最重要内容。另一个小问题源于它会消耗磁盘空间。这很容易解决,因为您可以向您的 crontab 添加一个作业,该作业会不时删除该文件。请记住,如果日志文件已被删除并使用 touch 重新创建,则必须重新启动 syslog,因为它必须存在 syslog 才能使用它。如果您有最新版本的 textutils,那么 tail(带有 --retry 选项)将不会在意文件被删除(使用 rm 命令)并且将创建并打开一个新文件。如果您有不支持此选项的旧版本,您可以只 kill tail 命令(这很容易通过从 ps 管道到 grep 再到 kill 来完成),在与之前相同的 cron 中,然后 Perl 脚本将从远程端重新启动它。当然,我们强烈建议尽可能升级 textutils。
配置完成后,您只需启动脚本,将标准输出(和错误输出,但这不必要)重定向到将保存您的日志的文件。日志记录机器上的脚本不需要任何特殊权限。当然,日志文件必须始终可由实际运行远程脚本中通过 ssh 执行的 tail 命令的用户读取。这意味着,如果您要删除该文件,则每次都必须在 cron 作业中设置正确的所有权和模式。
两种方法对于远程日志记录都有效,并且几乎等效。我们想描述这两种方法,因为在某些环境中,一种方法可能更容易使用。例如,如果您在要存储日志的机器上没有运行 sshd,您只需在您的 home 中安装 ssh 客户端并使用第二种解决方案。此外,我们希望指出在通过网络通信中可能出现的一些有趣的问题。
第一种方法使用某种“实时”日志记录。每次 syslog 生成消息时,如果可能,它都会通过管道发送到另一侧。因此,一旦 syslog 生成某些内容,它就会被发送(在正常情况下,当然)并且无法以任何方式停止。对于这样的系统来说,这是一个很棒的功能,但会导致一般的进程通信问题:缓冲。由于用作 syslogd 和程序之间通信链接的管道以非阻塞模式打开并且具有有限的缓冲区大小,因此如果 syslog 生成数据的速度太快,则数据将会丢失。如果它可以以阻塞模式打开它,我们可以解决这个问题,但是如果管道在短时间内无法获得可用空间,该进程将只是冻结并丢失其他数据。这是一个有趣的实时通信问题。实际上,这不应该是一个问题,因为如果您明智地选择要通过网络发送的 syslog 工具,并且两台机器之间存在良好的连接,那么缓冲区不应如此快速地被填满。
作为解决这种可能的数据丢失的一种方法,第二个程序将从磁盘文件中读取日志,跳过缓冲问题(磁盘文件是增长的缓冲区,因此它没有如此严格的限制)。当然,这不是“实时”解决方案,因为 tail 必须检查文件是否在给定的时间段内发生更改,否则它将不断地使用 CPU 来检查更改;请参阅 info 文件中的 --sleep 间隔。因此,理论上在这个短暂的时间段内,恶意破解者应该找到您的程序正在运行并将其杀死(同样,非常不可能)。
Federico (drzeus@infis.univ.ts.it) 在乌迪内大学学习计算机科学。在不进行黑客攻击或编码时,他喜欢阅读科幻小说、听音乐和弹吉他。
Christian (chris@infis.univ.ts.it) 在的里雅斯特大学学习天体物理学,并兼职担任系统管理员和高中教师。在不玩 Linux 和其他有趣的软件或硬件时,他喜欢与女友讨论谁是有史以来最好的电影导演。