精通-Linux-安全和加固第三版-全-

精通 Linux 安全和加固第三版(全)

原文:annas-archive.org/md5/af3066bad13b60c17037fd5d8701f2fc

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章:《掌握 Linux 安全与加固》第三版:保护您的 Linux 系统免受网络攻击的实用指南

欢迎使用 Packt Early Access。我们为您提供这本书的独家预览,您可以在正式发售之前提前了解内容。写一本书可能需要几个月的时间,但我们的作者今天就能与您分享最前沿的信息。Early Access 让您提前了解最新的发展,通过章节草稿的形式发布。虽然现在这些章节可能还有些粗糙,但我们的作者会随着时间的推移不断更新它们。

您可以随时翻阅本书,也可以从头到尾跟随阅读;Early Access 设计得非常灵活。我们希望您在了解 Packt 书籍写作过程的同时,也能享受阅读的乐趣。

  1. 第一章:在虚拟环境中运行 Linux

  2. 第二章:保护用户账户

  3. 第三章:使用防火墙保护您的服务器(第一部分)

  4. 第四章:使用防火墙保护您的服务器(第二部分)

  5. 第五章:加密技术

  6. 第六章:SSH 加固

  7. 第七章:掌握自主访问控制

  8. 第八章:访问控制列表与共享目录管理

  9. 第九章:通过 SELinux 和 AppArmor 实现强制访问控制

  10. 第十章:内核加固与进程隔离

  11. 第十一章:扫描、审计与加固

  12. 第十二章:日志记录与日志安全

  13. 第十三章:漏洞扫描与入侵检测

  14. 第十四章:使用 fapolicyd 阻止应用程序

  15. 第十五章:忙碌小蜜蜂的安全小贴士与技巧

第二章:1 在虚拟环境中运行 Linux

加入我们在 Discord 上的书籍社区

packt.link/SecNet

所以,你可能会问自己:为什么我要学习 Linux 安全?难道 Linux 不是已经很安全了吗?毕竟,它不像 Windows。但事实是,有很多原因。

确实,Linux 在安全性方面相较于 Windows 有一些优势。这些优势包括以下几点:

  • 与 Windows 不同,Linux 从一开始就被设计为多用户操作系统。因此,Linux 系统中的用户安全通常比其他系统要好一些。

  • Linux 提供了更好的管理用户与非特权用户之间的隔离。这使得入侵者更难进入系统,同时也使得用户不小心将恶意软件传染到 Linux 系统的可能性更小。

  • Linux 对病毒和恶意软件的感染比 Windows 更具抵抗力。某些 Linux 发行版自带内建机制,例如 Red Hat 及其免费克隆中的 SELinux,以及 Ubuntu 和 SUSE 中的 AppArmor,它们帮助防止入侵者控制系统。

  • Linux 是免费和开源的软件。这允许任何有能力审核 Linux 代码的人寻找漏洞或后门。

但即使有这些优势,Linux 和人类创造的其他一切一样。也就是说,它并不完美。

以下是我们将在本章中讨论的主题:

  • 观察威胁形势

  • 为什么每个 Linux 管理员都需要了解 Linux 安全

  • 关于威胁形势的一些介绍,以及攻击者在某些时候如何能够突破 Linux 系统的几个例子

  • 跟进 IT 安全新闻的资源

  • 物理、虚拟和云环境的差异

  • 在 VirtualBox 上设置 Ubuntu Server 和 Red Hat 类型的虚拟机,并在 Red Hat 类型虚拟机中安装 企业 Linux 附加包EPEL)库

  • 创建虚拟机快照

  • 在 Windows 主机上安装 Cygwin,以便 Windows 用户可以从其 Windows 主机连接到虚拟机

  • 使用 Windows 10/11 的 Bash shell 访问 Linux 系统

  • 如何保持你的 Linux 系统更新

让我们从谈论威胁开始。

观察威胁形势

如果你过去几年一直关注 IT 技术新闻,你可能至少看到过几篇关于攻击者入侵 Linux 服务器的文章。例如,虽然 Linux 并不容易感染病毒,但确实发生过几起攻击者在 Linux 服务器上植入其他类型恶意软件的案件。以下是一些例子:

  • 僵尸网络恶意软件:这使得服务器加入一个由远程攻击者控制的僵尸网络。一个比较著名的案例是将 Linux 服务器加入一个僵尸网络,并发起 拒绝服务DoS)攻击,攻击其他网络。

  • 勒索软件:这种软件旨在加密用户数据,直到服务器所有者支付赎金。但即使支付了赎金,也不能保证数据能够恢复。

  • 加密货币挖矿软件:这会导致安装该软件的服务器的 CPU 超负荷工作并消耗更多的能量。被挖掘的加密货币会进入攻击者的账户,这些攻击者是植入软件的人。

当然,也有许多安全漏洞并不涉及恶意软件,例如攻击者通过某种方式窃取用户凭证、信用卡数据或其他敏感信息。

一些安全漏洞是由于单纯的疏忽所致。这里有一个例子,说明一位粗心的 Adobe 管理员将公司的私密安全密钥放在了一个公开的安全博客上:arstechnica.com/information-technology/2017/09/in-spectacular-fail-adobe-security-team-posts-private-pgp-key-on-blog/

现在,让我们再多谈一些关于安全漏洞的事情。

为什么会发生安全漏洞?

无论你运行的是 Linux、Windows 还是其他操作系统,安全漏洞发生的原因通常都是相同的。它们可能是操作系统中的安全漏洞,或者是运行在该操作系统上的应用程序中的安全漏洞。通常,与漏洞相关的安全漏洞本可以通过管理员及时应用安全更新来避免。

另一个大问题是配置不当的服务器。一个标准的、开箱即用的 Linux 服务器配置实际上是相当不安全的,可能会引发大量问题。配置不当的服务器的一个原因是缺乏经过专业培训的人员来安全地管理 Linux 服务器。(当然,这对本书的读者来说是个好消息,因为——相信我——IT 安全领域的高薪工作是没有短缺的。)

现在,除了服务器和桌面上的 Linux 操作系统,我们还在物联网IoT)设备上使用 Linux。这些设备存在许多安全问题,很大一部分原因是人们根本不知道如何安全地配置它们。

在本书的旅程中,我们将学习如何以正确的方式做生意,使我们的服务器尽可能安全。我们可以做的一件事是跟进与安全相关的新闻。

跟进安全新闻

如果你从事 IT 行业,即使你不是安全管理员,你也会想要跟进最新的安全新闻。在互联网时代,做到这一点非常容易。

首先,有许多专门报道网络安全新闻的网站。例如 Packet Storm SecurityThe Hacker News。一些常规的技术新闻网站和 Linux 新闻网站,如 Ars TechnicaFudzillaThe RegisterZDNetLXer,也会报道网络安全漏洞的相关信息。而且,如果你更喜欢看视频而不是阅读,你会找到许多好的 YouTube 频道,例如 BeginLinux Guru

最后,不管你使用的是哪种 Linux 发行版,务必跟进你所使用的 Linux 发行版的新闻和当前文档。发行版维护者应该有一种方式,让你知道他们的产品中是否出现了安全问题。

这里有一些安全相关网站的链接:

你还可以查看一些通用的 Linux 学习资源以及 Linux 新闻网站:

阅读本书时需要时刻记住的一点是,唯一一个你见过的完全 100%安全的操作系统,将会是安装在永远不会开机的计算机上。

物理、虚拟和云环境之间的区别

为了让你能够进行实践操作,我将向你介绍虚拟机的概念。这仅仅是一种在一个操作系统内部运行另一个操作系统的方式。所以,不管你在主机上运行的是 Windows、macOS 还是 Linux,都没关系。无论如何,你都可以运行一个 Linux 虚拟机,用它进行练习,而且如果它被弄坏了,你也不必担心。

我们将使用的虚拟化软件是 Oracle 的 VirtualBox,它非常适合我们要做的工作。在企业环境中,你会发现有些虚拟化软件更适合用于数据中心。过去,服务器硬件只能一次做一件事,这意味着你必须为 DNS 运行一台服务器,为 DHCP 运行另一台,依此类推。如今,我们有了内存充足、硬盘空间大的服务器,每个 CPU 最多有 64 个核心。因此,在每台服务器上安装多个虚拟机,且每个虚拟机执行特定任务,变得更加便宜和方便。这也意味着你不仅需要关注托管这些虚拟机的物理服务器的安全性,还需要关注每个虚拟机的安全性。一个额外的问题是,你需要确保虚拟机之间正确地隔离,尤其是那些包含敏感数据的虚拟机。

然后,就是云计算了。许多不同的公司提供云服务,个人或公司可以在云端启动 Windows 实例或他们选择的 Linux 发行版。当在云服务上设置 Linux 发行版时,有些操作你需要立即做,以增强安全性。(这是我们在第六章SSH 加固中会讲解的内容。)并且要意识到,当你在云服务上设置服务器时,你会更关注适当的安全性,因为它会有一个连接到广阔互联网的接口。(你本地的服务器,除非是用于面向公众的,否则通常是与互联网隔离的。)

在我们介绍完基本材料后,接下来进入真正的核心内容,从介绍我们的虚拟化软件开始。

介绍 VirtualBox 和 Cygwin

每当我写作或讲解时,我都会尽力避免给学生提供一种安眠药式的内容。在本书中,只有在必要时才会涉及一些理论,但我更喜欢提供有用的实践信息。书中还会有大量的逐步操作实验,偶尔也会加入一些幽默的元素。

做实验的最佳方式是使用 Linux 虚拟机。我们所做的大多数工作都可以适用于任何 Linux 发行版,但我们也会做一些特定于Red Hat 企业版 LinuxRHEL)或 Ubuntu Linux 的事情。(RHEL 在企业使用中最为流行,而 Ubuntu 在云部署中最为流行。)SUSE 是第三大企业 Linux 发行版。我们不会做太多关于 SUSE 的内容,但偶尔会提到它的一些小特点。

Red Hat 是一家价值十亿美元的公司,因此它在 Linux 市场中的地位无可置疑。但由于 Ubuntu Server 是免费的,我们不能仅凭其母公司的价值来评判它的受欢迎程度。事实上,Ubuntu Server 是部署基于云的应用程序最广泛使用的 Linux 发行版。

详情请见此处:www.zdnet.com/article/ubuntu-linux-continues-to-dominate-openstack-and-other-clouds/

由于 Red Hat 是收费产品,我们将使用 CentOS 7、AlmaLinux8 和 AlmaLinux9,这些都是基于 Red Hat 源代码构建的,并且是免费的。(我们会使用这三个发行版,因为它们之间有一些差异,并且它们都会在相当长的时间内得到支持。)

对于 Ubuntu,我们将集中讨论 22.04 版本,因为它是最新的 长期支持 (LTS) 版本。(我们也会偶尔看一下 Ubuntu 20.04,因为它仍然得到支持,并且它与 22.04 之间存在一些差异。)新的 LTS 版本会在每个偶数年份的 4 月发布,而非 LTS 版本会在每个奇数年份的 4 月和每年 10 月发布。对于生产环境,你主要需要坚持使用 LTS 版本,因为非 LTS 版本有时可能会遇到一些问题。

有多种虚拟化平台可供选择,但我个人的首选是 VirtualBox。

VirtualBox 适用于 Windows、Linux 和 Mac 主机,且所有平台均免费提供。(它也可以用于 Solaris 主机,但我怀疑你们当中大多数人不会运行这个系统。)它具有在其他平台上需要付费的功能,比如创建虚拟机快照的能力。

我们将进行的一些实验需要你模拟从主机连接到远程 Linux 服务器。如果你的主机是 Linux 或 Mac 机器,你只需要打开终端并使用内置的 安全外壳(SSH) 工具。如果你的主机运行的是 Windows,你需要安装某种 Bash shell,例如 Cygwin,或者直接使用 Windows 10/11 Pro 中自带的 Bash shell。

在 VirtualBox 中安装虚拟机

对于从未使用过 VirtualBox 的你们,以下是一个快速入门指南:

  1. 下载并安装 VirtualBox 和 VirtualBox 扩展包。你可以从 www.virtualbox.org/ 获取它们。

  2. 下载 Ubuntu Server 22.04、CentOS 7、AlmaLinux8 和 AlmaLinux9 的安装 .iso 文件。你可以从 ubuntu.com/almalinux.org/www.centos.org/ 获取它们。

  3. 启动 VirtualBox 并点击屏幕顶部的 新建 图标。根据提示填写信息。将虚拟磁盘大小增加到 20 GB,但保留其他所有默认设置,如下所示:

    B19501_01_01.png

    B19501_01_01.png

  4. 启动新虚拟机。点击位置对话框旁边的文件夹图标,导航到存储下载的 .iso 文件的目录。选择 Ubuntu ISO 文件、CentOS ISO 文件或 AlmaLinux ISO 文件,如下图所示。(如果 ISO 文件没有显示在列表中,点击左上角的 添加 按钮来添加它。)

    B19501_01_02.png

    B19501_01_02.png

  5. 点击对话框中的 开始 按钮以开始安装操作系统。请注意,对于 Ubuntu 服务器,你将不会安装桌面界面。对于 CentOS 7 虚拟机,你可以根据需要选择 KDE 桌面或 GNOME 桌面。对于 AlmaLinux,你的唯一桌面选择是 GNOME。(我们将至少进行一次需要桌面界面的 AlmaLinux 机器操作。)

  6. 安装 Ubuntu 时,来到此屏幕时选择 试用或安装 Ubuntu 服务器

    B19501_01_03.png

    B19501_01_03.png

  7. 对其他 Linux 发行版重复该过程。

  8. 使用以下两个命令更新 Ubuntu 虚拟机:

sudo apt update 
sudo apt dist-upgrade
  1. 暂时不要更新 CentOS 和 AlmaLinux 虚拟机,因为我们将在下一个练习中进行更新。

  2. 对于 Ubuntu,在 SSH 设置 屏幕上选择安装 OpenSSH 服务器

安装 Ubuntu 时,系统会要求你为自己创建一个普通用户帐户和密码。它不会要求你创建 root 用户密码,而是会自动将你添加到 sudo 组中,以便你拥有管理员权限。

当你到达 CentOS 或 AlmaLinux 安装程序的用户帐户创建屏幕时,请确保勾选 将此用户设置为管理员,因为默认情况下此选项是未勾选的。系统会提供给你创建 root 用户密码的机会,但这是完全可选的。(事实上,我从不设置 root 密码。)

AlmaLinux 9 安装程序的用户帐户创建屏幕—与 CentOS 7 和 AlmaLinux 8 的屏幕相同—如下所示:

B19501_01_04.png

B19501_01_04.png

对于 Ubuntu 22.04,你会看到一个简洁的屏幕,设置你的真实姓名、用户名和密码。Ubuntu 安装程序会自动将你的用户帐户添加到 sudo 组,从而赋予你完全的管理员权限。

这是 Ubuntu 22.04 的用户帐户创建屏幕:

B19501_01_05.png

B19501_01_05.png

现在,让我们转到 CentOS 7。

在 CentOS 7 虚拟机上安装 EPEL 仓库

虽然 Ubuntu 软件包仓库几乎包含了你在本课程中所需的一切,但 CentOS 和 AlmaLinux 的软件包仓库——可以说——相对匮乏。为了在 CentOS 和 AlmaLinux 的动手实验中获得所需的软件包,你需要安装 EPEL 仓库。(EPEL 项目由 Fedora 团队管理。)当你在 Red Hat 7 和 CentOS 7 系统上安装第三方仓库时,还需要安装优先级包并编辑 .repo 文件,以为每个仓库设置正确的优先级。这将防止来自第三方仓库的软件包覆盖官方 Red Hat 和 CentOS 软件包,即使它们恰好拥有相同的名称。以下步骤将帮助你安装所需的软件包并编辑 .repo 文件:

  1. 安装 EPEL 所需的两个软件包已经包含在 CentOS 7 的常规仓库中。要安装它们,只需运行以下命令:
sudo yum install yum-plugin-priorities epel-release
  1. 安装完成后,导航到 /etc/yum.repos.d 目录,并在你喜欢的文本编辑器中打开 CentOS-Base.repo 文件。在 baseupdatesextras 部分的最后一行后,添加 priority=1。在 centosplus 部分的最后一行后,添加 priority=2。保存文件并关闭编辑器。你编辑过的每个部分应该看起来像这样,除了适当的名称和优先级号码:
 [base] 
  name=CentOS-$releasever - Base 
  mirrorlist=http://mirrorlist.centos.org/? 
  release=$releasever&arch=$basearch&repo=os&infra=$infra 
  #baseurl=http://mirror.centos.org/centos/ 
  $releasever/os/$basearch/ 
  gpgcheck=1 
  gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 
  priority=1
  1. 打开 epel.repo 文件进行编辑。在 epel 部分的最后一行后,添加 priority=10。在每个剩余部分的最后一行后,添加 priority=11

  2. 更新系统后,运行以下命令以创建已安装和可用软件包的列表:

sudo yum upgrade 
sudo yum list > yum_list.txt

现在,让我们继续进行 AlmaLinux 的设置。

在 AlmaLinux 8/9 虚拟机上安装 EPEL 仓库

要在 AlmaLinux 上安装 EPEL 仓库,你只需运行以下命令:

sudo dnf install epel-release

由于在 CentOS 7 及更早版本中没有优先级包,因此我们不必担心配置仓库的优先级。

当软件包安装完成后,使用以下两个命令更新系统并创建可用软件包列表:

sudo dnf upgrade 
sudo dnf list > dnf_list.txt

接下来,我们来配置网络。

配置 VirtualBox 虚拟机的网络

我们的一些培训场景将要求你模拟连接到远程服务器。你可以通过使用主机机器连接到虚拟机来完成此操作。当你第一次在 VirtualBox 中创建虚拟机时,网络模式设置为NAT。为了从主机连接到虚拟机,你需要将虚拟机的网络适配器设置为桥接适配器模式。以下是你可以执行此操作的步骤:

  1. 关闭你已创建的任何虚拟机。

  2. VirtualBox 管理器界面中,打开虚拟机的设置对话框。

  3. 点击 Network 菜单项。将 Attached to 设置从 NAT 更改为 Bridged Adapter,并将 Promiscuous Mode 设置为 Allow All,如此截图所示:

    B19501_01_06.png

    B19501_01_06.png

  4. 重新启动虚拟机并设置其使用静态 IP 地址。

提示:

如果您从子网范围的高端分配静态 IP 地址,则更容易避免与从您的互联网网关分配的低编号 IP 地址发生冲突。

使用 VirtualBox 创建虚拟机快照

与虚拟机一起工作的美妙之处之一是,如果出现问题,您可以创建一个快照并回滚到它。使用 VirtualBox,这很容易做到,只需按照以下步骤操作:

  1. VirtualBox 管理器 屏幕的 Machine 菜单中,选择 Tools/Snapshots

  2. 在屏幕右侧更进一步,点击 Take 图标以打开快照对话框。要么填写所需的 Snapshot Name,要么接受默认名称。可选地,您可以创建一个描述,如此截图所示:

B19501_01_07.png

B19501_01_07.png

当您对虚拟机进行更改后,可以通过关闭虚拟机,然后突出显示 快照名称 并点击 恢复 按钮来回滚到快照。

使用 Cygwin 连接到您的虚拟机

如果您的主机机器是 Linux 或 Mac,您只需打开主机的终端并使用那些已经存在的工具来连接虚拟机。Windows 10 和 Windows 11,即使在基础 Home 版本中,现在都带有内置的 Secure Shell 客户端,分别内置于普通的 命令提示符PowerShell 中,您可以选择使用它。但如果您希望使用更接近实际 Linux 经验的工具,您可以考虑使用 Cygwin。

Cygwin,红帽公司的项目,是一个专为 Windows 构建的免费开源 Bash shell。它是免费的,易于安装。

在您的 Windows 主机上安装 Cygwin

这里有一个快速入门使用 Cygwin 的方法:

  1. 在您主机机器的浏览器中,从 www.cygwin.com/ 下载适合您 Windows 版本的适当的 setup*.exe 文件。

  2. 双击安装程序图标开始安装。在大多数情况下,只需接受默认设置,直到您到达 包选择 屏幕。 (唯一的例外是您选择下载镜像的屏幕。)

  3. 包选择 屏幕的顶部,从 View 菜单中选择 Category

  4. 展开 Net 类别,如下截图所示:

    B19501_01_08.png

    B19501_01_08.png

  5. 向下滚动直到看到 openssh 包。在 New 列下,点击 Skip(这将导致 Skip 的位置显示版本号),如此截图所示:

    B19501_01_09.png

    B19501_01_09.png

  6. 选择正确的包之后,你的屏幕应该是这样的:

    B19501_01_10.png

    B19501_01_10.png

  7. 在右下角,点击下一步。如果弹出解析依赖关系的屏幕,点击该屏幕上的下一步

  8. 保留你下载的安装文件,因为稍后你会用它来安装更多的软件包或更新 Cygwin。(当你打开 Cygwin 时,任何更新的软件包都会出现在查看菜单中的待处理视图里。)

  9. 一旦你从 Windows 开始菜单打开 Cygwin,你可以根据需要调整其大小,并使用 Ctrl + + 或 Ctrl + - 键组合来调整字体大小。

接下来,我们将查看 Windows 10/11 中的 Bash shell。

使用 Windows 10 SSH 客户端与 Linux 虚拟机交互

如果你使用的是 Windows 10,你的操作系统中已经内建了 SSH 客户端。

那么,让我们看看如何操作:

  1. 要访问它,你可以从Windows 系统菜单中打开传统的命令提示符,如下所示:

    B19501_01_11.png

    B19501_01_11.png

  2. 然后,像在 Mac 或 Linux 机器上那样,输入 SSH 命令:

    B19501_01_12.png

    B19501_01_12.png

  3. 更好的选择是使用Windows****PowerShell,而不是普通的命令提示符。按照如下方式进入:

    B19501_01_13.png

    B19501_01_13.png

  4. 如前所述,让我们用它来登录到我的 Orange Pi 设备,正如你在这里看到的:

B19501_01_14.png

B19501_01_14.png

如果可以选择,建议使用PowerShell而不是命令提示符PowerShell 更接近于 Linux 的 Bash shell 体验,你会因此更加满意。

使用 Windows 11 SSH 客户端与 Linux 虚拟机交互

在 Windows 11 中,你的操作方式和 Windows 10 一样,唯一的区别是命令提示符PowerShell的菜单位置不同。命令提示符现在在主菜单中有了自己的终端项,而PowerShell则位于Windows 工具子菜单下。Windows 11 还新增了第三个选项,即内建的 Ubuntu 虚拟机,你会在任务栏底部看到它的图标。

Cygwin 与 Windows shell 的对比

Cygwin 和 Windows 10/11 内建的 SSH 客户端各有优缺点。支持 Cygwin 的优点是,你可以安装多种软件包,几乎可以按你的需求自定义它。另外,Cygwin 会将 SSH 的 known_hosts 和密钥文件存储在用户主目录的 .ssh 目录中,如果你习惯于使用 Linux,这里是你应该找到这些文件的位置。如果你使用的是 Windows 内建的 SSH 客户端,你需要在其他位置查找这些文件。

支持 Windows 10/11 内建 SSH 客户端的理由之一就是它已经预装了。而且,如果你需要访问普通的 Windows 文件夹,它比 Cygwin 更容易使用,因为 Cygwin 会把你困在它自己的沙盒目录结构中。

保持 Linux 系统更新

花些时间浏览常见漏洞和暴露数据库,你很快就会明白为什么保持系统更新如此重要。没错,你甚至会发现,我们心爱的 Linux 也有安全漏洞,正如你在这里看到的:

B19501_01_15.png

B19501_01_15.png

更新 Linux 系统只需要一两个简单的命令,通常比更新 Windows 系统更快且不那么痛苦。

你可以在这里找到常见漏洞和暴露数据库:

cve.mitre.org/

所有你们这些有责任心、敬业的 Linux 管理员,肯定都想熟悉这个网站。

接下来,我们来看看如何更新基于 Debian 的系统,包括 Ubuntu。

更新基于 Debian 的系统

让我们来看看如何更新基于 Debian 的系统:

  1. 在 Debian 及其众多衍生版本(包括 Ubuntu)上,运行两个命令,如下所示:
sudo apt update 
sudo apt dist-upgrade
  1. 偶尔,你还需要删除一些不再需要的旧软件包。你怎么知道呢?很简单。当你登录系统时,命令行上会出现一条消息。要删除这些旧软件包,只需运行这个命令:
sudo apt auto-remove

接下来,我们将为 Ubuntu 配置自动更新。

为 Ubuntu 配置自动更新

当你首次安装 Ubuntu 22.04 时,自动更新默认是开启的。为了验证这一点,你首先需要检查 unattended-upgrades 服务的状态,像这样:

donnie@ubuntu2204-packt:~$ systemctl status unattended-upgrades
● unattended-upgrades.service - Unattended Upgrades Shutdown
     Loaded: loaded (/lib/systemd/system/unattended-upgrades.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2022-10-08 19:25:54 UTC; 52min ago
. . .
. . .
donnie@ubuntu2204-packt:~$

然后,查看/etc/apt/apt.conf.d/20auto-upgrades文件。如果启用了自动更新,你会看到如下内容:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

不过,我必须承认,我对此有些复杂的感受。我的意思是,虽然安全更新能够在我不做任何事情的情况下自动安装,这很好,但很多更新需要系统重启后才能生效。默认情况下,Ubuntu 系统在安装更新后不会自动重启。如果你保持默认设置,当你登录系统时,会看到一个关于此的消息。但如果你愿意,你可以设置 Ubuntu 在自动更新后自动重启。方法如下:

  1. 进入/etc/apt/apt.conf.d目录,并使用你喜欢的文本编辑器打开50unattended-upgrades文件。在大约67行的位置,你会看到一行内容:
//Unattended-Upgrade::Automatic-Reboot "false";
  1. 通过删除前导斜杠来取消注释这一行,并将false改为true,像这样:
Unattended-Upgrade::Automatic-Reboot "true";
  1. 在这个新配置下,Ubuntu 现在将在自动更新过程完成后立即重新启动。如果你更希望在特定的时间重启机器,请向下滚动到大约103行,你会看到以下内容:
//Unattended-Upgrade::Automatic-Reboot-Time "02:00";
  1. 由于这一行被其前导的双斜杠注释掉了,它目前没有任何效果。要让机器在早上 2:00 重新启动,只需取消注释这一行。要让它在比如晚上 10:00 重启,取消注释这一行并将时间改为22:00,像这样:
Unattended-Upgrade::Automatic-Reboot-Time "22:00";

当然,有一个基本的原则是,你不应在生产系统上安装系统更新,而不先在测试系统上进行测试。任何操作系统供应商偶尔都可能提供有问题的更新,而这也包括 Ubuntu。(我知道你在说什么:说得对,Donnie。)Ubuntu 的自动更新功能与这一基本原则直接冲突。如果启用了自动更新,禁用它其实很简单,如果你选择这么做的话。

  1. 要禁用自动更新,只需进入/etc/apt/apt.conf.d目录,并用你喜欢的文本编辑器打开20auto-upgrades文件。你会看到如下内容:
APT::Periodic::Update-Package-Lists "1"; 
APT::Periodic::Unattended-Upgrade "1";
  1. 将第二行的参数更改为 0,这样文件看起来就像这样:
APT::Periodic::Update-Package-Lists "1"; 
APT::Periodic::Unattended-Upgrade "0";

现在,系统仍然会检查更新,并在登录屏幕上显示有更新时的消息,但不会自动安装它们。当然,应该说得很明显,你需要定期检查你的系统,以查看是否有更新可用。如果你确实希望保持自动更新启用,确保启用自动重启,或者每周至少登录系统几次,看看是否需要重启。

  1. 如果你想查看是否有任何与安全相关的更新可用,但不想看到任何非安全更新,可以使用unattended-upgrade命令,像这样:
sudo unattended-upgrade --dry-run -d
  1. 若要手动安装与安全相关的更新,而不安装非安全更新,只需运行:
sudo unattended-upgrade -d

如果你在一个每次使用后都会关机的工作站上运行桌面版 Ubuntu,你可以根据需要启用自动更新,但无需启用自动重启。

此外,如果你运行的是非 Ubuntu 版本的 Debian(包括树莓派的 Raspbian),你可以通过安装unattended-upgrades软件包,使其具备与 Ubuntu 相同的功能。只需运行此命令:

sudo apt install unattended-upgrades

你也可以使用apt命令只安装安全更新,但这需要将apt的输出通过一系列复杂的文本过滤器进行管道处理,以便屏蔽非安全更新。使用unattended-upgrade命令要简单得多。

我之前说过,我们应该总是在测试系统上测试更新,之后再在生产系统上安装它们,这一点对于企业服务器来说确实是成立的。但当我们有一大堆物联网设备需要保持更新时,尤其是当这些设备分布在各地、甚至是消费者设备中时,我们该怎么办呢?

在神奇的物联网世界中,适用于各种 Pi 设备(包括无处不在的树莓派)的最流行 Linux 发行版是 ARM CPU 版本的 Ubuntu、Raspbian 和 Debian。如果你在现场和消费设备中有大量物联网设备,一旦它们被部署或售出,你可能无法直接控制它们。它们仍然需要保持更新,因此设置无人值守更新并自动重启无疑会带来便利。但请记住,在物联网世界中,我们不仅要关注安全性,还要关注安全问题。例如,如果你有设备被设置为某种关键的、安全相关的工业控制器,那么在执行自动更新后,你很可能不希望设备自动重启。但如果你是安装 Linux 的智能电视供应商,那么肯定应该设置它们自动更新并在更新后自动重启。

接下来,让我们来看一下更新 RHEL 7 系统。

更新基于 Red Hat 7 的系统

对于基于 Red Hat 的系统,包括 CentOS 和 Oracle Linux,在安装过程中没有自动更新机制。因此,在默认配置下,你需要自己执行更新:

  1. 要更新一个基于 Red Hat 7 的系统,只需运行以下命令:
sudo yum upgrade
  1. 有时候,你可能只想查看是否有任何与安全相关的更新准备好安装。可以通过运行以下命令来做到这一点:
sudo yum updateinfo list updates security
  1. 如果有任何安全更新可用,你将在命令输出的最后看到它们。我刚刚测试的系统中,只有一个安全更新可用,内容如下:
FEDORA-EPEL-2019-d661b588d2 Low/Sec. nagios-common-4.4.3-1.el7.x86_64 

updateinfo list done
  1. 如果你只想安装安全更新,运行以下命令:
sudo yum upgrade --security
  1. 现在,假设你需要一个 CentOS 系统自动更新自己。你很幸运,因为有一个专门的包来实现这一点。安装并启用它,然后通过运行以下两条命令来启动它:
sudo yum install yum-cron 

sudo systemctl enable --now yum-cron
  1. 要进行配置,进入/etc/yum目录,编辑yum-cron.conf文件。在文件顶部,你将看到如下内容:
[commands] 
# What kind of update to use: 
# default = yum upgrade 
# security = yum --security upgrade 
# security-severity:Critical = yum --sec-severity=Critical upgrade 
# minimal = yum --bugfix update-minimal 
# minimal-security = yum --security update-minimal 
# minimal-security-severity:Critical = --sec-severity=Critical update-minimal 
update_cmd = default

这列出了我们可以执行的各种类型的升级。最后一行显示我们已经设置好更新所有内容。

  1. 假设你只希望自动应用安全更新。只需将最后一行更改为以下内容:
update_cmd = security
  1. 1520行,你会看到如下内容:
download_updates = yes 
apply_updates = no

这表示默认情况下,yum-cron只设置为自动下载更新,但不进行安装。

  1. 如果你希望自动安装更新,将apply_updates参数更改为yes

    请注意,不像 Ubuntu,CentOS 没有设置让系统在更新后自动重启。

  2. 最后,让我们来看一下yum-cron的邮件设置,你可以在yum-cron.conf文件的4857行找到,如下所示:

[email] 
# The address to send email messages from. 
# NOTE: 'localhost' will be replaced with the value of system_name. 
email_from = root@localhost 

# List of addresses to send messages to. 
email_to = root 

# Name of the host to connect to to send email messages. 
email_host = localhost

如你所见,email_to = 行被设置为将消息发送给 root 用户账户。如果你想接收来自你自己账户的消息,只需在这里更改。

  1. 要查看消息,你需要安装一个邮件阅读程序,如果尚未安装。(如果你在安装操作系统时选择了最小安装,则未安装该程序。)你最好的选择是安装mutt,如下所示:
sudo yum install mutt
  1. 当你打开mutt并查看一条消息时,你将看到类似这样的内容:

    B19501_01_16.png

    B19501_01_16.png

  2. 与所有操作系统一样,某些更新将要求系统重启。那么,如何知道系统何时需要重启呢?当然是使用needs-restarting命令。不过,首先,你需要确保系统中安装了needs-restarting。可以通过以下命令来做到这一点:

sudo yum install yum-utils

一旦安装了该软件包,你可以通过三种方式使用needs-restarting。如果仅运行该命令而不加任何选项开关,你将看到需要重启的服务和要求重启计算机的包。你还可以使用-s-r选项,如下所示:

命令 解释
sudo needs-restarting 这个命令显示需要重启的服务,以及系统可能需要重启的原因。
sudo needs-restarting -s 这个命令仅显示需要重启的服务。
sudo needs-restarting -r 这个命令仅显示系统需要重启的原因。

接下来,我们将更新基于 Red Hat 8/9 的系统。

更新基于 Red Hat 8/9 的系统

旧版的yum工具已经存在了几乎永远,它是一个出色、勤奋的工具。但它确实有时会出现一些小问题,并且在某些时候会非常慢。不过,别担心。Red Hat 的英雄们终于做出了改变,通过用dnf替代了yum。所以,当你使用你的 AlmaLinux 8/9 虚拟机时,你将使用dnf而不是yum。让我们来看看如何操作。

  1. 大多数情况下,你使用dnf的方式与使用yum是一样的,使用相同的参数和选项。例如,要进行系统升级,只需执行:
sudo dnf upgrade
  1. yumdnf之间的主要功能差异在于,dnf具有不同的自动更新机制。现在,你不再安装yum-cron包,而是安装dnf-automatic包,如下所示:
sudo dnf install dnf-automatic
  1. /etc/dnf目录中,你会看到automatic.conf文件,配置方法与 CentOS 7 中的yum-cron.conf文件相同。与旧版的yum-cron作为定时任务运行不同,dnf-automatic是通过systemd定时器来工作的。当你第一次安装dnf-automatic时,定时器是禁用的。你需要通过运行以下命令来启用并启动它:
sudo systemctl enable --now dnf-automatic.timer
  1. 使用以下命令来验证它是否正在运行:
sudo systemctl status dnf-automatic.timer
  1. 如果它启动成功,你在检查状态时应该会看到类似这样的内容:
[donnie@redhat-8 ~]$ sudo systemctl status dnf-automatic.timer 
 dnf-automatic.timer - dnf-automatic timer 
   Loaded: loaded (/usr/lib/systemd/system/dnf-automatic.timer; enabled; vendor preset: disabled) 
   Active: active (waiting) since Sun 2019-07-07 19:17:14 EDT; 13s ago 
  Trigger: Sun 2019-07-07 19:54:49 EDT; 37min left 

Jul 07 19:17:14 redhat-8 systemd[1]: Started dnf-automatic timer. 
[donnie@redhat-8 ~]$

要确定系统是否需要重启,只需安装yum-utils包并运行needs-restarting命令,和 CentOS 7 中的做法一样。(出于某种原因,Red Hat 开发人员从未将包名称更改为dnf-utils。)

想了解更多关于dnf-automatic的细节,只需输入:

man dnf-automatic

就是这些了。

自动更新听起来是件好事,对吧?嗯,在某些情况下确实如此。在我自己的 Linux 工作站上,我总是喜欢关闭它。因为每当我想安装一个软件包时,系统总是告诉我必须等更新过程完成才能继续。这让我非常烦躁。在企业环境中,在某些情况下禁用自动更新可能也是可取的,这样管理员可以更好地控制更新过程。

在企业环境中进行更新时,有一些特殊的考虑因素。接下来我们来看一下这些问题。

在企业中管理更新

当你首次安装任何 Linux 发行版时,它会配置为访问其自己的软件包仓库。这允许用户直接从这些常规发行版仓库安装软件包和更新。这对于家庭或小型企业使用非常好,但对于企业环境来说就不太合适。

在企业环境中,还有两个额外的考虑因素:

  • 你需要限制最终用户可以安装的软件包。

  • 在允许更新安装到生产网络之前,你总是需要先在一个独立的测试网络上进行测试。

出于这些原因,企业通常会设置自己的仓库服务器,只包含经过批准的软件包和更新。网络中的所有其他机器将配置为从这些仓库拉取软件包和更新,而不是从常规的发行版仓库中拉取。(这里我们不讨论如何设置本地仓库服务器,因为这个话题更适合放在 Linux 管理书籍中。)

Ubuntu 一直是最具创新性的 Linux 发行版之一,但它也曾面临过不少质量控制问题。在早期,至少有一个 Ubuntu 更新完全破坏了操作系统,用户需要重新安装操作系统。所以,是的,在任何关键任务环境中,更新之前一定要先进行测试。

我认为这就是我们引言章节的全部内容了。让我们来总结一下,好吗?

总结

我们已经在 Linux 安全性和强化的旅程上迈出了良好的第一步。在这一章中,我们讨论了了解如何保护和强化 Linux 系统与了解如何保护和强化 Windows 系统一样重要的原因。我们提供了一些关于配置不当的 Linux 系统可能如何被攻破的例子,并提到学习 Linux 安全性对你的职业生涯可能有所帮助。接着,我们讨论了一些在将 Linux 服务器设置为虚拟机或云服务时需要考虑的特殊事项。然后,我们介绍了如何使用 VirtualBox、Cygwin 和 Windows 10/11 shell 设置虚拟化实验室环境。最后,我们简要回顾了如何保持 Linux 系统更新。

在下一章中,我们将探讨如何锁定用户账户,确保错误的人永远无法获得管理员权限。我们下次见。

问题

  1. 因为 Linux 的设计比 Windows 更加安全,我们不必担心 Linux 安全问题。

    1. 正确

    2. 错误

  2. 以下哪个关于 IoT 设备上 Linux 的说法是正确的?

    1. 它们太多了。

    2. 它们正在接管世界。

    3. 它们中的太多配置不安全。

    4. 它们的配置如此安全,以至于会让安全从业者失业。

  3. 以下哪个关于企业中自动操作系统更新的说法是正确的?

    1. 你应该始终保持它们启用。

    2. 它们违反了在生产网络安装更新之前,必须先在测试网络上进行测试的基本原则。

    3. 与手动更新不同,自动更新后你无需重新启动系统。

    4. 对于 IoT 设备,启用自动更新没有太大意义。

进一步阅读

这里有一些方便的资源供你参考:

第三章:2 用户账户安全

加入我们的书籍社区,和我们一起在 Discord 上讨论

packt.link/SecNet

用户管理是 IT 管理中较为具有挑战性的方面之一。你需要确保用户能够始终访问他们的文件,并且能够执行完成工作所需的任务。你还需要确保用户的文件始终受到未授权用户的保护,并且用户不能执行与他们工作描述不符的任务。这是一个艰巨的任务,但我们旨在证明这是可以做到的。在本章中,我们将讨论如何锁定用户账户和用户凭证,以保护它们免受攻击者和窥探者的侵害。我们还将探讨如何防止用户获得超出其工作所需权限的特权。

本章涵盖的具体内容如下:

  • 以 root 用户登录的危险

  • 使用sudo的优势

  • 为完全管理员用户和仅具有某些委派权限的用户设置sudo权限

  • 使用sudo的高级技巧

  • 锁定用户的主目录

  • 强制执行强密码标准

  • 设置并强制执行密码和账户过期规则

  • 防止暴力破解密码攻击

  • 锁定用户账户

  • 设置安全横幅

  • 检测被破坏的密码

  • 理解中央用户管理系统

以 root 用户登录的危险

Unix 和 Linux 操作系统相对于 Windows 的一个巨大优势在于,Unix 和 Linux 在将特权管理员账户与普通用户账户分离方面做得更好。事实上,旧版本的 Windows 容易受到安全问题的攻击,例如病毒感染,就是因为常常设置了具有管理员权限的用户账户,而没有像新版 Windows 那样启用用户帐户控制(UAC)。 (即使有了 UAC,Windows 系统仍然会受到感染,只是频率没有那么高。)在 Unix 和 Linux 中,要感染一个配置正确的系统要困难得多。

你可能已经知道,Unix 或 Linux 系统中无所不能的管理员账户是 root 账户。如果你以 root 用户身份登录,你可以对系统做任何你想做的事情。所以你可能会想,“是的,这很方便,所以我就这么做。”然而,始终以 root 用户登录可能会带来一堆安全问题。考虑以下情况,以 root 用户登录可能会带来以下风险:

  • 让你更容易意外执行对系统造成损害的操作

  • 让其他人更容易执行对系统造成损害的操作

因此,如果你总是以 root 用户身份登录,或者即使你只是让 root 用户账户变得容易访问,可以说你在为攻击者和入侵者做了大部分的工作。而且,想象一下,如果你是一个大型公司的 Linux 系统管理员,而允许用户执行管理任务的唯一方式是将 root 密码提供给所有人。如果其中一个用户离开了公司,你肯定不希望该用户依然能登录到系统,所以你不得不更改密码并将新密码分发给其他所有用户。那么如果你只希望用户对某些任务拥有管理员权限,而不是拥有完整的 root 权限怎么办?

我们需要的是一种机制,使用户能够执行管理任务,而无需承担一直以 root 用户身份登录的风险,并且该机制还允许用户仅拥有完成某项工作所需的最小管理员权限。在 Linux 和 Unix 系统中,我们有这种机制,形式就是 sudo 工具。

使用 sudo 的优势

如果使用得当,sudo 工具可以极大增强系统的安全性,并且能让管理员的工作变得更加轻松。使用 sudo 后,你可以执行以下操作:

  • 为某些用户分配完整的管理员权限,同时为其他用户分配执行与其工作直接相关的任务所需的权限。

  • 允许用户通过输入自己正常的用户密码来执行管理任务,这样你就不必把 root 密码分发给每个人。

  • 增加入侵者突破你系统的难度。如果你启用了 sudo 并禁用了 root 用户账户,那么潜在的入侵者就无法知道该攻击哪个账户,因为他们无法知道哪个账户具有管理员权限。

  • 创建可以在整个企业网络中部署的 sudo 策略,即便这个网络包含了混合的 Unix、BSD 和 Linux 系统。

  • 提升你的审计能力,因为你将能够看到用户在使用其管理员权限时的操作。

关于上面最后一点,请参考我 CentOS 7 虚拟机的 secure 日志中的以下片段:

Sep 29 20:44:33 localhost sudo: donnie : TTY=pts/0 ; PWD=/home/donnie ; 
USER=root ; COMMAND=/bin/su - 
Sep 29 20:44:34 localhost su: pam_unix(su-l:session): session opened for 
user root by donnie(uid=0) 
Sep 29 20:50:39 localhost su: pam_unix(su-l:session): session closed for 
user root 

你可以看到我使用了 su - 登录到 root 命令提示符,然后又退出了。在我登录的过程中,我执行了几项需要 root 权限的操作,但这些操作并没有被记录下来。唯一被记录下来的,是我使用 sudo 执行的操作。也就是说,由于该机器上的 root 账户被禁用了,我使用了我的 sudo 权限来使 su - 能正常工作。让我们看另一个片段,进一步展示这个过程是如何运作的:

Sep 29 20:50:45 localhost sudo: donnie : TTY=pts/0 ; PWD=/home/donnie ; 
USER=root ; COMMAND=/bin/less /var/log/secure 
Sep 29 20:55:30 localhost sudo: donnie : TTY=pts/0 ; PWD=/home/donnie ; 
USER=root ; COMMAND=/sbin/fdisk -l 
Sep 29 20:55:40 localhost sudo: donnie : TTY=pts/0 ; PWD=/home/donnie ; 
USER=root ; COMMAND=/bin/yum upgrade 
Sep 29 20:59:35 localhost sudo: donnie : TTY=tty1 ; PWD=/home/donnie ;
USER=root ; COMMAND=/bin/systemctl status sshd 
Sep 29 21:01:11 localhost sudo: donnie : TTY=tty1 ; PWD=/home/donnie ; 
USER=root ; COMMAND=/bin/less /var/log/secure

这次,我使用了我的sudo权限打开了一个日志文件,查看了我的硬盘配置,执行了系统更新,检查了 Secure Shell 守护进程的状态,并再次查看了一个日志文件。所以,如果你是我公司的安全管理员,你将能够看到我是否在滥用我的sudo权限。

现在,你可能会问:“那有什么能防止一个人直接执行sudo su -,以避免他的不当行为被发现呢?”这个问题很简单。只需要不给用户进入根命令提示符的权限。

为完整的管理员用户设置 sudo 权限

在我们讨论如何限制用户可以做什么之前,让我们先看看如何允许用户做所有事情,包括登录根命令提示符。有几种方法可以实现这一点。

将用户添加到预定义的管理员组

第一种方法是最简单的,就是将用户添加到预定义的管理员组,然后,如果还没有配置sudo策略,就配置该策略,以允许该组执行任务。虽然这种方法很简单,但不同的 Linux 发行版使用不同的管理员组。

在 Unix、BSD 和大多数 Linux 系统中,你需要将用户添加到wheel组。(包括 CentOS 和 AlmaLinux 在内的 Red Hat 家族成员属于这一类。)当我在任何一台 RHEL 类机器上执行groups命令时,我会得到如下输出:

[donnie@localhost ~]$ groups
donnie wheel
[donnie@localhost ~]$

这显示我属于wheel组。通过执行sudo visudo,我将打开sudo策略文件。向下滚动,我们可以看到赋予wheel组强大权限的那一行:

## Allows people in group wheel to run all commands
%wheel ALL=(ALL) ALL

百分号表示我们正在处理一个组。ALL出现三次表示该组的成员可以在网络上部署此策略的任何机器上以任何用户身份执行任何命令。唯一的小问题是,组成员在执行sudo任务时会被提示输入他们自己的正常用户账户密码。再往下滚动,你会看到以下内容:

## Same thing without a password
# %wheel ALL=(ALL) NOPASSWD: ALL

如果我们在前面的代码片段中注释掉%wheel行,并且在这个代码片段中去掉%wheel行前的注释符号,那么wheel组的成员将能够执行所有sudo任务,而无需输入任何密码。这是我真的不推荐的做法,即使是家庭使用也不应该这样做。在商业环境中,允许人员拥有无密码的sudo权限绝对是不可取的。

要将现有用户添加到wheel组中,使用带有-G选项的usermod命令。你可能还想使用-a选项,以防止将用户从他或她所属的其他组中移除。以我们的示例为例,我们来添加 Maggie:

sudo usermod -a -G wheel maggie

你也可以在创建用户账户时将其添加到wheel组中。我们现在就为 Frank 执行这个操作:

sudo useradd -G wheel frank

提示

请注意,使用useradd时,我假设我们在使用的是 Red Hat 系列的操作系统,因为它默认会创建用户账户。对于使用wheel组的非 Red Hat 系列发行版,你需要重新配置默认设置,或者使用额外的选项来创建用户的家目录并分配正确的 shell。你的命令应该像这样:

sudo useradd -G wheel -m -d /home/frank -s /bin/bash frank

对于 Debian 系列的成员,包括 Ubuntu,过程是一样的,只是你需要使用sudo组而不是wheel组。(考虑到 Debian 的开发者一向走自己的路,这一点也不奇怪。)

提示:

这种技术的一个应用场景是在你需要在云服务上创建虚拟专用服务器时,比如在 Rackspace、DigitalOcean 或 Vultr 上。当你登录到这些服务并首次创建虚拟机时,云服务会让你以 root 用户身份登录到虚拟机。(即使在 Ubuntu 上进行本地安装时 root 用户账户是禁用的,这种情况依然存在。)

在这种情况下,你首先需要为自己创建一个普通用户账户,并赋予该账户完全的sudo权限。然后,退出 root 账户,使用你的普通用户账户重新登录。接下来,你需要使用以下命令禁用 root 账户:

sudo passwd -l root

你还需要做一些额外的配置以限制 Secure Shell 访问,但我们将在第六章SSH 加固中介绍这些内容。

在 sudo 策略文件中创建条目

好的,如果你只是在处理单台机器,或者只在使用这两种管理员组中的一个组跨网络部署sudo策略,那么将用户添加到wheel组或sudo组非常有效。但是如果你想要在一个包含 Red Hat 和 Ubuntu 机器的混合网络中部署sudo策略呢?或者你不想到每台机器上去添加用户到管理员组?那么,只需在sudo策略文件中创建条目即可。你可以为单个用户创建条目,也可以创建一个用户别名。如果你在 CentOS 或其中一台 AlmaLinux 虚拟机上执行sudo visudo,你将看到一个已被注释掉的用户别名示例:

# User_Alias ADMINS = jsmith, mikem

你可以取消注释这行并添加你自己的用户名,或者你可以直接添加一行来创建自己的用户别名。要给予用户别名中的成员完全的sudo权限,请再添加一行,内容如下:

ADMINS ALL=(ALL) ALL 

也可以为单个用户添加visudo条目,你可能需要在非常特殊的情况下这么做。以下是一个示例:

frank ALL=(ALL) ALL

但为了便于管理,最好选择使用用户组或用户别名。

提示:

sudo策略文件是/etc/sudoers文件。我总是犹豫着告诉学生这个,因为时不时会有学生尝试用普通文本编辑器编辑它。然而,这并不奏效,所以请不要尝试。一定要通过sudo visudo命令编辑sudoers文件。

为用户设置仅限特定委派权限的 sudo

IT 安全哲学的基本原则是给予网络用户足够的权限,让他们完成工作,但不要赋予超出其职能范围的权限。所以,你会希望尽可能少的人拥有完全的sudo权限。(如果你启用了 root 用户账户,你会希望更少的人知道 root 密码。)你还需要一种方法,根据用户的具体工作分配权限。备份管理员需要执行备份任务,帮助台人员需要执行用户管理任务,等等。通过sudo,你可以委派这些权限,并禁止用户做任何不符合其职位描述的其他管理工作。

最好的解释方法是让你在任何一台 RHEL 类型的虚拟机上打开visudo。CentOS 7、AlmaLinux 8 和 AlmaLinux 9 都适用于此。所以,先启动其中一台,并输入:

sudo visudo

与 Ubuntu 不同,RHEL 类型的发行版有一个完全注释并且文档化良好的sudoers文件。我已经向你展示了创建ADMIN用户别名的行,你还可以为其他目的创建其他用户别名。例如,你可以创建一个BACKUPADMINS用户别名用于备份管理员,一个WEBADMINS用户别名用于 Web 服务器管理员,或者你需要的其他任何别名。所以,你可以添加像这样的行:

User_Alias SOFTWAREADMINS = vicky, cleopatra

这很好,除了 Vicky 和 Cleopatra 依然无法执行任何操作。你需要为用户别名分配一些职责。

如果你查看稍后提到的示例用户别名,你会看到一组示例命令别名。其中一个示例恰好是SOFTWARE,它包含了管理员在安装、移除软件或更新系统时所需要的命令。它被注释掉了,就像所有其他示例命令别名一样,因此在使用之前,你需要去掉行首的哈希符号:

Cmnd_Alias SOFTWARE = /bin/rpm, /usr/bin/up2date, /usr/bin/yum

现在,只需要简单地将SOFTWARE命令别名分配给SOFTWAREADMINS用户别名即可:

SOFTWAREADMINS ALL=(ALL) SOFTWARE

现在,作为SOFTWAREADMINS用户别名成员的 Vicky 和 Cleopatra,可以在所有安装了该策略的服务器上,使用 root 权限运行rpmup2dateyum命令。

除了SERVICES命令别名之外,其他所有预定义的命令别名在你取消注释并将其分配给用户、组或用户别名后就可以使用了:

Cmnd_Alias SERVICES = /sbin/service, /sbin/chkconfig, /usr/bin/systemctl start, /usr/bin/systemctl stop, /usr/bin/systemctl reload, /usr/bin/systemctl restart, /usr/bin/systemctl status, /usr/bin/systemctl enable, /usr/bin/systemctl disable

这个 SERVICES 别名的问题在于,它还列出了 systemctl 命令的不同子命令。sudo 的工作原理是,如果某个命令单独列出,那么被分配的用户可以使用该命令的任何子命令、选项或参数。所以,在 SOFTWARE 示例中,SOFTWARE 用户别名的成员可以运行如下命令:

sudo yum upgrade

但是,当命令在命令别名中列出,并带有子命令、选项或参数时,那就是被分配到该命令别名的任何人可以执行的所有内容。在当前配置的 SERVICES 命令别名中,systemctl 命令将无法工作。为了看到原因,我们将 Charlie 和 Lionel 设置在 SERVICESADMINS 用户别名中,然后像之前那样取消注释 SERVICES 命令别名:

User_Alias SERVICESADMINS = charlie, lionel
SERVICESADMINS ALL=(ALL) SERVICES

现在,看看当 Lionel 尝试检查 Secure Shell 服务的状态时会发生什么:

[lionel@centos-7 ~]$ sudo systemctl status sshd
 [sudo] password for lionel:
 Sorry, user lionel is not allowed to execute '/bin/systemctl status sshd' as root on centos-7.xyzwidgets.com.
 [lionel@centos-7 ~]$

好的,Lionel 可以运行 sudo systemctl status,这几乎没有什么用处,但他不能做任何有意义的事情,比如指定他想要检查的服务。这有点问题。有两种方法可以解决这个问题,但只有一种是你想要使用的。你可以直接删除所有 systemctl 子命令,让 SERVICES 别名看起来像这样:

Cmnd_Alias SERVICES = /sbin/service, /sbin/chkconfig, /usr/bin/systemctl

但是,如果你这么做,Lionel 和 Charlie 也将能够关闭或重启系统,编辑服务文件,或将机器从一个 systemd 目标切换到另一个。这可能不是你想要的。由于 systemctl 命令涵盖了许多不同的功能,你必须小心不要让授权的用户访问太多这些功能。一个更好的解决方案是,在每个 systemctl 子命令中添加一个通配符:

Cmnd_Alias SERVICES = /sbin/service, /sbin/chkconfig, /usr/bin/systemctl start *, /usr/bin/systemctl stop *, /usr/bin/systemctl reload *, /usr/bin/systemctl restart *, /usr/bin/systemctl status *, /usr/bin/systemctl enable *, /usr/bin/systemctl disable * 

现在,Lionel 和 Charlie 可以执行这个命令别名中列出的任何 systemctl 功能,用于任何服务:

 [lionel@centos-7 ~]$ sudo systemctl status sshd
 [sudo] password for lionel:
 ● sshd.service - OpenSSH server daemon
    Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
  Active: active (running) since Sat 2017-09-30 18:11:22 EDT; 23min ago
     Docs: man:sshd(8)
              man:sshd_config(5)
  Main PID: 13567 (sshd)
      CGroup: /system.slice/sshd.service
                   └─13567 /usr/sbin/sshd -D
 Sep 30 18:11:22 centos-7.xyzwidgets.com systemd[1]: Starting OpenSSH server daemon...
 Sep 30 18:11:22 centos-7.xyzwidgets.com sshd[13567]: Server listening on 0.0.0.0 port 22.
 Sep 30 18:11:22 centos-7.xyzwidgets.com sshd[13567]: Server listening on :: port 22.
 Sep 30 18:11:22 centos-7.xyzwidgets.com systemd[1]: Started OpenSSH server daemon.
 [lionel@centos-7 ~]$

请记住,你不仅限于使用用户别名和命令别名。你还可以将权限分配给 Linux 组或单个用户。你还可以将单独的命令分配给用户别名、Linux 组或单个用户。下面是一个例子:

katelyn ALL=(ALL) STORAGE
gunther ALL=(ALL) /sbin/fdisk -l
%backup_admins ALL=(ALL) BACKUP

Katelyn 现在可以执行 STORAGE 命令别名中的所有命令,而 Gunther 只能使用 fdisk 查看分区表。backup_admins Linux 组的成员可以执行 BACKUP 命令别名中的命令。

本主题中我们最后要看的内容是你在用户别名示例前看到的主机别名示例:

# Host_Alias FILESERVERS = fs1, fs2
# Host_Alias MAILSERVERS = smtp, smtp2

每个主机别名都由一组服务器主机名组成。这使得你可以在一台机器上创建一个 sudoers 文件,并通过网络进行部署。例如,你可以创建一个 WEBSERVERS 主机别名,一个 WEBADMINS 用户别名,以及一个包含适当命令的 WEBCOMMANDS 命令别名。你的配置大致如下:

Host_Alias WEBSERVERS = webserver1, webserver2
User_Alias WEBADMINS = junior, kayla
Cmnd_Alias WEBCOMMANDS = /usr/bin/systemctl status httpd, /usr/bin/systemctl start httpd, /usr/bin/systemctl stop httpd, /usr/bin/systemctl restart httpd
WEBADMINS WEBSERVERS=(ALL) WEBCOMMANDS 

现在,当用户在网络上的服务器中输入命令时,sudo 将首先查看该服务器的主机名。如果该用户被授权在该服务器上执行命令,则 sudo 允许执行。否则,sudo 将拒绝执行。在中小型企业中,手动将主 sudoers 文件复制到所有服务器可能完全没问题。但在大型企业中,你需要简化并自动化这一过程。为此,你可以使用 Puppet、Chef 或 Ansible 等工具。(这三种技术超出了本书的范围,但你可以在 Packt 网站上找到许多关于它们的书籍和视频课程。)

所有这些技巧都适用于你的 Ubuntu 虚拟机,也适用于 CentOS 虚拟机。唯一的区别是,Ubuntu 没有预定义的命令别名,因此你需要自己手动输入。

无论如何,我知道你已经厌倦了阅读,现在让我们开始做一些实际操作吧。

分配有限 sudo 权限的实操实验

在这个实验中,你将创建一些用户并为他们分配不同的权限。为了简化操作,我们使用 AlmaLinux 9 虚拟机。

  1. 登录到 AlmaLinux 虚拟机并为 Lionel、Katelyn 和 Maggie 创建用户账户:
 sudo useradd lionel
 sudo useradd katelyn
 sudo useradd maggie
 sudo passwd lionel
 sudo passwd katelyn
 sudo passwd maggie
  1. 打开 visudo
sudo visudo

找到 STORAGE 命令别名,并移除它前面的注释符号。

  1. 在文件末尾添加以下行,使用制表符分隔列:
lionel ALL=(ALL) ALL
katelyn ALL=(ALL) /usr/bin/systemctl status sshd
maggie ALL=(ALL) STORAGE 

保存文件并退出 visudo

  1. 为了节省时间,我们将使用 su 来登录不同的用户账户。这样,你就不需要退出自己的账户来执行这些步骤。首先,登录到 Lionel 的账户,并通过运行多个 root 权限的命令来验证他是否具有完整的 sudo 权限:
 su - lionel
 sudo su -
 exit
 sudo systemctl status sshd
 sudo fdisk -l
 exit
  1. 这次,请以 Katelyn 的身份登录并尝试运行一些 root 权限的命令。不过,如果这些命令并不完全成功,也不必太失望:
 su - katelyn
 sudo su -
 sudo systemctl status sshd
 sudo systemctl restart sshd
 sudo fdisk -l
 exit
  1. 最后,以 Maggie 的身份登录,并运行与 Katelyn 相同的一组命令。

  2. 请记住,尽管我们在这个实验中只使用了三个独立用户,但你完全可以通过设置用户别名或 Linux 用户组来处理更多的用户。

既然 sudo 是如此强大的安全工具,你一定会认为每个人都会使用它,对吧?遗憾的是,事实并非如此。几乎每次你查看 Linux 教程网站或 Linux 教程 YouTube 频道时,你都会看到演示者以 root 用户身份登录。在某些情况下,我看到演示者在云虚拟机上远程以 root 用户身份登录。现在,如果以 root 用户身份登录已经是一个糟糕的主意,那么通过互联网以 root 用户身份登录就更加糟糕了。不管怎样,看到每个人都从 root 用户的 shell 中演示这些教程,真让我抓狂。

说到这些,有些事情是无法与 sudo 一起使用的。像 cd 这样的 Bash shell 内部命令无法与其一起使用,使用 echo/proc 文件系统注入内核值也无法与其一起工作。对于这些任务,用户必须切换到 root 命令提示符。然而,确保只有那些必须使用 root 用户命令提示符的人才能访问它。

接下来,让我们来看一些更高级的 sudo 用法。

使用 sudo 的高级技巧和窍门

现在我们已经了解了设置良好的 sudo 配置的基础知识,我们面临着一个悖论。也就是说,尽管 sudo 是一种安全工具,但你可以用它做的某些事情,反而可能使你的系统变得比原来更不安全。让我们看看如何避免这种情况。

sudo 定时器

默认情况下,sudo 定时器设置为五分钟。这意味着一旦用户执行了一个 sudo 命令并输入了密码,他或她可以在五分钟内执行另一个 sudo 命令,而无需再次输入密码。尽管这显然很方便,但如果用户在命令终端仍然打开的情况下离开桌面,这也可能成为一个问题。

如果五分钟定时器还没有到期,其他人可能会过来执行一些 root 级别的任务。如果你的安全需求要求这样做,你可以通过在 sudoers 文件的 Defaults 部分添加一行来轻松禁用此定时器。这样,用户每次运行 sudo 命令时都必须输入密码。你可以将其设为全局设置,也可以仅为某些个别用户设置。

假设你正坐在一个舒适的工作间,登录到一台远程 Linux 服务器,而这台服务器的五分钟定时器仍然启用。如果你需要离开你的桌子片刻,最好的做法是先退出服务器。否则,你可以通过运行以下命令来重置 sudo 定时器:

sudo -k

这是少数几个无需输入密码即可执行的 sudo 操作之一。但下次你执行 sudo 命令时,即使上次输入密码距离现在还不到五分钟,你也必须再次输入密码。

查看你的 sudo 权限

不确定你拥有哪些 sudo 权限吗?不用担心,你有办法查找。只需运行以下命令:

sudo -l

当我为自己执行此操作时,我首先会看到我帐户的一些环境变量,然后我会看到我拥有完整的 sudo 权限:

donnie@packtpub1:~$ sudo -l
 [sudo] password for donnie:
 Matching Defaults entries for donnie on packtpub1:
 env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

 User donnie may run the following commands on packtpub1:
 (ALL : ALL) ALL
 donnie@packtpub1:~$

当我的猫 Frank(以前是流浪的火焰点暹罗猫)为他的帐户执行此操作时,他看到他只能执行 fdisk -l 命令:

frank@packtpub1:~$ sudo -l
 [sudo] password for frank:
 Matching Defaults entries for frank on packtpub1:
 env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

 User frank may run the following commands on packtpub1:
 (ALL) /sbin fdisk -l
 frank@packtpub1:~$

但由于他是一只猫,他不会抱怨。相反,他会试图做一些偷偷摸摸的事情,就像我们稍后看到的那样。

禁用 sudo 定时器的动手实验

对于这个实验,你将禁用你 AlmaLinux 虚拟机上的 sudo 定时器:

  1. 登录到你用于上一个实验的相同 AlmaLinux 虚拟机。我们将使用你已经创建的用户账户。

  2. 在你自己的用户账户命令提示符下,输入以下命令:

 sudo fdisk -l
 sudo systemctl status sshd
 sudo iptables -L

你会看到,只需要输入一次密码就能执行这三个命令。

  1. 在你自己的用户账户命令提示符下,运行以下命令:
 sudo fdisk -l
 sudo -k
 sudo fdisk -l

请注意,sudo -k命令会重置你的计时器,因此你需要再次输入密码。使用以下命令打开visudo

sudo visudo

在文件的Defaults specification部分,添加以下行:

Defaults timestamp_timeout = 0 

保存文件并退出visudo

  1. 执行你在步骤 2中执行的命令。这次,你应该会看到每次都需要输入密码。

  2. 打开visudo并修改你添加的那一行,使其如下所示:

Defaults:lionel timestamp_timeout = 0 

保存文件并退出visudo

  1. 在你自己的账户 Shell 中,重复你在步骤 2中执行的命令。然后,以 Lionel 身份登录,并再次执行这些命令。

  2. 通过运行以下命令查看你自己的sudo权限:

sudo -l

请注意,这个过程同样适用于 Ubuntu。

防止用户拥有 root shell 访问权限

假设你想为某个用户设置有限的sudo权限,但你是通过添加类似这样的行来实现的:

maggie ALL=(ALL) /bin/bash, /bin/zsh

很抱歉地告诉你,你并没有限制 Maggie 的访问权限。你实际上已经赋予了她完整的sudo权限,包括 Bash 和 ZSH Shell。因此,不要在你的sudoers文件中添加这样的行,因为这会让你陷入麻烦。

防止用户使用 Shell 转义功能

某些程序,特别是文本编辑器和分页程序,具有便捷的 Shell 转义功能。用户可以在不退出程序的情况下运行 Shell 命令。例如,在 Vi 和 Vim 编辑器的命令模式下,用户可以通过运行:!ls来执行ls命令。执行该命令的结果如下所示:

# useradd defaults file
GROUP=100
HOME=/home
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash
SKEL=/etc/skel
CREATE_MAIL_SPOOL=yes
~
~
:!ls

输出将如下所示:

[donnie@localhost default]$ sudo vim useradd
 [sudo] password for donnie:
 grub nss useradd
 Press ENTER or type command to continue
 grub nss useradd
 Press ENTER or type command to continue

现在,假设你想让 Frank 只能编辑sshd_config文件,而不是其他文件。你可能会想在你的sudo配置中添加一行,类似如下:

frank ALL=(ALL) /bin/vim /etc/ssh/sshd_config

这看起来好像可行,对吧?实际上并非如此,因为一旦 Frank 通过他的sudo权限打开了sshd_config文件,他就可以使用 Vim 的 Shell 转义功能来执行其他 root 级命令,其中包括编辑其他配置文件。你可以通过让 Frank 使用sudoedit而不是vim来解决这个问题:

frank ALL=(ALL) sudoedit /etc/ssh/sshd_config

sudoedit没有 Shell 转义功能,因此你可以安全地允许 Frank 使用它。其他具有 Shell 转义功能的程序包括:

  • emacs

  • less

  • view

  • more

防止用户使用其他危险程序

有些没有 Shell 转义功能的程序,如果你给用户无限制的使用权限,仍然可能是危险的。这些程序包括:

  • cat

  • cut

  • awk

  • sed

如果你必须授予某人使用这些程序的sudo权限,最好将他们的使用限制在特定文件上。这也是我们接下来的提示。

限制用户使用命令的操作

假设您创建了一个 sudo 规则,让 Sylvester 可以使用 systemctl 命令:

sylvester ALL=(ALL) /usr/bin/systemctl

这使得 Sylvester 可以完全使用 systemctl 的功能。他可以控制守护进程、编辑服务文件、关闭或重启系统,并执行 systemctl 的所有其他功能。显然,这可能不是您所希望的。最好指定允许 Sylvester 执行的 systemctl 功能。假设您希望他只能控制 Secure Shell 服务,您可以将这行修改为如下:

sylvester ALL=(ALL) /usr/bin/systemctl * sshd 

现在,Sylvester 可以做他需要做的任何与 Secure Shell 服务相关的事情,但他不能关闭或重启系统,不能编辑其他服务文件,或者改变 systemd 目标。但如果您希望 Sylvester 只执行 Secure Shell 服务的某些特定操作,那您就需要去掉通配符并指定所有允许 Sylvester 执行的操作:

sylvester ALL=(ALL) /usr/bin/systemctl status sshd, /usr/bin/systemctl restart sshd 

现在,Sylvester 只能重启 Secure Shell 服务或检查其状态。

提示:

在编写 sudo 策略时,您需要注意网络上不同 Linux 和 Unix 发行版之间的差异。例如,在 Red Hat 类型的系统上,systemctl 可执行文件位于 /usr/bin 目录下;而在 Debian/Ubuntu 系统中,它位于 /bin 目录下。如果您需要将 sudoers 文件推广到一个拥有多个操作系统的大型企业网络中,可以使用主机别名来确保服务器只允许执行适合其操作系统的命令。

同时,请注意某些系统服务在不同的 Linux 发行版上有不同的名称。在 Red Hat 类型的系统中,Secure Shell 服务是 sshd,而在 Debian/Ubuntu 系统中,它的名称是 ssh

允许用户以其他用户身份运行命令

在以下一行中,(ALL) 表示 Sylvester 可以以任何用户身份运行 systemctl 命令:

sylvester ALL=(ALL) /usr/bin/systemctl status sshd, /usr/bin/systemctl restart sshd

这实际上赋予了 Sylvester 执行这些命令的 root 权限,因为 root 用户绝对是任何用户。如果需要,您可以将 (ALL) 更改为 (root),以指定 Sylvester 只能以 root 用户身份执行这些命令:

sylvester ALL=(root) /usr/bin/systemctl status sshd, /usr/bin/systemctl restart sshd 

好吧,可能这没什么意义,因为没有任何变化。之前 Sylvester 就拥有了这些 systemctl 命令的 root 权限,现在他依然拥有。但这个功能有更实际的用途。假设 Vicky 是数据库管理员,您希望她以 database 用户身份运行:

vicky ALL=(database) /usr/local/sbin/some_database_script.sh

Vicky 现在可以通过输入以下命令,以数据库用户身份运行命令:

sudo -u database some_database_script.sh

这是一个可能不常用的功能,但还是要记住它。你永远不知道什么时候它会派上用场。

防止通过用户的 shell 脚本滥用权限

那么,如果用户编写了一个需要sudo权限的 shell 脚本该怎么办呢?为了解答这个问题,让我们让 Frank 创建一个名为 frank_script.sh 的 shell 脚本,内容如下:

#!/bin/bash
echo "This script belongs to Frank the Cat."

好吧,他其实不需要sudo权限,但我们假装他需要。在他设置了可执行权限并使用sudo运行脚本后,输出将如下所示:

 frank@packtpub1:~$ sudo ./frank_script.sh
 [sudo] password for frank:
 Sorry, user frank is not allowed to execute './frank_script.sh' as root on packtpub1.tds.
 frank@packtpub1:~$

所以,Frank 自然地感到沮丧,要求我创建一个sudo规则,以便他可以运行这个脚本。于是,我打开visudo并为 Frank 添加这个规则:

frank ALL=(ALL) /home/frank/frank_script.sh

现在,当 Frank 使用sudo运行脚本时,它可以正常工作:

 frank@packtpub1:~$ sudo ./frank_script.sh
 [sudo] password for frank:
 This script belongs to Frank the Cat.
 frank@packtpub1:~$

但是,由于这个文件在 Frank 自己的家目录中,而且他是它的所有者,他可以随意编辑它。所以,作为一个狡猾的类型,他在脚本的末尾添加了sudo -i这一行,使得脚本现在看起来是这样的:

#!/bin/bash

echo "This script belongs to Frank the Cat."
sudo -i

准备好迎接即将发生的震惊吧:

 frank@packtpub1:~$ sudo ./frank_script.sh
 This script belongs to Frank the Cat.
 root@packtpub1:~#

如你所见,Frank 现在已经以 root 用户身份登录。

sudo -i的作用是让用户登录到 root 用户的 shell,类似于sudo su -。如果 Frank 在自己的命令提示符下执行sudo -i,它将失败,因为 Frank 没有这个权限。但他有权限使用sudo运行自己的脚本。通过将脚本保存在自己的家目录中,Frank 可以将 root 级命令写入其中。通过sudo运行该脚本时,脚本中的 root 级命令将以 root 权限执行。

为了解决这个问题,我将使用我的强大sudo权限,将 Frank 的脚本移动到/usr/local/sbin/目录,并将所有权更改为 root 用户,这样 Frank 就无法编辑它。当然,在我这样做之前,我会确保从脚本中删除sudo -i这一行:

 donnie@packtpub1:~$ sudo -i
 root@packtpub1:~# cd /home/frank
 root@packtpub1:/home/frank# mv frank_script.sh /usr/local/sbin
 root@packtpub1:/home/frank# chown root: /usr/local/sbin/frank_script.sh
 root@packtpub1:/home/frank# exit
 logout
 donnie@packtpub1:~$

最后,我将打开visudo并更改他的规则,以反映脚本的新位置。新的规则如下所示:

frank ALL=(ALL) /usr/local/sbin/frank_script.sh

Frank 仍然可以运行脚本,但他无法编辑它:

 frank@packtpub1:~$ sudo frank_script.sh
 This script belongs to Frank the Cat.
 frank@packtpub1:~$

检测并删除默认用户账户

处理物联网(IoT)设备的一个挑战是,你并不像在设置正常的服务器时那样进行正常的操作系统安装。相反,你下载一个预装了操作系统的镜像,并将该镜像写入 microSD 卡。已安装的操作系统会设置一个默认用户账户,而且很多时候这个账户被赋予了完整的sudo权限,并且不需要输入sudo密码。以 Raspberry Pi 的 Raspex Linux 发行版为例。(Raspex 是基于 Ubuntu 源代码构建的。)在 Raspex 下载站点的文档页面上,我们看到默认用户是raspex,该用户的默认密码也是raspex。我们还看到root用户的默认密码是root

19501_02_01.png

19501_02_01.png

所以,默认的凭证暴露给了全世界显而易见。显然,当你在设置 IoT 设备时,首先要做的就是设置你自己的用户账户,给它设置一个强密码,并赋予它sudo权限。然后删除那个默认账户,因为留下它,尤其是如果你留下默认密码的话,简直就是在自找麻烦。

但是让我们深入挖掘一下。查看 Raspex 上的/etc/password文件,你会看到默认用户:

raspex:x:1000:1000:,,,:/home/raspex:/bin/bash

然后,查看/etc/sudoers文件,你会看到这一行,它允许raspex用户执行所有sudo命令而无需输入密码:

raspex ALL=(ALL) NOPASSWD: ALL

另一个需要注意的是,一些用于物联网设备的 Linux 发行版会将此规则放在/etc/sudoers.d目录的一个单独文件中,而不是主sudoers文件中。无论如何,在设置物联网设备时,你都需要删除这个规则以及默认用户账户。当然,你还需要更改root用户密码,并锁定root用户账户。

好的,让我们快速看一下最近新增的一些 sudo 功能。

新的 sudo 特性

我之前提到过,sudo 的一个美妙之处在于,它允许你查看用户在使用 sudo 权限时的操作。从 sudo 版本 1.9.0 开始,sudo 的日志记录功能得到了极大增强。你现在可以将 sudo 的日志消息保存为.json 格式,这使得 sudo 能够记录比通常更多的信息,且格式更易于解析。从 sudo 版本 1.9.4 开始,你还可以让 sudo 将日志消息发送到中央日志服务器,这样恶意行为者就更难从系统日志文件中删除他们的恶行记录。

不幸的是,由于空间限制,我无法在这里对这些新特性进行详细说明。不过没关系。在 Opensource.com 网站上,Peter Czanik 先生写了一篇很好的文章,详细解释了这些特性。所以,我会把你引导到他的文章:

系统管理员 2022 年需要了解的 5 个新 sudo 特性-- opensource.com/article/22/2/new-sudo-features-2022

我还应该提到,要了解你的 Linux 发行版支持哪些新 sudo 特性,你需要知道它包含的是哪个版本的 sudo。你可以通过以下方式来查找:

sudo --version

接下来,我们来看看一些 SUSE 的独特之处。

SUSE 和 OpenSUSE 的特殊 sudo 考虑事项

如果你曾经使用过任何类型的 SUSE 机器,你可能会困惑于,当你执行 sudo 命令时,它要求输入 root 用户的密码,而不是你自己的密码。这是因为 SUSE 有一种与众不同的方式来处理 sudo。

当你安装 SUSE 发行版时,你会看到一个类似于 RHEL 类型发行版的用户创建界面。

19501_02_02.png

19501_02_02.png

然而,当你勾选将此密码用于系统管理员复选框时,它并不会像 RHEL 类型的发行版那样将你的用户账户添加到 wheel 组中。相反,它会自动将你为自己创建的密码分配给 root 用户账户。所以,你和 root 用户将会拥有相同的密码。

当你在 SUSE 机器上执行sudo visudo时,你会看到这两行,而其他任何 Linux 发行版都没有:

Defaults targetpw   # ask for the password of the target user i.e. root
ALL   ALL=(ALL) ALL   # WARNING! Only use this together with 'Defaults targetpw'!

这意味着任何拥有 root 用户密码的用户都可以执行所有 sudo 命令。不过,公平地说,前面这两行的注释告诉我们,这只是用于初始系统设置,在将机器投入生产使用之前,我们应按照正常方式重新配置 sudo。为此,首先将自己的用户帐户添加到 wheel 组,如下所示:

donnie@localhost:~> sudo usermod -a -G wheel donnie
[sudo] password for root: 
donnie@localhost:~>

通过注销系统并重新登录来使组成员资格生效。使用 groups 命令验证是否属于 wheel 组,如下所示:

donnie@localhost:~> groups
users wheel
donnie@localhost:~>

接下来,执行 sudo visudo 命令,并注释掉之前我们查看的两行。它们现在应该看起来像这样:

#Defaults targetpw   # ask for the password of the target user i.e. root
#ALL   ALL=(ALL) ALL   # WARNING! Only use this together with 'Defaults targetpw'!

(或者,更好的是,直接删除这些行。)

向下滚动文件,直到你看到这一行:

# %wheel ALL=(ALL:ALL) ALL

删除前面的注释符号,使该行看起来像这样:

%wheel ALL=(ALL:ALL) ALL

保存文件并退出 visudo。现在,当你执行 sudo 命令时,你将被要求输入自己的密码,而不是 root 用户的密码。

donnie@localhost:~> sudo fdisk -l
[sudo] password for donnie:
. . .
. . .

当然,root 用户帐户仍然启用,因此让我们将其禁用,如下所示:

donnie@localhost:~> sudo passwd -l root
passwd: password expiry information changed.
donnie@localhost:~>

好的,这基本上涵盖了 sudo 相关内容。接下来我们来看一下如何保护普通用户账户。

以 Red Hat 方式锁定用户的主目录

这是不同 Linux 发行版家族之间业务差异的另一个领域。正如我们将看到的,每个发行版家族对用户主目录的默认安全设置不同。一个管理混合环境的安全管理员需要考虑这一点。

关于 Red Hat Enterprise Linux 及其所有衍生版本(如 CentOS 和 AlmaLinux)的一大优点是,它们比其他任何 Linux 发行版具有更好的开箱即用安全性。这使得加固 Red Hat 类型系统更加快捷和容易,因为许多工作已经完成。已经为我们完成的一项工作是锁定用户的主目录:

 [donnie@localhost home]$ sudo useradd charlie
 [sudo] password for donnie:
 [donnie@localhost home]$
 [donnie@localhost home]$ ls -l
 total 0
 drwx------. 2 charlie charlie 59 Oct 1 15:25 charlie
 drwx------. 2 donnie donnie 79 Sep 27 00:24 donnie
 drwx------. 2 frank frank 59 Oct 1 15:25 frank
 [donnie@localhost home]$

默认情况下,Red Hat 类型系统上的 useradd 工具会创建权限设置为 700 的用户主目录。这意味着只有拥有该主目录的用户可以访问它。所有其他普通用户都无法访问。我们可以通过查看 /etc/login.defs 文件来了解原因。在你的 CentOS 7 虚拟机上,向文件底部滚动,你会看到以下内容:

CREATE_HOME yes
UMASK 077

在 RHEL 8 或 RHEL 9 类型发行版(如 AlmaLinux)的 login.defs 文件中,你会看到 UMASK 设置为完全开放的权限,这看起来有些奇怪。如下所示:

UMASK           022

但是,在下面几行,你会看到一个全新的指令,这是我们之前没有见过的,看起来像这样:

HOME_MODE       0700

所以,尽管 UMASK 是完全开放的,但新用户的主目录仍然会被正确地锁定。

login.defs 文件是配置 useradd 默认设置的两个文件之一。UMASK 行或 HOME_MODE 行决定了在创建主目录时的权限值。红帽类发行版通常配置为 077,这会去掉组和其他用户的所有权限。所有 Linux 发行版的 login.defs 文件中都有这一 UMASK 行,但直到最近,红帽类发行版是唯一将 UMASK 设置为如此严格值的发行版。大多数非红帽类发行版通常将 UMASK 设置为 022,这会创建一个权限值为 755 的主目录。这样,任何人都能进入他人的主目录并访问彼此的文件。

按 Debian/Ubuntu 的方式锁定用户主目录

Debian 及其衍生版,如 Ubuntu,提供了两个用户创建工具:

  1. useradd

  2. adduser

让我们看看它们两个。

在 Debian/Ubuntu 上添加用户

useradd 工具存在,但 Debian 和 Ubuntu 并不像红帽类发行版那样提供预配置的默认设置。如果你只是执行 sudo useradd frank 在默认的 Debian/Ubuntu 机器上,Frank 将没有主目录,并且会被分配错误的默认 shell。因此,要在 Debian 或 Ubuntu 系统上使用 useradd 创建一个用户账户,命令应该是这样的:

sudo useradd -m -d /home/frank -s /bin/bash frank

在这个命令中,我们有:

  1. -m 创建主目录。

  2. -d 指定主目录。

  3. -s 指定 Frank 的默认 shell。(如果没有 -s,Debian/Ubuntu 会将 /bin/sh shell 分配给 Frank。)

当你查看 Debian 或 Ubuntu 20.04 机器上的主目录时,你会看到它们是完全开放的,所有人都可以执行和读取:

 donnie@packt:/home$ ls -l
 total 8
 drwxr-xr-x 3 donnie donnie 4096 Oct 2 00:23 donnie
 drwxr-xr-x 2 frank frank 4096 Oct 1 23:58 frank
 donnie@packt:/home$

如你所见,Frank 和我可以访问对方的东西。(不, 我可不想让 Frank 碰我的东西。)每个用户可以修改自己目录的权限,但有多少用户知道该如何做呢?所以,让我们自己来解决这个问题:

 cd /home
 sudo chmod 700 *

让我们看看现在的情况:

 donnie@packt:/home$ ls -l
 total 8
 drwx------ 3 donnie donnie 4096 Oct 2 00:23 donnie
 drwx------ 2 frank frank 4096 Oct 1 23:58 frank
 donnie@packt:/home$

看起来好多了。

要更改主目录的默认权限设置,请打开 /etc/login.defs 进行编辑。找到这一行:

UMASK 022

将其更改为:

UMASK 077

现在,新用户的主目录在创建时将会被锁定,就像红帽及其衍生版一样。

在 Ubuntu 22.04 上,情况有所不同。Ubuntu 开发者终于意识到,用户的主目录默认应该被锁定。因此,Ubuntu 22.04 的 login.defs 文件中的 HOME_MODE 设置现在是这样的:

HOME_MODE       0750

这包括用户自己个人组的访问权限,但没关系,这仍然有效地意味着只有各自的主目录拥有者才能进入。

在 Debian/Ubuntu 上添加用户

adduser工具是一种交互式的用户账户和密码创建方式,使用一个命令即可完成,这在 Debian 家族的 Linux 发行版中是独有的。大多数在 Debian 实现的useradd中缺失的默认设置,adduser已经为其设置好了。在 Debian 和 Ubuntu 20.04 中,它创建的用户家目录权限为开放的755,幸运的是,这很容易更改。(稍后我们会看到如何更改。)在 Ubuntu 22.04 中,它创建的家目录权限为更严格的 750 权限值。

尽管adduser对于创建用户账户非常方便,但它没有useradd那样的灵活性,也不适合在 shell 脚本中使用。adduser能做到useradd做不到的一件事是:在创建账户时自动加密用户的家目录。要使其生效,首先需要安装ecryptfs-utils软件包。因此,要为 Cleopatra 创建一个加密家目录的账户,您需要执行以下操作:

sudo apt install ecryptfs-utils
 donnie@ubuntu-steemnode:~$ sudo adduser --encrypt-home cleopatra
 [sudo] password for donnie:
 Adding user `cleopatra' ...
 Adding new group `cleopatra' (1004) ...
 Adding new user `cleopatra' (1004) with group `cleopatra' ...
 Creating home directory `/home/cleopatra' ...
 Setting up encryption ...
 ************************************************************************
 YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IT IN A SAFE LOCATION.
  ecryptfs-unwrap-passphrase ~/.ecryptfs/wrapped-passphrase
 THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME.
 ********************************************************************
Done configuring.
 Copying files from `/etc/skel' ...
 Enter new UNIX password:
 Retype new UNIX password:
 passwd: password updated successfully
 Changing the user information for cleopatra
 Enter the new value, or press ENTER for the default
  Full Name []: Cleopatra Tabby Cat
  Room Number []: 1
  Work Phone []: 555-5556
  Home Phone []: 555-5555
  Other []:
 Is the information correct? [Y/n] Y
 donnie@ubuntu-steemnode:~$

Cleopatra 第一次登录时,需要运行前面输出中提到的ecryptfs-unwrap-passphrase命令。然后她需要记下她的密码短语并将其存储在安全的地方:

 cleopatra@ubuntu-steemnode:~$ ecryptfs-unwrap-passphrase
 Passphrase:
 d2a6cf0c3e7e46fd856286c74ab7a412
 cleopatra@ubuntu-steemnode:~$

我们将在加密章节时更详细地讨论加密的相关内容。

配置 adduser 的实践实验

在本实验中,我们将使用 Ubuntu 22.04 虚拟机上的adduser工具。

  1. 安装ecryptfs-utils软件包:
sudo apt install ecryptfs-utils
  1. 为 Cleopatra 创建一个具有加密家目录的用户账户,然后查看结果:
 sudo adduser --encrypt-home cleopatra
 ls -l /home
  1. 以 Cleopatra 身份登录并运行ecryptfs-unwrap-passphrase命令:
su - cleopatra
ecryptfs-unwrap-passphrase
exit
  1. 请注意,adduser要求提供的一些信息是可选的,您可以直接按Enter键跳过这些项。

强制实施强密码标准

你可能不会想到,看似平凡的强密码标准竟然如此有争议,但事实确实如此。你在整个计算机职业生涯中听到的传统智慧是:

  • 设置密码的最低长度。

  • 设置由大写字母、小写字母、数字和特殊字符组合成的密码。

  • 确保密码不包含任何字典中的单词或用户个人数据的相关信息。

  • 强制用户定期更改密码。

但是,通过使用你喜欢的搜索引擎,你会发现不同的专家在这些标准的细节上存在分歧。例如,有专家对于密码是否应该每 30、60 或 90 天更改存在分歧,也有专家对密码中是否需要包含所有四种类型的字符存在不同意见,甚至对于密码的最小长度应该是多少也存在争议。

最有趣的争议来自——从一个非常意想不到的地方——就是最初发明上述标准的那个人。他现在说这一切都是胡说八道,并后悔自己提出了这些标准。他现在说我们应该使用既长又易于记住的密码短语,并且只有在密码泄露的情况下才应更改。

比尔·伯尔(Bill Burr),前美国国家标准与技术研究院(NIST)工程师,他是我之前提到的强密码标准的制定者,分享了他现在为何否定自己工作的原因。详细内容请参见www.pcmag.com/news/355496/you-might-not-need-complex-alphanumeric-passwords-after-all

自本书的初版发布以来,NIST 已经开始同意比尔·伯尔的观点。他们现在已经将密码实施标准更改为符合伯尔先生的建议。你可以在此阅读更多内容。

www.riskcontrolstrategies.com/2018/01/08/new-nist-guidelines-wrong/

然而,尽管如此,现实情况是,许多组织仍然坚持使用复杂的密码并且定期过期,如果你无法说服他们改变,他们会要求你遵守这些规则。此外,如果你使用传统密码,你确实希望它们足够强大,以抵御任何形式的密码攻击。所以现在,我们来看看如何在 Linux 系统上强制执行强密码标准。

提示:

我必须承认,我以前从未想过尝试在 Linux 系统上创建一个密码短语来替代密码。因此,我刚刚在我的 CentOS 虚拟机上尝试了一下,看看它是否有效。

我为玛吉(Maggie)创建了一个账户,她是我那只黑白相间的燕尾服猫咪。她的密码,我设置了密码短语I like other kitty cats。你可能会想:“哦,这太糟糕了。这不符合任何复杂度标准,而且它使用了字典词汇。这样怎么能算安全呢?”但事实上,作为一个由不同词汇组成的短语,并且这些词汇通过空格分开,确实使它变得安全,并且非常难以通过暴力破解攻击。

现在,在现实生活中,我绝不会创建一个表达我对猫咪喜爱的密码短语,因为很容易发现我确实非常喜欢猫。而是,我会选择一个与我生活中的某个不为人知的、更隐秘的部分相关的密码短语。无论如何,密码短语相比传统密码有两个优点。它们比传统密码更难破解,同时用户也更容易记住。不过,为了额外的安全性,千万不要创建一个关于你生活中大家都知道的事实的密码短语。

安装与配置 pwquality

我们将使用 pwquality 模块来配置 可插拔认证模块PAM)。这是一个更新的技术,已经取代了旧的 cracklib 模块。在任何 Red Hat 7 或更高版本的系统上,以及在 SUSE 和 OpenSUSE 系统中,pwquality 都会默认安装,即使是进行最小化安装时也是如此。如果你 cd/etc/pam.d 目录,你可以进行 grep 操作,查看 PAM 配置文件是否已经设置好。retry=3 意味着用户在登录系统时,只有三次机会输入正确的密码:

[donnie@localhost pam.d]$ grep 'pwquality' *
 password-auth:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 password-auth-ac:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 system-auth:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 system-auth-ac:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 [donnie@localhost pam.d]$

在 Debian 和 Ubuntu 上,你需要自行安装 pwquality,如下所示:

sudo apt install libpam-pwquality

其余的过程在所有操作系统中都是一样的,只需要编辑 /etc/security/pwquality.conf 文件。当你在文本编辑器中打开此文件时,你会看到所有内容都被注释掉了,这意味着没有启用任何密码复杂性标准。你还会看到这个文件非常有文档说明,因为每个设置都有相应的解释性注释。

你可以根据需要设置密码复杂性标准,只需取消注释相关行并设置相应的值。让我们来看一下其中一个设置:

# Minimum acceptable size for the new password (plus one if 
# credits are not disabled which is the default). (See pam_cracklib manual.) 
# Cannot be set to lower value than 6\. 
# minlen = 8 

最小长度设置基于积分系统。这意味着密码中每种不同类型的字符类别都会减少一个所需的最小密码长度。例如,我们将 minlen 设置为 19,并尝试给 Katelyn 分配密码 turkeylips

minlen = 19
[donnie@localhost ~]$ sudo passwd katelyn
 Changing password for user katelyn.
 New password:
 BAD PASSWORD: The password is shorter than 18 characters
 Retype new password:
 [donnie@localhost ~]$

因为 turkeylips 中的小写字母算作一种字符类别的积分,所以我们只需要 18 个字符,而不是 19 个。如果我们再次尝试使用 TurkeyLips,我们会得到:

[donnie@localhost ~]$ sudo passwd katelyn
 Changing password for user katelyn.
 New password:
 BAD PASSWORD: The password is shorter than 17 characters
 Retype new password:
 [donnie@localhost ~]$

这一次,大写字母 T 和大写字母 L 计算为第二个字符类别,所以我们只需要 17 个字符的密码。

minlen 这一行下方,你会看到积分设置行。假设你不希望小写字母计算在积分内。你会找到这一行:

# lcredit = 1 

取消注释,并将 1 改为 0

lcredit = 0

然后,尝试将 turkeylips 作为密码分配给 Katelyn:

[donnie@localhost ~]$ sudo passwd katelyn
 Changing password for user katelyn.
 New password:
 BAD PASSWORD: The password is shorter than 19 characters
 Retype new password:
 [donnie@localhost ~]$

这一次,pwquality 确实要求 19 个字符。如果我们将积分值设置为大于 1 的数值,我们会对同一类字符类型获得多个积分,直到该数值为止。

我们还可以将积分值设置为负数,以要求密码中有一定数量的字符类型。例如,我们可以这样设置:

dcredit = -3

这将要求密码中至少有三个数字。然而,使用这个功能是非常不明智的,因为进行密码攻击的人很快就能发现你要求的模式,这将帮助攻击者更精确地发起攻击。如果你需要要求密码包含多种字符类型,最好使用 minclass 参数:

# minclass = 3

它已经设置为3,这将要求密码字符来自三个不同的类别。要使用此值,你只需移除注释符号。

pwquality.conf中的其余参数的工作方式基本相同,每个参数都有一个精心编写的注释来解释其作用。

提示:

如果你使用sudo权限设置其他人的密码,当你创建一个不符合复杂性要求的密码时,系统会发出警告,但仍然允许你执行此操作。如果普通用户在没有sudo权限的情况下尝试更改自己的密码,系统则不允许设置不符合复杂性要求的密码。

设置密码复杂性要求的实践实验

对于这个实验,你可以使用 CentOS、AlmaLinux 或 Ubuntu 虚拟机,任选其一。唯一的区别是,你不需要在 CentOS 或 AlmaLinux 上执行第 1 步。

  1. 仅限 Ubuntu,安装libpam-pwquality包:
sudo apt install libpam-pwquality
  1. 打开/etc/security/pwquality.conf文件,使用你喜欢的文本编辑器。移除minlen行前的注释符号,并将值更改为19。它现在应该像这样:
 minlen = 19

保存文件并退出编辑器。

  1. 为 Goldie 创建一个用户账户,并尝试为她分配密码turkeylipsTurkeyLipsTurkey93Lips。注意每条警告信息中的变化。

  2. pwquality.conf文件中,注释掉minlen这一行,取消注释minclassmaxclassrepeat这一行。将maxclassrepeat的值更改为5。现在,这些行应该像这样:

minclass = 3 
maxclassrepeat = 5 

保存文件并退出文本编辑器。

  1. 尝试为 Goldie 的账户分配各种不符合你设定的复杂性要求的密码,并查看结果。

在你 CentOS 7 机器的/etc/login.defs文件中,你会看到PASS_MIN_LEN 5这一行。

理论上,这是为了设置最小密码长度,但实际上,pwquality会覆盖它。所以,你可以将该值设置为任何值,它都不会产生任何影响。

设置并强制执行密码和账户过期政策

你绝对不希望未使用的用户账户仍然保持激活状态。曾经发生过管理员为临时使用设置用户账户,比如用于会议,之后在账户不再需要时忘记了这些账户。

另一个例子是,如果你的公司雇佣了合同工,合同在特定日期到期。如果让这些账户在临时员工离开公司后仍然保持激活并可访问,将是一个巨大的安全问题。在这种情况下,你需要一种方式来确保临时用户账户在不再需要时不会被遗忘。如果你的雇主遵循定期更改密码的传统做法,那么你还需要确保这个要求得到执行。

密码过期数据和帐户过期数据是两件不同的事情。它们可以单独或一起设置。当某人的密码过期时,他或她可以更改密码,一切就会恢复正常。如果某人的帐户过期,只有拥有适当管理员权限的人才能解锁帐户。

要开始,请查看您自己帐户的过期数据。请注意,查看您自己的数据不需要sudo权限,但您仍然需要指定自己的用户名:

donnie@packt:~$ chage -l donnie
 [sudo] password for donnie:
 Last password change : Oct 03, 2017
 Password expires : never
 Password inactive : never
 Account expires : never
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@packt:~$

您可以看到,这里没有设置过期时间。一切都是根据开箱即用的系统默认值设置的。除了显而易见的项目外,以下是对您看到内容的简要说明:

  • 密码不活跃:如果这个值设置为正数,则表示在密码过期后,我会有这么多天的时间来更改密码,直到系统锁定我的帐户。

  • 密码更改之间的最小天数:由于这个值设置为0,我可以随时更改密码。如果它设置为一个正数,则在更改密码后,我必须等待指定天数才能再次更改密码。

  • 密码更改之间的最大天数:默认值为99999,意味着我的密码永远不会过期。

  • 密码过期前的警告天数:默认值为7,但当密码设置为永不过期时,这个值其实没什么意义。

使用chage工具,您可以为其他用户设置密码和帐户过期时间,或者使用-l选项查看过期时间。任何无特权的用户都可以使用chage -l命令而无需sudo来查看自己的数据。要设置数据或查看其他用户的数据,您需要sudo权限。稍后我们将详细了解chage

在我们查看如何更改过期时间之前,首先来看看默认设置存储在哪里。我们首先看一下/etc/login.defs文件。以下是三行相关内容:

PASS_MAX_DAYS 99999 
PASS_MIN_DAYS 0 
PASS_WARN_AGE 7

您可以编辑这些值以适应您组织的需求。例如,将PASS_MAX_DAYS更改为30,将导致从那时起所有新用户的密码都有 30 天的有效期。(顺便说一句,在login.defs中设置默认的密码过期时间对我们使用的所有 Linux 发行版都有效。)

仅针对 Red Hat 类型系统配置useradd的默认过期时间

/etc/default/useradd文件包含其余的默认设置。在这种情况下,我们将查看 AlmaLinux 9 机器上的文件:

Ubuntu 也有useradd配置文件,但它不起作用。不管您怎么配置,Ubuntu 版本的useradd根本不会读取它。所以关于这个文件的说明仅适用于 Red Hat 类型的系统。

# useradd defaults file
GROUP=100
HOME=/home 
INACTIVE=-1 
EXPIRE= 
SHELL=/bin/bash 
SKEL=/etc/skel 
CREATE_MAIL_SPOOL=yes

EXPIRE=行设置新用户账户的默认到期日期。默认情况下,没有默认到期日期。INACTIVE=-1表示用户密码过期后,用户账户不会自动锁定。如果我们将其设置为正数,则新用户将有多少天的时间来更改过期的密码,直到账户被锁定。要更改useradd文件中的默认值,你可以手动编辑该文件,或者使用useradd -D并搭配相应的选项开关来修改你想更改的项目。例如,要设置默认到期日期为 2023 年 12 月 31 日,命令如下:

sudo useradd -D -e 2023-12-31

要查看新的配置,你可以打开useradd文件或直接执行sudo useradd -D

[donnie@localhost ~]$ sudo useradd -D
 GROUP=100
 HOME=/home
 INACTIVE=-1
 EXPIRE=2023-12-31
 SHELL=/bin/bash
 SKEL=/etc/skel
 CREATE_MAIL_SPOOL=yes
 [donnie@localhost ~]$

现在,你已经设置了任何新创建的用户账户将具有相同的到期日期。你也可以通过INACTIVE设置或SHELL设置来实现同样的效果:

sudo useradd -D -f 5
 sudo useradd -D -s /bin/zsh
 [donnie@localhost ~]$ sudo useradd -D
 GROUP=100
 HOME=/home
 INACTIVE=5
 EXPIRE=2019-12-31
 SHELL=/bin/zsh
 SKEL=/etc/skel
 CREATE_MAIL_SPOOL=yes
 [donnie@localhost ~]$

现在,任何新创建的用户账户都会将 Zsh 设置为默认 Shell,并且密码过期后必须在五天内更改,以防止账户被自动锁定。

useradd不会进行任何安全检查来确保你指定的默认 Shell 已经安装在系统中。在我们的案例中,Zsh 没有安装,但useradd仍然允许你将 Zsh 设置为默认 Shell 来创建账户。

那么,useradd的这个配置功能在现实生活中有多有用呢?可能并不太有用,除非你需要一次性创建大量具有相同设置的用户账户。即使如此,精明的管理员也会通过 Shell 脚本来自动化这个过程,而不是去弄这个配置文件。

使用 useradd 和 usermod 为每个账户设置到期数据

你可能会觉得在login.defs中设置默认密码到期数据很有用,但你可能不会觉得配置useradd配置文件有多大用处。说实话,几乎不太可能需要你为所有用户账户设置相同的到期日期?在login.defs中设置密码到期数据更有用,因为你可以直接设置新密码将在一定天数内到期,而不是在某个特定日期过期。

很可能,你会根据账户是否会在特定日期后不再需要来为每个账户设置到期数据。有三种方式可以做到这一点:

  • 使用useradd和适当的选项开关在创建账户时设置到期数据。(如果你需要一次性创建一堆账户并设置相同的到期数据,你可以通过 Shell 脚本来自动化这个过程。)

  • 使用usermod修改现有账户的到期数据。(usermod的优点是它使用的选项开关与useradd相同。)

  • 使用chage修改现有账户的到期数据。(这个命令使用了完全不同的一组选项开关。)

你可以使用useraddusermod来设置账户过期数据,但不能设置密码过期数据。影响账户过期数据的两个选项开关如下:

  • -e:使用此选项设置账户的过期日期,格式为 YYYY-MM-DD。

  • -f:使用此选项设置用户密码过期后,账户被锁定的天数。

假设你想为 Charlie 创建一个将在 2025 年底过期的账户。在 Red Hat 类机器上,你可以输入以下命令:

sudo useradd -e 2025-12-31 charlie

在非 Red Hat 类的机器上,你需要添加创建主目录并分配正确默认 shell 的选项开关:

sudo useradd -m -d /home/charlie -s /bin/bash -e 2025-12-31 charlie

使用chage -l来验证你输入的内容:

donnie@ubuntu-steemnode:~$ sudo chage -l charlie
 Last password change : Oct 06, 2017
 Password expires : never
 Password inactive : never
 Account expires : Dec 31, 2025
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@ubuntu-steemnode:~$

假设 Charlie 的合同已经延长,你需要将他的账户过期时间更改为 2026 年 1 月底。你可以像在任何 Linux 发行版上使用usermod一样操作:

sudo usermod -e 2026-01-31 charlie

再次使用chage -l验证所有设置是否正确:

donnie@ubuntu-steemnode:~$ sudo chage -l charlie
 Last password change : Oct 06, 2017
 Password expires : never
 Password inactive : never
Account expires : Jan 31, 2026
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@ubuntu-steemnode:~$

可选地,你可以设置密码过期后账户被锁定前的天数:

sudo usermod -f 5 charlie

但如果现在进行此操作,你在chage -l输出中不会看到任何变化,因为我们仍然没有设置 Charlie 密码的过期数据。

使用 chage 设置每个账户的过期数据

你只会使用chage来修改现有账户,并用它来设置账户过期或密码过期。以下是相关的选项开关:

选项 说明
-d 如果你在某人的账户上使用-d 0选项,你将强制用户在下次登录时更改密码。
-E 这相当于useraddusermod中的小写-e。它设置用户账户的过期日期。
-I 这相当于useraddusermod中的-f。它设置密码过期后账户被锁定之前的天数。
-m 这设置密码更改之间的最小天数。换句话说,如果 Charlie 今天更改了密码,-m 5选项将强制他等待五天才能再次更改密码。
-M 这设置密码过期之前的最大天数。(但要注意,如果 Charlie 上次设置密码是在 89 天前,使用-M 90选项将导致他的密码明天过期,而不是 90 天后的今天。)
-W 这将设置密码即将过期时的警告天数。
  1. 你可以一次设置这些数据项中的一个,或者你可以一次性设置所有这些数据。事实上,为了避免给你带来麻烦而为每个单独的项做不同的演示,让我们一次性设置它们,除了-d 0,然后看看我们得到了什么:
sudo chage -E 2026-02-28 -I 4 -m 3 -M 90 -W 4 charlie
 donnie@ubuntu-steemnode:~$ sudo chage -l charlie
 Last password change : Oct 06, 2019
 Password expires : Jan 04, 2026
 Password inactive : Jan 08, 2026
 Account expires : Feb 28, 2026
 Minimum number of days between password change : 3
 Maximum number of days between password change : 90
 Number of days of warning before password expires : 4
 donnie@ubuntu-steemnode:~$

所有过期数据现在已被设置。

在我们的最后一个示例中,假设你刚为 Samson 创建了一个新账户,并且你希望强制他在第一次登录时更改密码。有两种方法可以做到这一点。无论哪种方式,都应在你最初设置密码之后进行。例如,操作如下:

sudo chage -d 0 samson
 or
 sudo passwd -e samson

 donnie@ubuntu-steemnode:~$ sudo chage -l samson
 Last password change : password must be changed
 Password expires : password must be changed
 Password inactive : password must be changed
 Account expires : never
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@ubuntu-steemnode:~$

接下来,我们将进行一个实操实验。

设置账户和密码过期数据的实操实验

在这个实验中,你将创建几个新的用户账户,设置过期日期,并查看结果。你可以在任何虚拟机上进行此实验,唯一的区别是useradd命令的使用:

  1. 在你的 CentOS 或 AlmaLinux 虚拟机上,为 Samson 创建一个用户账户,设置过期日期为 2025 年 6 月 30 日,并查看结果:
sudo useradd -e 2025-06-30 samson
sudo chage -l samson

对于 Ubuntu,运行以下命令:

sudo useradd -m -d /home/samson -s /bin/bash -e 2025-06-30
sudo chage -l samson
  1. 使用usermod命令将 Samson 的账户过期日期更改为 2025 年 7 月 31 日:
sudo usermod -e 2025-07-31
sudo chage -l samson
  1. 为 Samson 的账户设置密码,然后强制他在第一次登录时更改密码。以 Samson 身份登录,修改密码,然后重新登录到你的账户:
sudo passwd samson
sudo passwd -e samson
sudo chage -l samson
su - samson
exit
  1. 使用chage命令设置密码更改的等待期为五天,密码过期周期为 90 天,不活跃期为两天,警告期为五天:
sudo chage -m 5 -M 90 -I 2 -W 5 samson
sudo chage -l samson
  1. 保留这个账户,因为你将在下一部分的实验中使用它。

接下来,让我们来看一下如何防止暴力破解攻击。

防止暴力破解密码攻击

令人惊讶的是,这是另一个引发争议的话题。我的意思是,没有人否认自动锁定受攻击的用户账户的智慧。争议的部分在于我们应该允许多少次登录失败后再锁定账户。

在计算机的石器时代,那时我还有一头浓密的头发,早期的 Unix 操作系统只允许用户创建一个最多包含八个小写字母的密码。所以在那个时候,早期的人只需坐在键盘前,随机输入密码,就能暴力破解他人的密码。那时的哲学就是,在三次登录失败后就将用户账户锁定。如今,随着强密码的使用,或者更好的是,强密码短语的使用,将登录失败的锁定值设为三次,将带来三个结果:

  1. 这会不必要地让用户感到沮丧。

  2. 这将增加帮助台人员的额外工作量。

  3. 如果账户确实正在受到攻击,它将在你还未有机会收集攻击者信息之前锁定账户。

将锁定值设置为更现实的数字,比如 100 次登录失败,仍然可以提供良好的安全性,同时还为你提供足够的时间收集有关攻击者的信息。同样重要的是,这样做不会给用户和帮助台人员带来不必要的沮丧。

无论您的雇主允许多少次失败的登录尝试,您仍然需要知道如何配置它。在 RHEL 7 类型的系统和 Ubuntu 18.04 中,您将通过配置 pam_tally2 插件认证模块(PAM)来实现。而在 RHEL 8/9 类型的系统和 Ubuntu 20.04/22.04 中,您则需要配置 pam_faillock PAM 模块。让我们深入了解,看看如何操作。

在 CentOS 7 上配置 pam_tally2 PAM 模块

为了让这个功能生效,我们将依赖我们的好朋友 PAM。pam_tally2 模块已经预装在 CentOS 7 上,但尚未配置。我们将首先编辑 /etc/pam.d/login 文件。配置它的方法很简单,因为 pam_tally2 手册页的底部有一个示例:

EXAMPLES
 Add the following line to /etc/pam.d/login to lock the account after
4 failed logins. Root account will be locked as well. The accounts will be
automatically unlocked after 20 minutes. The module does not have to be
called in the account phase because the login calls pam_setcred(3)
correctly.
 auth required pam_securetty.so
 auth required pam_tally2.so deny=4 even_deny_root
unlock_time=1200
 auth required pam_env.so
 auth required pam_unix.so
 auth required pam_nologin.so
 account required pam_unix.so
 password required pam_unix.so
 session required pam_limits.so
 session required pam_unix.so
 session required pam_lastlog.so nowtmp
 session optional pam_mail.so standard

在示例的第二行中,我们看到 pam_tally2 配置了以下参数:

  • deny=4:这意味着在仅四次失败的登录尝试后,受攻击的用户账户将被锁定。

  • even_deny_root:这意味着即使是 root 用户账户也会在受到攻击时被锁定。

  • unlock_time=1200:账户将在 1200 秒(或 20 分钟)后自动解锁。

现在,如果您查看虚拟机上的实际 login 文件,您会发现它们看起来与手册页中的示例 login 文件并不完全相同。没关系,我们依然可以使它工作。

一旦您配置了 login 文件并进行了失败的登录,您将在 /var/log 目录中看到一个新文件的生成。您可以使用 pam_tally2 工具查看该文件中的信息。如果您不想等待超时期间,您还可以使用 pam_tally2 手动解锁被锁定的账户:

donnie@ubuntu-steemnode:~$ sudo pam_tally2
 Login Failures Latest failure From
 charlie 5 10/07/17 16:38:19
 donnie@ubuntu-steemnode:~$ sudo pam_tally2 --user=charlie --reset
 Login Failures Latest failure From
 charlie 5 10/07/17 16:38:19
 donnie@ubuntu-steemnode:~$ sudo pam_tally2
 donnie@ubuntu-steemnode:~$

请注意,在我重置 Charlie 账户后,我并没有收到任何来自另一次查询的输出。

配置 pam_tally2 的实验室操作(CentOS 7)

配置 pam_tally2 非常简单,因为它只需要在 /etc/pam.d/login 文件中添加一行。为了让事情更容易,您可以直接从 pam_tally2 手册页中的示例复制并粘贴那一行。尽管我之前说过要将失败登录次数提高到 100,但我们现在将失败次数保持在 4,因为我知道您不希望为了演示这一点而进行 100 次失败的登录尝试:

  1. 在 CentOS 7 虚拟机中,打开 /etc/pam.d/login 文件进行编辑。找到调用 pam_securetty 模块的行。(应该在第 2 行左右。)在该行下面,插入以下内容:
auth required pam_tally2.so deny=4 even_deny_root unlock_time=1200

保存文件并退出编辑器。

  1. 对于这一步,您需要退出自己的账户,因为 pam_tally2 无法与 su 一起使用。所以请退出并在故意使用错误密码的情况下,尝试登录您在上一个实验中创建的 samson 账户。继续这样做,直到看到账户被锁定的提示信息。请注意,当 deny 值设置为 4 时,实际上需要五次失败的登录尝试才能锁定 Samson 账户。

  2. 重新登录到你的用户帐户。运行此命令并注意输出:

sudo pam_tally2
  1. 在此步骤中,你将模拟自己是帮助台工作人员,Samson 刚刚打电话请求解锁他的帐户。确认你确实在和真正的 Samson 通话后,输入以下两个命令:
sudo pam_tally2 --user=samson --reset
sudo pam_tally2
  1. 现在你已经了解了这个过程,打开/etc/pam.d/login文件进行编辑,将deny=参数从4改为100并保存文件。(这将使你的配置在现代安全理念方面更加真实。)

  2. 接下来,让我们看看如何在 AlmaLinux 机器上配置 pam_faillock。

在 AlmaLinux 8/9 上配置 pam_faillock

  1. pam_faillock 模块已经安装在任何 RHEL 8 或 RHEL 9 类型的 Linux 发行版上。由于 pam_faillock 的基本概念与 pam_tally2 基本相同,我们将省略初步解释,直接进入操作步骤。

配置 AlmaLinux 8 或 AlmaLinux 9 上 pam_faillock 的实践实验

虽然你可以通过手动编辑 PAM 配置文件来启用和配置 pam_faillock,但 RHEL 系列发行版提供了一种更简单的方法,叫做authselect

  1. 在 AlmaLinux 8 或 AlmaLinux 9 虚拟机上,通过以下方式查看可用的 authselect 配置文件:
[donnie@localhost ~]$ sudo authselect list
[sudo] password for donnie: 
- minimal    Local users only for minimal installations
- sssd       Enable SSSD for system authentication (also for local users only)
- winbind    Enable winbind for system authentication
[donnie@localhost ~]$
  1. 至少目前,我们只处理本地用户。所以,我们将使用最小配置文件。像这样查看该配置文件的功能:
[donnie@localhost ~]$ sudo authselect list-features minimal
. . .
. . .
with-faillock
. . .
. . .
[donnie@localhost ~]$

请注意,有许多包含的功能,但我们只对with-faillock功能感兴趣。

  1. 启用最小配置文件,如下所示:
[donnie@localhost ~]$ sudo authselect select minimal --force
  1. 启用配置文件后,我们现在可以启用 pam_faillock 模块,如下所示:
[donnie@localhost ~]$ sudo authselect enable-feature with-faillock
  1. /etc/security/目录下,在你喜欢的文本编辑器中打开faillock.conf文件。查找以下四行:
# silent
# deny = 3
# unlock_time = 600
# even_deny_root

从所有四行中删除前面的注释符号,并保存文件。

  1. 创建一个用户帐户,并故意让该用户进行三次登录失败尝试。查看结果,如下所示:
[donnie@localhost ~]$ sudo faillock
donnie:
When                Type  Source                                           Valid
vicky:
When                Type  Source                                           Valid
2022-10-12 15:54:35 RHOST 192.168.0.16                                         V
2022-10-12 15:54:42 RHOST 192.168.0.16                                         V
2022-10-12 15:54:46 RHOST 192.168.0.16                                         V
[donnie@localhost ~]$
  1. 等待十分钟的计时器到期,然后让用户使用正确的密码尝试登录。

  2. 让用户注销。然后,再次让用户故意进行三次登录失败尝试。这次,在计时器到期之前重置用户帐户,如下所示:

[donnie@localhost ~]$ sudo faillock --reset --user vicky

在 Ubuntu 上执行此操作有些不同,所以让我们接下来看看如何在 Ubuntu 上配置。

在 Ubuntu 20.04 和 Ubuntu 22.04 上配置 pam_faillock

遗憾的是,authselect工具在 Ubuntu 上不可用,所以我们只能手动编辑 PAM 配置文件。以下是操作步骤。

配置 Ubuntu 20.04 和 Ubuntu 22.04 上 pam_faillock 的实践实验

  1. 在你喜欢的文本编辑器中打开/etc/pam.d/common-auth文件。在文件的顶部插入以下两行:
auth        required                                     pam_faillock.so preauth silent
       auth        required                                     pam_faillock.so authfail
  1. 在文本编辑器中打开/etc/pam.d/common-account文件,在文件底部添加以下行:
account     required                                     pam_faillock.so
  1. 按照我在前面的实验第 5 步中展示的方法,配置/etc/security/faillock.conf文件,步骤相同。

  2. 按照前面 AlmaLinux 实验的第 6 至第 8 步进行设置测试。

  3. 就这样。接下来,让我们看看如何手动锁定用户账户。

锁定用户账户

好了,你刚刚看到如何让 Linux 在受到攻击时自动锁定用户账户。接下来,会有一些情况是你需要手动锁定用户账户的。让我们看看几个例子:

  • 当用户去度假,而你希望确保在他或她不在期间,没人擅自操作该用户账户时

  • 当一个用户因可疑活动而接受调查时

  • 当用户离开公司时

关于最后一点,你可能会问自己,为什么我们不能直接删除那些已经不再在公司工作的人账户? 当然,你可以这样做,轻而易举。不过,在这样做之前,你需要检查当地法律,以确保不会陷入麻烦。以美国为例,我们有萨班斯-奥克斯利法案,限制了公开上市公司从其计算机中删除哪些文件。如果你删除了一个用户账户,同时删除了该用户的主目录和邮件缓存,那么你可能就违反了萨班斯-奥克斯利法案,或者在你所在国家的类似法律。

总之,你可以使用两个工具来临时锁定用户账户:

  • usermod

  • passwd

与我刚才所说的表面上相矛盾的是,某些时候你确实需要删除非活跃的用户账户。这是因为恶意行为者可以利用非活跃账户进行不正当行为,尤其是当该账户具有某种管理权限时。但在删除账户时,一定要确保遵守当地法律和公司政策。事实上,你最好的做法是确保你的组织在变更管理程序中有书面指导方针来处理删除非活跃用户账户的事宜。

使用usermod来锁定用户账户

假设 Katelyn 已经开始休产假,将会离开好几个星期。我们可以通过执行以下命令来锁定她的账户:

 sudo usermod -L katelyn

当你查看 Katelyn 在/etc/shadow文件中的条目时,你会看到她的密码哈希前面加了一个感叹号,如下所示:

katelyn:!$6$uA5ecH1A$MZ6q5U.cyY2SRSJezV000AudP.ckXXndBNsXUdMI1vPO8aFmlLXcbGV25K5HSSaCv4RlDilwzlXq/hKvXRkpB/:17446:0:99999:7:::

这个感叹号防止系统读取她的密码哈希,这实际上锁定了她的系统访问权限。

要解锁她的账户,只需执行以下命令:

sudo usermod -U katelyn

你会看到感叹号被移除,这样她就能登录她的账户了。

使用passwd来锁定用户账户

你还可以使用以下命令来锁定 Katelyn 的账户:

sudo passwd -l katelyn

这与usermod -L的作用相同,只是方式略有不同。首先,passwd -l会给你一些关于发生了什么的反馈,而usermod -L则完全没有反馈。在 Ubuntu 上,反馈信息如下:

donnie@ubuntu-steemnode:~$ sudo passwd -l katelyn
 [sudo] password for donnie:
 passwd: password expiry information changed.
 donnie@ubuntu-steemnode:~$

在 CentOS 或 AlmaLinux 中,反馈信息如下:

[donnie@localhost ~]$ sudo passwd -l katelyn
 Locking password for user katelyn.
 passwd: Success
 [donnie@localhost ~]$

此外,在 CentOS 或 AlmaLinux 系统上,你会发现 passwd -l 会在密码哈希前放置两个感叹号,而不是一个。不管怎样,效果是一样的。

要解锁 Katelyn 的帐户,只需执行以下操作:

sudo passwd -u katelyn

在 Red Hat 或 CentOS 版本 7 之前,usermod -U 只会移除 passwd -lshadow 文件的密码哈希前放置的一个感叹号,从而导致帐户仍然被锁定。不过没什么大问题,因为再次运行 usermod -U 会移除第二个感叹号。

自从 RHEL 7 类型的发行版推出以来,这个问题已经得到修复。passwd -l 命令仍然会在 shadow 文件中放置两个感叹号,但 usermod -U 现在会同时移除这两个感叹号。(这真是遗憾,因为它破坏了我一直很喜欢为学生做的一个很好的演示。)

锁定 root 用户帐户

现在,云计算是一个大生意,租用像 Rackspace、DigitalOcean 或 Microsoft Azure 这样的虚拟私人服务器已经相当普遍。这些服务器可以用于各种用途:

  • 你可以运行自己的网站,安装自己的服务器软件,而不是让托管服务来安装。

  • 你可以为其他人设置一个基于 Web 的应用程序以供访问。

  • 最近,我在一个加密货币挖矿频道的 YouTube 演示中看到,展示了如何在租用的虚拟私人服务器上设置一个股权证明(Proof of Stake)主节点。

这些云服务的一个共同点是,当你首次设置帐户时,服务提供商会为你设置一个虚拟机,并让你登录到 root 用户帐户。(即使是在 Ubuntu 上,虽然本地安装的 Ubuntu 会禁用 root 帐户,初次设置时也会让你登录 root 用户帐户。)

我知道有些人会一直登录到这些云服务器的 root 帐户,并认为这没什么,但其实这真是个糟糕的主意。有一些僵尸网络,比如 Hail Mary 僵尸网络,会不断扫描互联网,寻找开放了 SSH 端口的服务器。当这些僵尸网络找到一个目标时,它们会对该服务器的 root 用户帐户进行暴力破解攻击。是的,僵尸网络有时会成功入侵,尤其是当 root 帐户设置了弱密码时。

因此,当你设置基于云的服务器时,首先要做的就是为自己创建一个普通用户帐户,并赋予其完全的 sudo 权限。然后,退出 root 用户帐户,登录到你的新帐户,接着执行以下操作:

sudo passwd -l root

我的意思是,真的,为什么要冒着让你的 root 帐户被入侵的风险呢?

设置安全标语

你绝对不希望出现的情况是,登录横幅上写着类似 Welcome to our network 这样的内容。我之所以这么说,是因为很多年前,我参加了一个由 SANS 主办的网络事件处理课程。我们的导师讲了一个故事,讲述一家公司的网络入侵嫌疑人被告上法庭,结果案件被驳回了。理由是什么?这位嫌疑人说,“我看到了写着欢迎来到网络的消息,所以我以为我真的是被欢迎进入这里的。”是的,显然,这个理由足以让案件被驳回。

几年后,我将这个故事讲给我的 Linux 管理课程的学生们听。一位学生说,“这完全没有道理。我们所有人家门口都有欢迎垫,但这并不意味着小偷可以随便进来。”我不得不承认,他说的有道理,现在我不得不怀疑这个故事的真实性。

不管怎么说,为了安全起见,你应该设置登录消息,明确表示只有授权用户才能访问系统。

使用 motd 文件

/etc/motd 文件会在任何通过安全外壳(Secure Shell)登录系统的用户面前显示消息横幅。在 CentOS 或 AlmaLinux 机器上,已经有一个空的 motd 文件。对于 Ubuntu 机器来说,motd 文件并不存在,但创建一个文件非常简单。无论哪种情况,打开该文件并在文本编辑器中创建你的消息。保存文件并通过安全外壳远程登录测试。你应该能看到类似这样的内容:

 maggie@192.168.0.100's password:
 Last login: Sat Oct 7 20:51:09 2017
 Warning: Authorized Users Only!
 All others will be prosecuted.
 [maggie@localhost ~]$

motd 代表 Message of the Day(每日信息)。

Ubuntu 配备了一个动态的 MOTD 系统,显示来自 Ubuntu 母公司以及操作系统的消息。当你在 /etc 目录中创建一个新的 motd 文件时,放入其中的任何消息都会显示在动态输出的末尾,如下所示:

Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-48-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
  System information as of Thu Oct 13 06:20:54 PM UTC 2022
  System load:  0.0               Processes:               103
  Usage of /:   47.8% of 9.75GB   Users logged in:         1
  Memory usage: 12%               IPv4 address for enp0s3: 192.168.0.11
  Swap usage:   0%
39 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
Warning!!! Authorized users only!
Last login: Thu Oct 13 17:14:52 2022 from 192.168.0.16

Warning!!! Authorized users only! 这一行是我放入 /etc/motd 文件中的内容。

使用 issue 文件

issue 文件,位于 /etc 目录中,在本地终端的登录提示符上方显示消息。一个默认的 issue 文件会包含显示机器信息的宏代码。以下是来自 Ubuntu 机器的一个示例:

Ubuntu 22.04.1 LTS \n \l

或者,在一台类似 Red Hat 的机器上,它会像这样:

\S
Kernel \r on an \m

在一台 Ubuntu 机器上,横幅会像这样:

19501_02_03.png

19501_02_03.png

在一台类似 Red Hat 的机器上,它看起来会像这样:

19501_02_04.png

19501_02_04.png

你可以在 issue 文件中放置一个安全消息,重启后它会显示出来:

19501_02_05.png

19501_02_05.png

现实中,将安全消息放在 issue 文件里真的有意义吗?如果你的服务器被正确地锁在服务器机房,并且有严格的访问控制,那可能没有必要。对于那些暴露在外的桌面机器,这种做法就更有意义了。

使用 issue.net 文件

还是别尝试了。那是telnet登录用的,任何在服务器上启用了telnet的人都在严重犯错。然而,不知道为什么,issue.net文件依然会出现在/etc目录中。

检测泄露的密码

是的,亲爱的们,坏人确实有广泛的密码字典,这些密码字典包含常用的密码或已经泄露的密码。进行暴力破解密码的最有效方式之一是使用这些字典进行字典攻击。这种攻击方式是当密码破解工具从指定的字典中读取密码并逐一尝试,直到字典中的密码被用尽,或者攻击成功为止。那么,你如何知道你的密码是否在这些字典中?很简单,只需使用一个在线服务来检查你的密码。一个流行的网站是Have I Been Pwned?,你可以在这里看到:

19501_02_06.png

19501_02_06.png

你可以在这里访问Have I Been Pwned?

haveibeenpwned.com

你只需要输入你的密码,服务就会告诉你密码是否出现在任何泄露的密码列表上。但想想看,你真的想把你的生产密码发到某个网站上吗?是的,我也这么认为。那么我们干脆只发送密码的哈希值。更好的是,我们只发送足够的哈希值,让网站能够在它的数据库中找到密码,但不会发送太多信息,以至于他们能够知道你密码的确切内容。我们将通过使用Have I Been Pwned? 应用程序编程接口API)来实现这一点。

为了演示基本原理,假设我们使用curl和 API,查看密码哈希中包含21BD1值的列表。(你可以在你的任何虚拟机上执行此操作。我将在我当前用来输入的 Fedora 工作站上执行它。)只需运行以下命令:

curl https://api.pwnedpasswords.com/range/21BD1

你将会看到很多类似的输出,所以我只展示前几行:

 [donnie@fedora-teaching ~]$ curl https://api.pwnedpasswords.com/range/21BD1
 0018A45C4D1DEF81644B54AB7F969B88D65:1
 00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2
 011053FD0102E94D6AE2F8B83D76FAF94F6:1
 012A7CA357541F0AC487871FEEC1891C49C:2
 0136E006E24E7D152139815FB0FC6A50B15:3
 01A85766CD276B17DE6DA022AA3CADAC3CE:3
 024067E46835A540D6454DF5D1764F6AA63:3
 02551CADE5DDB7F0819C22BFBAAC6705182:1
 025B243055753383B479EF34B44B562701D:2
 02A56D549B5929D7CD58EEFA97BFA3DDDB3:8
 02F1C470B30D5DDFF9E914B90D35AB7A38F:3
 03052B53A891BDEA802D11691B9748C12DC:6
. . .
. . .

让我们将其管道化到wc -l,这是一个方便的计数工具,用来查看我们找到了多少个匹配的结果:

 [donnie@fedora-teaching ~]$ curl https://api.pwnedpasswords.com/range/21BD1 | wc -l
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 100 20592 0 20592 0 0 197k 0 --:--:-- --:--:-- --:--:-- 199k
 526
 [donnie@fedora-teaching ~]$

根据这个,我们找到了 526 个匹配项。但这并没有什么实际用处,因此让我们稍微让事情变得更复杂一点。我们通过创建一个pwnedpasswords.sh的 shell 脚本来实现,这个脚本如下所示:

#!/bin/bash
candidate_password=$1
echo "Candidate password: $candidate_password"
full_hash=$(echo -n $candidate_password | sha1sum | awk '{print substr($1, 0, 32)}')
prefix=$(echo $full_hash | awk '{print substr($1, 0, 5)}')
suffix=$(echo $full_hash | awk '{print substr($1, 6, 26)}')
if curl https://api.pwnedpasswords.com/range/$prefix | grep -i $suffix;
 then echo "Candidate password is compromised";
 else echo "Candidate password is OK for use";
fi

好吧,我现在不能试图把你变成一个 shell 脚本大师,但这是简化后的解释:

  • candidate_password=$1:当你调用脚本时,需要输入你想检查的密码。

  • full_hash=prefix=suffix=:这些行计算密码的 SHA1 哈希值,然后提取我们想要发送给密码检查服务的哈希部分。

  • if curl:我们使用一个if..then..else结构,将选定的密码哈希部分发送到检查服务,然后告诉我们密码是否已被泄露。

保存文件后,像这样为用户添加可执行权限:

chmod u+x pwnedpasswords.sh

现在,让我们看看TurkeyLips,我最喜欢的密码,是否已经泄露:

 [donnie@fedora-teaching ~]$ ./pwnedpasswords.sh TurkeyLips
 Candidate password: TurkeyLips
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 09FDEDF4CA44D6B432645D6C1D3A8D4A16BD:2
 100 21483 0 21483 0 0 107k 0 --:--:-- --:--:-- --:--:-- 107k
 Candidate password is compromised
 [donnie@fedora-teaching ~]$

是的,它确实已经被破解了。所以,我觉得我不想用它作为生产环境的密码。

现在,让我们再试一次,不过这次在末尾加上一个随机的两位数字:

 [donnie@fedora-teaching ~]$ ./pwnedpasswords.sh TurkeyLips98
 Candidate password: TurkeyLips98
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 100 20790 0 20790 0 0 110k 0 --:--:-- --:--:-- --:--:-- 110k
 Candidate password is OK for use
 [donnie@fedora-teaching ~]$

好吧,它说这个密码是可以的。不过,尽管如此,你可能还是不想使用这样一个已知被破解的简单密码排列。

我本想为我在这里展示的 Shell 脚本背书,但我做不到。那是我的朋友 Leo Dorrendorf(前 VDOO 物联网安全公司成员,后来被 JFrog 收购)创作的。 (我已经在他的许可下复制了这个脚本。)

如果你对物联网设备的安全解决方案感兴趣,可以在这里查看:

jfrog.com/security-and-compliance/?vr=1/

完全公开:VDOO/JFrog 公司曾是我的客户之一。

话虽如此,我还是需要提醒你,使用密码短语总比使用密码好。密码短语不仅更难破解,而且也更不容易出现在任何泄露的凭证列表中。

检测泄露密码的动手实验

在本实验中,你将使用pwnedpasswords API 来检查你自己的密码:

  1. 使用curl查看在密码哈希中包含21BD1字符串的密码数量:
curl https://api.pwnedpasswords.com/range/21BD1
  1. 在任何 Linux 虚拟机的主目录中,创建一个名为pwnpassword.sh的脚本,内容如下:
#!/bin/bash
candidate_password=$1
echo "Candidate password: $candidate_password"
full_hash=$(echo -n $candidate_password | sha1sum | awk '{print substr($1, 0, 32)}')
prefix=$(echo $full_hash | awk '{print substr($1, 0, 5)}')
suffix=$(echo $full_hash | awk '{print substr($1, 6, 26)}')
if curl https://api.pwnedpasswords.com/range/$prefix | grep -i $suffix;
        then echo "Candidate password is compromised";
        else echo "Candidate password is OK for use";
fi
  1. 给脚本添加可执行权限:
chmod u+x pwnedpasswords.sh
  1. 运行脚本,指定TurkeyLips作为密码:
./pwnedpasswords.sh TurkeyLips
  1. 重复步骤 4多次,每次使用不同的密码。

到目前为止,我们查看的用户管理技术在少量计算机上运行得非常好。但如果你在一个大型企业中工作呢?我们接下来会讨论这个问题。

理解集中式用户管理

在企业环境中,你通常会有数百或数千个用户和计算机需要管理。所以,登录到每个网络服务器或每个用户的工作站来执行我们刚才概述的程序是非常不切实际的。(但请记住,你仍然需要具备这些技能。)我们需要的是一种从一个中心位置管理计算机和用户的方法。由于篇幅限制,我无法提供有关执行此操作的各种方法的完整细节。所以,现在我们只能先进行高层次的概述。

Microsoft Active Directory

我并不是 Windows 或 Microsoft 的忠实粉丝。但在谈到 Active Directory 时,我得承认它确实很出色。它是一个相当流畅的产品,极大简化了非常大规模企业网络的管理。没错,确实可以将 Unix/Linux 计算机及其用户添加到 Active Directory 域中。

我一直有一个不为人知的秘密,我希望你不会因此讨厌我。在接触 Linux 之前,我获得了 Windows Server 2003 的 MCSE 认证。大多数时候,我的客户仅使用 Linux 计算机,但我偶尔还是需要用到 MCSE 技能。几年前,一位前客户需要我将一个基于 Linux 的 Nagios 服务器设置为 Windows Server 2008 域的一部分,以便其用户可以通过 Active Directory 进行身份验证。我花了些时间才弄清楚,但最终完成了,客户也很满意。

除非你像我一样经常需要身兼数职,否则作为一个 Linux 管理员,你可能不需要学习如何使用 Active Directory。你很可能只是告诉 Windows Server 管理员你需要什么,让他们来处理。

我知道,你已经迫不及待想看看我们能在 Linux 服务器上做些什么了。那么,现在开始吧。

Linux 上的 Samba

Samba 是一个 Unix/Linux 守护进程,可以实现三种功能:

  • 它的主要目的是将 Unix/Linux 服务器上的目录共享给 Windows 工作站。这些目录在 Windows 文件资源管理器中显示,就像是从其他 Windows 计算机共享的一样。

  • 它还可以设置为网络打印服务器。

  • 它还可以被设置为 Windows 域控制器。

你可以在 Linux 服务器上安装 Samba 3 版本,并将其配置为充当旧式的 Windows NT 域控制器。这是一个相当复杂的过程,需要一些时间。一旦完成,你可以将 Linux 和 Windows 计算机都加入到域中,并使用正常的 Windows 用户管理工具来管理用户和组。

Linux 社区的一项“圣杯”任务就是弄清楚如何在 Linux 服务器上模拟 Active Directory。这个目标在几年前随着 Samba 4 版本的推出成为了一种现实。但其设置过程非常复杂,可能不是你愿意做的事情。所以,也许我们应该继续寻找更好的方案。

RHEL 类型发行版上的 FreeIPA/身份管理

几年前,Red Hat 公司将 FreeIPA 引入为 Fedora 的一组软件包。为什么是 Fedora 呢?因为他们希望在将其提供给实际生产网络之前,在 Fedora 上进行彻底的测试。现在,它已经可以在 RHEL 7 到 RHEL 9 以及所有它们的衍生版本中使用,包括 CentOS 和 AlmaLinux。这就是 IPA 的含义:

  • 身份

  • 策略

  • 审计

它在某种程度上是对微软的 Active Directory 的回应,但仍然不是一个完整的解决方案。它能做一些很酷的事情,但仍然处于不断发展中。它最酷的一点就是安装和配置的简单性。其实只需要从正常的软件源安装软件包,打开合适的防火墙端口,然后运行一个设置脚本。接着,你就可以通过 FreeIPA 的 web 界面开始向新域添加用户和计算机了。这里,我正在添加 Cleopatara,我那只灰白相间的虎斑猫:

19501_02_07.png

19501_02_07.png

尽管你可以将 Windows 机器添加到 FreeIPA 域中,但并不推荐这样做。不过,从 RHEL/CentOS 7.1 开始,你可以使用 FreeIPA 与 Active Directory 域创建跨域信任。

这个程序的正式名称是 FreeIPA。但是,出于某种奇怪的原因,Red Hat 的开发人员在文档中拒绝提到这个名字,他们总是把它称为身份管理(Identity Management)或 IdM。

关于用户管理的话题就这些了。让我们总结一下,然后进入下一章。

总结

在这一章中,我们涉及了许多内容,希望你能找到一些实际可用的建议。我们从展示总是以 root 用户身份登录的风险开始,并说明你应该使用sudo。除了展示sudo的基本用法,我们还探讨了一些有用的sudo技巧和窍门。

我们通过学习如何锁定用户的主目录,如何强制执行强密码策略,以及如何强制执行账户和密码过期策略,开始了用户管理的内容。然后,我们讨论了防止暴力破解密码攻击的方法,如何手动锁定用户账户,如何设置安全横幅,以及如何检查是否存在被破坏的密码。最后,我们简要回顾了中央用户管理系统。

在下一章中,我们将探讨如何使用各种防火墙工具。我在那里见。

问题

  1. 授予用户管理权限的最佳方式是什么?

    1. 为每个管理员用户提供 root 用户密码。

    2. 将每个管理员用户添加到sudo组或wheel组中。

    3. 创建sudo规则,只允许管理员用户执行与他们工作直接相关的任务。

    4. 将每个管理员用户添加到sudoers文件中,并授予他们完全的管理员权限。

  2. 以下哪项是正确的?

    1. 当用户以 root 用户身份登录时,他们执行的所有操作将会被记录在auth.logsecure日志文件中。

    2. 当用户使用sudo时,他们执行的所有操作将会被记录在messagessyslog日志文件中。

    3. 当用户以 root 用户身份登录时,他们执行的所有操作将会被记录在messagessyslog日志文件中。

    4. 当用户使用sudo时,他们执行的所有操作将会被记录在auth.logsecure日志文件中。

  3. 在哪个文件中配置复杂的密码标准?

  4. 使用useradd工具时,/etc/login.defs文件中的UMASK设置应该是什么?

  5. 使用adduser工具时,如何配置/etc/adduser.conf文件,以确保新用户的主目录防止其他用户访问?

  6. 国家标准与技术研究院(NIST)最近对其推荐的密码政策做出了什么改变?

  7. 你会使用以下哪种方法为其他用户创建sudo规则?

    1. 用你喜欢的文本编辑器打开/etc/sudoers文件。

    2. 使用visudo打开/etc/sudoers文件。

    3. 为每个用户的主目录添加一个 sudoers 文件。

    4. 使用 visudo 打开 /var/spool/sudoers 文件。

  8. 以下哪三种工具可以用来设置用户账户的到期日期?

    1. useradd

    2. adduser

    3. usermod

    4. chage

  9. 为什么您可能希望锁定前员工的用户账户,而不是删除它?

    1. 锁定账户比删除账户更容易。

    2. 删除账户需要花费太长时间。

    3. 不可能删除一个用户账户。

    4. 删除一个用户账户及其文件和邮件存储可能会使您陷入法律麻烦。

  10. 您刚刚为 Samson 创建了一个用户账户,现在想强制他在第一次登录时更改密码。以下哪两个命令可以实现这一点?

    1. sudo chage -d 0 samson

    2. sudo passwd -d 0 samson

    3. sudo chage -e samson

    4. sudo passwd -e samson

  11. 以下哪项最能代表最佳的安全实践?

    1. 始终将 root 用户密码提供给所有需要执行管理任务的用户。

    2. 始终向所有需要执行管理任务的用户授予完整的 sudo 权限。

    3. 始终仅向所有需要执行管理任务的用户授予特定的、有限的 sudo 权限。

    4. 始终在常规文本编辑器(如 nano、vim 或 emacs)中编辑 sudoers 文件。

  12. 以下哪项陈述是正确的?

    1. sudo 只能在 Linux 上使用。

    2. sudo 可在 Linux、Unix 和 BSD 操作系统上使用。

    3. 当用户使用 sudo 执行任务时,该任务不会被记录在安全日志中。

    4. 使用 sudo 时,用户必须输入 root 用户的密码。

  13. 您希望特定用户编辑一个特定的系统配置文件,但不希望他们使用 shell 逃逸,这样就可以执行其他管理任务。以下哪项是您应该做的?

    1. sudoers 文件中,指定用户只能使用 vim 打开特定的配置文件。

    2. sudoers 文件中,指定用户可以使用 sudoedit 编辑特定的配置文件。

    3. sudoers 文件中,为这些用户指定 no shell escape 选项。

    4. sudoers 文件中,将这些用户放入一个没有 shell 逃逸权限的组中。

  14. 以下哪项是 adduser 工具相较于传统的 useradd 工具的优势?

    1. adduser 可以在 shell 脚本中使用。

    2. adduser 可用于所有 Linux 发行版。

    3. adduser 有一个选项,允许您在创建用户账户时加密用户的主目录。

    4. adduser 也可用于 Unix 和 BSD。

  15. 在最新的 Linux 发行版中,您会使用哪种 PAM 来强制实施强密码?

    1. cracklib

    2. 密码

    3. 安全

    4. pwquality

进一步阅读

第四章:3 保护普通用户账户

加入我们的 Discord 图书社区

packt.link/SecNet

管理用户是 IT 管理中最具挑战性的方面之一。你需要确保用户始终可以访问他们的文件,并能够执行完成工作所需的任务。你还需要确保用户的文件始终对未经授权的用户保持安全。在本章中,我们将探讨如何锁定用户账户和用户凭证,以保护它们免受攻击者和窥探者的侵害。最后,我们将简要介绍一些集中式用户管理系统。

  • 锁定用户的主目录

  • 强制执行强密码标准

  • 设置并强制执行密码和账户到期策略

  • 防止暴力破解密码攻击

  • 锁定用户账户

  • 设置安全横幅

  • 检测被盗密码

  • 了解集中式用户管理系统

按 Red Hat 方式锁定用户的主目录

这是另一个不同 Linux 发行版家族在操作上有差异的领域。正如我们所见,每个发行版家族在用户主目录的默认安全设置上有所不同。一个管理多种 Linux 发行版混合环境的安全管理员需要考虑这一点。

传统上,Red Hat 企业版 Linux 及其所有后代,如 CentOS 和 AlmaLinux,提供比其他任何 Linux 发行版家族更好的开箱即用安全性。这使得加固 Red Hat 类型系统变得更快捷、更容易,因为很多工作已经完成。已经为我们做好的事情之一就是锁定用户的主目录:

 [donnie@localhost home]$ sudo useradd charlie
 [sudo] password for donnie:
 [donnie@localhost home]$
 [donnie@localhost home]$ ls -l
 total 0
 drwx------. 2 charlie charlie 59 Oct 1 15:25 charlie
 drwx------. 2 donnie donnie 79 Sep 27 00:24 donnie
 drwx------. 2 frank frank 59 Oct 1 15:25 frank
 [donnie@localhost home]$

默认情况下,Red Hat 类型系统上的 useradd 工具会创建权限设置为 700 的用户主目录。这意味着只有拥有主目录的用户可以访问它,所有其他普通用户都被锁定在外。我们可以通过查看 /etc/login.defs 文件来理解原因。在你的 CentOS 7 虚拟机上,向文件底部滚动,你会看到这样一行:

CREATE_HOME yes
UMASK 077

在 RHEL 8 或 RHEL 9 类型的发行版(如 AlmaLinux)的login.defs文件中,你会看到UMASK设置为宽松权限,这看起来有点奇怪。它长这样:

UMASK           022

但是,在其下方几行,你会看到一个我们以前从未见过的新指令,长这样:

HOME_MODE       0700

所以,尽管 UMASK 权限设置较为宽松,新创建的用户主目录仍然会得到正确的锁定。

login.defs 文件是配置 useradd 默认设置的两个文件之一。UMASK 行或 HOME_MODE 行决定了主目录创建时的权限值。Red Hat 类型的发行版默认配置了 077 值,这会删除组和其他用户的所有权限。所有 Linux 发行版的 login.defs 文件中都有 HOME_MODE 行或 UMASK 行,但直到最近,只有 Red Hat 类型的发行版才默认将它们设置为如此严格的值。大多数非 Red Hat 发行版通常将 UMASK 值设置为 022,这会创建权限值为 755 的主目录。这样,所有人都可以进入其他用户的主目录并访问彼此的文件。

以 Debian/Ubuntu 的方式锁定用户的主目录

Debian 及其衍生版本,如 Ubuntu,提供了两个用户创建工具:

  • useradd

  • adduser

让我们看看它们两个的情况。

Debian/Ubuntu 上的 useradd

useradd 工具是可用的,但 Debian 和 Ubuntu 并不像 Red Hat 类型的发行版那样提供方便的预配置默认设置。如果你仅在 Debian/Ubuntu 系统上执行 sudo useradd frank,Frank 将没有主目录,并且会被分配错误的默认 shell。所以,要在 Debian 或 Ubuntu 系统上使用 useradd 创建用户帐户,命令应该像这样:

sudo useradd -m -d /home/frank -s /bin/bash frank

下面是这些命令的详细说明:

  • -m 创建主目录。

  • -d 指定主目录。

  • -s 指定 Frank 的默认 shell。(如果没有 -s,Debian/Ubuntu 将为 Frank 分配 /bin/sh shell。)

当你查看 Debian 或 Ubuntu 20.04 系统上的主目录时,你会发现它们完全开放,所有人都可以执行和读取:

 donnie@packt:/home$ ls -l
 total 8
 drwxr-xr-x 3 donnie donnie 4096 Oct 2 00:23 donnie
 drwxr-xr-x 2 frank frank 4096 Oct 1 23:58 frank
 donnie@packt:/home$

正如你所看到的,Frank 和我可以访问对方的文件。(不,我不希望 Frank 进入我的文件。)每个用户可以更改自己目录的权限,但你有多少用户知道怎么做呢?所以,让我们自己来修复这个问题:

 cd /home
 sudo chmod 700 *

让我们看看现在的情况:

 donnie@packt:/home$ ls -l
 total 8
 drwx------ 3 donnie donnie 4096 Oct 2 00:23 donnie
 drwx------ 2 frank frank 4096 Oct 1 23:58 frank
 donnie@packt:/home$

这样看起来好多了。

要更改主目录的默认权限设置,请打开 /etc/login.defs 文件进行编辑。找到以下行:

UMASK 022

将其更改为如下:

UMASK 077

现在,新的用户主目录在创建时会被锁定,就像 Red Hat 及其衍生版一样。

在 Ubuntu 22.04 上,情况有所不同。Ubuntu 开发者终于意识到,用户的主目录应该默认被锁定。因此,Ubuntu 22.04 中的 login.defs 文件中的 HOME_MODE 设置现在是这样的:

HOME_MODE       0750

这包括用户自己个人组的访问权限,但这没关系。它仍然有效地意味着,只有各自的主目录所有者才能访问。

Debian/Ubuntu 上的 adduser

adduser工具是一种交互式创建用户账户和密码的方式,这是 Debian 系列 Linux 发行版特有的。Debian 实现的useradd缺失的大多数默认设置都已在adduser中设置好。在 Debian 和 Ubuntu 20.04 中,它创建用户主目录时会赋予755的宽松权限值。幸运的是,这是可以轻松更改的。(我们稍后将展示如何更改。)在 Ubuntu 22.04 中,它创建了正确锁定的主目录,权限值为限制性750。你会在/etc/adduser.conf文件中看到这一设置,大约在第 56 行附近:

DIR_MODE=750

正如我之前提到的,在 Ubuntu 20.04 中,这将设置为755权限值。只需将其更改为750即可锁定新创建的主目录。

尽管adduser非常适合用于简单地创建用户账户,但它没有useradd那样的灵活性,也不适合在 Shell 脚本中使用。adduser能做的一件事是,useradd做不到的,那就是在创建账户时自动加密用户的主目录。要使其正常工作,首先需要安装ecryptfs-utils包。因此,要为 Cleopatra 创建一个带有加密主目录的账户,您需要执行以下操作:

donnie@ubuntu-steemnode:~$ sudo apt install ecryptfs-utils
donnie@ubuntu-steemnode:~$ sudo adduser --encrypt-home cleopatra
 [sudo] password for donnie:
 Adding user `cleopatra' ...
 Adding new group `cleopatra' (1004) ...
 Adding new user `cleopatra' (1004) with group `cleopatra' ...
 Creating home directory `/home/cleopatra' ...
 Setting up encryption ...
 ************************************************************************
 YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IT IN A SAFE LOCATION.
  ecryptfs-unwrap-passphrase ~/.ecryptfs/wrapped-passphrase
 THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME.
 ********************************************************************
Done configuring.
 Copying files from `/etc/skel' ...
 Enter new UNIX password:
 Retype new UNIX password:
 passwd: password updated successfully
 Changing the user information for cleopatra
 Enter the new value, or press ENTER for the default
  Full Name []: Cleopatra Tabby Cat
  Room Number []: 1
  Work Phone []: 555-5556
  Home Phone []: 555-5555
  Other []:
 Is the information correct? [Y/n] Y
 donnie@ubuntu-steemnode:~$
The first time that Cleopatra logs in, she'll need to run the ecryptfs-unwrap-passphrase command that's mentioned in the preceding output. She'll then want to write her passphrase down and store it in a safe place:
 cleopatra@ubuntu-steemnode:~$ ecryptfs-unwrap-passphrase
 Passphrase:
 d2a6cf0c3e7e46fd856286c74ab7a412
 cleopatra@ubuntu-steemnode:~$

当我们进入加密章节时,将更详细地讨论整个加密过程。

使用adduser创建加密的主目录实验室

在本实验中,我们将在 Ubuntu 22.04 虚拟机上使用adduser工具。

  1. 安装ecryptfs-utils包:
sudo apt install ecryptfs-utils
  1. 为 Cleopatra 创建一个带有加密主目录的用户账户,然后查看结果:
sudo adduser --encrypt-home cleopatra
ls -l /home
  1. 以 Cleopatra 身份登录并运行ecryptfs-unwrap-passphrase命令:
su - cleopatra
ecryptfs-unwrap-passphrase
exit

请注意,adduser要求的某些信息是可选的,你可以直接按Enter键跳过这些项目。

强制实施强密码标准

你可能不会想到,看似无害的主题,如强密码标准,竟然会如此有争议,但事实的确如此。你在整个计算机生涯中无疑听过的传统观点是:

  • 密码需要达到一定的最小长度。

  • 创建由大写字母、小写字母、数字和特殊字符组合构成的密码。

  • 确保密码不包含词典中的任何单词,或不基于用户的个人数据。

  • 强制用户定期更改密码。

但通过使用你最喜欢的搜索引擎,你会发现不同的专家在这些标准的细节上存在分歧。例如,你会看到关于密码是否应该每 30、60 或 90 天更改的分歧,关于密码中是否必须包含所有四种类型的字符的分歧,甚至对于密码的最小长度应该是多少也存在不同的看法。

最有趣的争议来自——居然是——最初制定前述标准的那位人物。现在他说这一切都是废话,他后悔自己曾经提出这些标准。他现在表示,我们应该使用既长又容易记住的密码短语,而且只有在密码被泄露的情况下,才需要更改密码。

Bill Burr,这位曾在国家标准与技术研究院(NIST)工作的工程师,制定了我之前提到的强密码标准,现在分享了他为什么否定自己工作的原因。你可以参考www.pcmag.com/news/355496/you-might-not-need-complex-alphanumeric-passwords-after-all.

而且,自从这本书的初版出版以来,NIST 已经同意了 Bill Burr 的观点。他们现在已经更改了密码实施标准,以符合 Burr 先生的建议。你可以在这里阅读相关内容:

www.riskcontrolstrategies.com/2018/01/08/new-nist-guidelines-wrong/

然而,话虽如此,现实情况是,许多组织仍然坚持使用复杂的密码,并且要求密码定期过期,如果你无法说服他们改变这一做法,你将不得不遵守他们的规则。而且,既然你使用的是传统密码,你当然希望它们足够强大,能够抵御任何形式的密码攻击。那么现在,让我们来看看如何在 Linux 系统上强制执行强密码标准。

提示:

我必须承认,我之前从未想到过在 Linux 系统中使用密码短语代替密码。因此,我刚刚在我的 CentOS 虚拟机上尝试了一下,看看它是否有效。

我为 Maggie, 我的黑白色燕尾服小猫,创建了一个账户。她的密码短语是 I like other kitty cats。你可能会想,“哦,这太糟糕了。这个不符合任何复杂性标准,而且使用了字典中的词汇。这怎么可能安全呢?”但是,正因为它是一个包含不同单词并由空格分开的短语,它的安全性确实很高,并且非常难以通过暴力破解来攻破。

现在,在现实生活中,我绝不会创建一个表达我对猫咪的喜爱的密码短语,因为很容易发现我确实非常喜欢猫咪。相反,我会选择一个关于我生活中更为隐秘的部分的密码短语,只有我自己知道。无论如何,密码短语相比传统密码有两个优势。它们比传统密码更难破解,同时又更容易让用户记住。不过,为了额外的安全性,最好不要选择任何所有人都知道的生活事实来作为密码短语。

安装和配置 pwquality

我们将使用 pwquality 模块来实现 可插拔认证模块PAM)。这是一个较新的技术,已替代了旧的 cracklib 模块。在任何 Red Hat 7 或更高版本的系统上,甚至在做最小化安装时,pwquality 都会默认安装。在 /etc/pam.d/ 目录下,你可以通过 grep 命令来查看 PAM 配置文件是否已设置好。retry=3 表示用户在登录系统时只能尝试三次输入正确密码:

[donnie@localhost pam.d]$ grep 'pwquality' *
 password-auth:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 password-auth-ac:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 system-auth:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 system-auth-ac:password requisite pam_pwquality.so try_first_pass
 local_users_only retry=3 authtok_type=
 [donnie@localhost pam.d]$

在 Debian 和 Ubuntu 上,你需要自行安装 pwquality,可以像这样操作:

sudo apt install libpam-pwquality

其余过程对所有操作系统都是相同的,只需编辑 /etc/security/pwquality.conf 文件。当你在文本编辑器中打开该文件时,你会看到所有内容都被注释掉了,这意味着没有密码复杂性要求生效。你还会看到它有很好的文档说明,因为每个设置都有自己的注释。

你可以通过取消注释相应的行并设置适当的值来按需设置密码复杂性标准。让我们看一个设置:

# Minimum acceptable size for the new password (plus one if 
# credits are not disabled which is the default). (See pam_cracklib manual.) 
# Cannot be set to lower value than 6\. 
# minlen = 8 

最小长度设置是基于信用系统的。这意味着对于密码中的每种不同类型的字符类,最小要求的密码长度将减少一个字符。例如,假设我们将 minlen 设置为 19,并尝试为 Katelyn 设置密码 turkeylips

minlen = 19
[donnie@localhost ~]$ sudo passwd katelyn
 Changing password for user katelyn.
 New password:
 BAD PASSWORD: The password is shorter than 18 characters
 Retype new password:
 [donnie@localhost ~]$

因为 turkeylips 中的小写字符算作一种字符类的信用,所以我们只需要 18 个字符,而不是 19 个。如果我们使用 TurkeyLips 再试一次,结果会是:

[donnie@localhost ~]$ sudo passwd katelyn
 Changing password for user katelyn.
 New password:
 BAD PASSWORD: The password is shorter than 17 characters
 Retype new password:
 [donnie@localhost ~]$

这次,大写字母 TL 计为第二种字符类,因此密码只需要包含 17 个字符。

minlen 行下面,你会看到信用行。假设你不希望小写字母计入信用,你会看到这一行:

# lcredit = 1 

取消注释,并将 1 改为 0

lcredit = 0

然后,尝试为 Katelyn 分配 turkeylips 作为密码:

[donnie@localhost ~]$ sudo passwd katelyn
 Changing password for user katelyn.
 New password:
 BAD PASSWORD: The password is shorter than 19 characters
 Retype new password:
 [donnie@localhost ~]$

这次,pwquality 确实要求 19 个字符。如果我们将信用值设置为大于 1,我们将为相同字符类的多个字符获得信用,直到达到该值。

我们还可以将信用值设置为负数,以便要求密码中包含一定数量的字符类型。例如,我们可以这样设置:

dcredit = -3

这将需要密码至少包含三位数字。然而,使用此功能是一个非常糟糕的主意,因为进行密码攻击的人很快就能发现你所要求的模式,这将帮助攻击者更精确地进行攻击。如果你需要要求密码包含多种字符类型,最好使用minclass参数:

# minclass = 3

它已经设置为 3 的值,这要求字符来自三个不同的类别。要使用这个值,你只需去掉注释符号即可。

pwquality.conf 中的其他参数几乎以相同的方式工作,每个参数都有详细的注释来解释它的作用。

提示:

如果你使用 sudo 权限为其他人设置密码,如果创建的密码不符合复杂度要求,系统会提示警告,但你仍然可以设置。如果普通用户尝试在没有 sudo 权限的情况下更改自己的密码,系统将不允许设置不符合复杂度要求的密码。

设置密码复杂度标准的实践实验

对于此实验,你可以根据需要使用 CentOS、AlmaLinux 或 Ubuntu 虚拟机。唯一的区别是,对于 CentOS 或 AlmaLinux,你不会执行步骤 1。

  1. 仅适用于 Ubuntu,安装 libpam-pwquality 包:
sudo apt install libpam-pwquality
  1. 打开 /etc/security/pwquality.conf 文件,使用你喜欢的文本编辑器。去掉 minlen 行前面的注释符号,并将其值更改为 19。它现在应该是这样的:
 minlen = 19

保存文件并退出编辑器。

  1. 为 Goldie 创建一个用户账户,并尝试为她设置密码 turkeylipsTurkeyLipsTurkey93Lips。注意每个警告消息中的变化。

  2. pwquality.conf 文件中,注释掉 minlen 行。取消注释 minclass 行和 maxclassrepeat 行。将 maxclassrepeat 的值更改为 5。这些行现在应该是这样的:

minclass = 3 
maxclassrepeat = 5 

保存文件并退出文本编辑器。

  1. 尝试为 Goldie 的账户分配一些不符合你设置的复杂度标准的密码,并查看结果。

在 CentOS 7 机器上的 /etc/login.defs 文件中,你会看到这一行 PASS_MIN_LEN 5

这应该是用来设置密码的最小长度,但实际上,pwquality 会覆盖它。因此,你可以将此值设置为任何内容,它将不会产生任何效果。(注意,PASS_MIN_LEN 参数在 RHEL 8/9 类型的发行版中不再支持。)

设置并强制执行密码和账户过期

你绝对不希望的是未使用的用户账户保持活跃。曾经有过这样的事件,管理员为临时使用设置了用户账户,比如为一个会议设置账户,结果在账户不再需要后忘记了它们。

另一个例子是,如果你的公司雇佣了合同工,且合同在某个特定日期到期。允许这些账户在临时员工离开公司后仍然保持活跃和可访问,将会是一个巨大的安全问题。在这种情况下,你需要一种方法来确保在不再需要这些临时账户时,不会忘记它们。如果你的公司遵循常规的安全做法,要求用户定期更改密码,那么你还需要确保它能够得到执行。

密码过期数据和账户过期数据是两种不同的东西。它们可以分别或一起设置。当某人的密码过期时,他或她可以更改密码,一切都能恢复正常。如果某人的账户过期,只有具有适当管理员权限的人才能解锁账户。

要开始,先查看一下自己账户的过期数据。请注意,你无需 sudo 权限就能查看自己的数据,但仍然需要指定自己的用户名:

donnie@packt:~$ chage -l donnie
 [sudo] password for donnie:
 Last password change : Oct 03, 2017
 Password expires : never
 Password inactive : never
 Account expires : never
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@packt:~$

你可以看到这里没有设置任何过期数据。一切都根据系统的默认设置进行配置。除了显而易见的内容外,这里是你看到的详细内容:

  • 密码非活动期:如果设置为正数,我将在密码过期后有这么多天的时间来更改密码,否则系统将锁定我的账户。

  • 密码更改的最小天数:因为它被设置为 0,所以我可以随时更改我的密码。如果设置为正数,那么在更改密码后,我必须等待设定的天数才能再次更改密码。

  • 密码更改的最大天数:默认为 99999,意味着我的密码永远不会过期。

  • 密码过期前的警告天数:默认值为 7,但当密码设置为永不过期时,这个值就没有太大意义。

使用 chage 工具,你可以为其他用户设置密码和账户的过期数据,或者使用 -l 选项查看过期数据。任何非特权用户都可以使用 chage -l 在不使用 sudo 的情况下查看自己的数据。要设置数据或查看其他人的数据,你需要 sudo 权限。稍后我们将更详细地了解 chage

在我们讨论如何更改过期数据之前,首先来看一下默认设置存储的位置。我们将首先查看 /etc/login.defs 文件。以下是三行相关的内容:

PASS_MAX_DAYS 99999 
PASS_MIN_DAYS 0 
PASS_WARN_AGE 7

你可以根据组织的需要编辑这些值。例如,将 PASS_MAX_DAYS 更改为 30,那么从那时起所有新用户的密码将具有 30 天的过期日期。(顺便说一下,在 login.defs 中设置默认的密码过期数据适用于我们使用的所有 Linux 发行版。)

仅适用于 Red Hat 类型系统的 useradd 默认过期数据配置

/etc/default/useradd 文件包含了其余的默认设置。在这种情况下,我们将查看来自 AlmaLinux 9 机器的文件:

Ubuntu 也有 useradd 配置文件,但它不起作用。无论如何配置,Ubuntu 版本的 useradd 就无法读取它。所以,关于该文件的说明仅适用于 Red Hat 类型的系统。

# useradd defaults file
GROUP=100
HOME=/home 
INACTIVE=-1 
EXPIRE= 
SHELL=/bin/bash 
SKEL=/etc/skel 
CREATE_MAIL_SPOOL=yes

EXPIRE= 行设置新用户账户的默认过期日期。默认情况下,没有默认过期日期。INACTIVE=-1 表示用户密码过期后,用户账户不会自动被锁定。如果我们将其设置为正数,则任何新用户必须在账户被锁定之前的规定天数内更改过期的密码。要更改 useradd 文件中的默认值,可以手动编辑文件,或者使用 useradd -D 并搭配相应的选项切换来更改你想修改的项目。例如,要设置默认过期日期为 2025 年 12 月 31 日,可以使用如下命令:

sudo useradd -D -e 2025-12-31

要查看新配置,可以打开 useradd 文件,或者直接运行 sudo useradd -D

[donnie@localhost ~]$ sudo useradd -D
 GROUP=100
 HOME=/home
 INACTIVE=-1
 EXPIRE=2023-12-31
 SHELL=/bin/bash
 SKEL=/etc/skel
 CREATE_MAIL_SPOOL=yes
 [donnie@localhost ~]$

你现在已设置所有新创建的用户账户将具有相同的过期日期。你可以使用相同的方法设置 INACTIVESHELL 配置:

sudo useradd -D -f 5
sudo useradd -D -s /bin/zsh
 [donnie@localhost ~]$ sudo useradd -D
 GROUP=100
 HOME=/home
 INACTIVE=5
 EXPIRE=2019-12-31
 SHELL=/bin/zsh
 SKEL=/etc/skel
 CREATE_MAIL_SPOOL=yes
 [donnie@localhost ~]$

现在,任何新创建的用户账户将默认使用 Zsh 作为默认 shell,并且必须在五天内更改过期密码,否则账户将被自动锁定。

useradd 不进行任何安全检查,确保你指定的默认 shell 已安装在系统上。在我们的例子中,Zsh 没有安装,但 useradd 仍然允许你将 Zsh 设置为默认 shell 创建账户。

那么,useradd 配置功能在实际生活中有多有用呢?可能不太有用,除非你需要一次性创建大量相同设置的用户账户。即便如此,聪明的管理员会选择通过 shell 脚本自动化这一过程,而不是手动调整配置文件。

使用 useraddusermod 设置每个账户的过期数据。

你可能会发现设置 login.defs 中的默认密码过期日期非常有用,但配置 useradd 配置文件可能用处不大。实际上,创建所有用户账户都使用相同的过期日期的可能性并不大吧?在 login.defs 中设置密码过期日期更有用,因为你只是简单地指定新密码会在一定天数内过期,而不是所有密码都在一个特定日期过期。

很可能,你会想在每个账户的基础上设置账户过期数据,具体取决于你是否知道某个账户将在特定日期后不再需要。有三种方法可以做到这一点:

  • 使用 useradd 并搭配适当的选项切换在创建账户时设置过期数据。(如果你需要一次性创建多个账户,并且希望它们有相同的过期数据,可以使用 shell 脚本自动化该过程。)

  • 使用 usermod 修改现有账户的过期数据。(usermod 的优点是它使用与 useradd 相同的选项开关。)

  • 使用 chage 修改现有账户的过期数据。(这使用了一整套不同的选项开关。)

您可以使用useraddusermod来设置账户过期数据,但不能用来设置密码过期数据。影响账户过期数据的仅有两个选项开关如下:

  • -e:使用此选项为账户设置一个过期日期,格式为 YYYY-MM-DD。

  • -f:使用此选项设置密码过期后,账户锁定之前的天数。

假设您想为 Charlie 创建一个将在 2025 年底过期的账户。在 Red Hat 类型的机器上,您可以输入以下命令:

sudo useradd -e 2025-12-31 charlie

在非 Red Hat 类型的机器上,您需要添加创建主目录并分配正确默认 shell 的选项开关:

sudo useradd -m -d /home/charlie -s /bin/bash -e 2025-12-31 charlie

使用chage -l验证您所输入的信息:

donnie@ubuntu-steemnode:~$ sudo chage -l charlie
 Last password change : Oct 06, 2017
 Password expires : never
 Password inactive : never
 Account expires : Dec 31, 2025
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@ubuntu-steemnode:~$

现在,假设 Charlie 的合同已被延长,您需要将他的账户过期日期更改为 2026 年 1 月末。您可以像在任何 Linux 发行版上一样使用usermod

sudo usermod -e 2026-01-31 charlie

再次通过chage -l验证所有信息是否正确:

donnie@ubuntu-steemnode:~$ sudo chage -l charlie
 Last password change : Oct 06, 2017
 Password expires : never
 Password inactive : never
 Account expires : Jan 31, 2026
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@ubuntu-steemnode:~$

可选地,您可以设置密码过期后账户被锁定前的天数:

sudo usermod -f 5 charlie

但如果您现在执行此操作,您在chage -l输出中不会看到任何变化,因为我们还没有为 Charlie 的密码设置过期数据。

使用 chage 按账户单独设置过期日期

您只能使用chage修改现有账户,并且可以用它来设置账户过期或密码过期。以下是相关的选项开关:

选项 说明
-d 如果在某个账户上使用-d 0选项,将强制用户在下次登录时更改密码。
-E 这相当于useraddusermod中的小写-e选项。它设置用户账户的过期日期。
-I 这相当于useraddusermod-f选项。它设置了密码过期后,账户将被锁定之前的天数。
-m 设置密码更改之间的最小天数。换句话说,如果 Charlie 今天更改了密码,-m 5选项将强制他等待五天才能再次更改密码。
-M 该选项设置密码过期之前的最大天数。(但请注意,如果 Charlie 上次更改密码已经是 89 天前,使用-M 90选项将导致他的密码在明天过期,而不是 90 天后。)
-W 该选项设置密码即将过期时的警告天数。

您可以一次只设置其中一个数据项,也可以一次性设置所有数据项。事实上,为了避免每个单独项目演示给您带来的困扰,我们将一次性设置所有数据项,除了-d 0,然后查看结果:

donnie@ubuntu-steemnode:~$ sudo chage -E 2026-02-28 -I 4 -m 3 -M 90 -W 4 charlie
donnie@ubuntu-steemnode:~$ sudo chage -l charlie
 Last password change : Oct 06, 2019
 Password expires : Jan 04, 2026
 Password inactive : Jan 08, 2026
 Account expires : Feb 28, 2026
 Minimum number of days between password change : 3
 Maximum number of days between password change : 90
 Number of days of warning before password expires : 4
donnie@ubuntu-steemnode:~$

所有过期数据现在已设置完成。

在我们的最后一个示例中,假设你刚为 Samson 创建了一个新账户,并且你希望在他第一次登录时强制他更改密码。有两种方法可以实现这一点。不管哪种方法,你都应该在最初设置他的密码后,通过以下两个命令之一来操作:

sudo chage -d 0 samson
 or
sudo passwd -e samson

 donnie@ubuntu-steemnode:~$ sudo chage -l samson
 Last password change : password must be changed
 Password expires : password must be changed
 Password inactive : password must be changed
 Account expires : never
 Minimum number of days between password change : 0
 Maximum number of days between password change : 99999
 Number of days of warning before password expires : 7
 donnie@ubuntu-steemnode:~$

接下来,我们将进行一个动手实验。

设置账户和密码过期数据的动手实验

在这个实验中,你将创建几个新的用户账户,设置有效期,并查看结果。你可以在任何虚拟机上进行这个实验。唯一的不同将是在useradd命令上:

  1. 在你的 CentOS 或 AlmaLinux 虚拟机上,为 Samson 创建一个账户,并设置有效期为 2025 年 6 月 30 日,然后查看结果:
sudo useradd -e 2025-06-30 samson
sudo chage -l samson

对于 Ubuntu,运行以下命令:

sudo useradd -m -d /home/samson -s /bin/bash -e 2025-06-30 samson
sudo chage -l samson
  1. 使用usermod命令将 Samson 账户的有效期修改为 2025 年 7 月 31 日:
sudo usermod -e 2025-07-31 samson
sudo chage -l samson
  1. 为 Samson 的账户设置一个密码,然后强制他在第一次登录时更改密码。以 Samson 身份登录,修改密码,然后再登录到你自己的账户:
sudo passwd samson
sudo passwd -e samson
sudo chage -l samson
su - samson
exit
  1. 使用chage命令设置一个五天的密码更改等待期、90 天的密码过期期、两天的不活动期和五天的警告期:
sudo chage -m 5 -M 90 -I 2 -W 5 samson
sudo chage -l samson
  1. 保留这个账户,因为你将在下一节的实验中使用它。

接下来,让我们看看如何防止暴力破解攻击。

防止暴力破解密码攻击

非常令人惊讶的是,这也是一个引发争议的话题。我的意思是,没人会否认自动锁定受到攻击的用户账户是明智的。争议部分在于我们应该允许多少次失败的登录尝试后再锁定账户。

在计算机的石器时代,那时候我还拥有一头浓密的头发,早期的 Unix 操作系统只允许用户创建一个最多只能包含八个小写字母的密码。所以在那个时代,早期的人类只需要坐在键盘前随便输入随机的密码,就能暴力破解别人密码。那时,用户账户在三次登录失败后被锁定的理念就开始了。现在,使用强密码,或者更好的是强密码短语,将锁定值设置为三次登录失败将做到三件事:

  • 这会不必要地让用户感到沮丧。

  • 这会给帮助台人员带来额外的工作。

  • 如果账户真的遭到攻击,它会在你有机会收集攻击者信息之前就锁定账户。

将锁定值设置为更现实的数字,比如 100 次失败的登录尝试,依然能提供良好的安全性,同时给你足够的时间收集有关攻击者的信息。同样重要的是,这样做不会给用户和帮助台人员带来不必要的沮丧。

无论你允许多少次失败登录尝试,你仍然需要知道如何进行配置。在 RHEL 7 类型的系统和 Ubuntu 18.04 上,你将通过配置 pam_tally2 可插拔认证模块 (PAM) 来完成此操作。在 RHEL 8/9 类型的系统和 Ubuntu 20.04/22.04 上,你将改为配置 pam_faillock PAM 模块。让我们深入了解一下如何完成配置。

配置 CentOS 7 上的 pam_tally2 PAM 模块

为了让这项操作成功,我们将依赖我们的好朋友 PAM。pam_tally2 模块已经预装在 CentOS 7 上,但尚未配置。我们将从编辑 /etc/pam.d/login 文件开始。配置它非常简单,因为在 pam_tally2 的手册页底部有一个示例:

EXAMPLES
 Add the following line to /etc/pam.d/login to lock the account after
4 failed logins. Root account will be locked as well. The accounts will be
automatically unlocked after 20 minutes. The module does not have to be
called in the account phase because the login calls pam_setcred(3)
correctly.
 auth required pam_securetty.so
 auth required pam_tally2.so deny=4 even_deny_root
unlock_time=1200
 auth required pam_env.so
 auth required pam_unix.so
 auth required pam_nologin.so
 account required pam_unix.so
 password required pam_unix.so
 session required pam_limits.so
 session required pam_unix.so
 session required pam_lastlog.so nowtmp
 session optional pam_mail.so standard

提示:

如果你在使用文本模式的服务器,你只需要配置 /etc/pam.d/login 文件。但如果你使用的是运行图形桌面环境的机器,还需要配置 /etc/pam.d/password.auth/etc/pam.d/system.auth 文件。你将在实操实验中看到如何进行配置。

在示例的第二行,我们看到 pam_tally2 设置了以下参数:

  • deny=4:这意味着在仅进行四次失败登录尝试后,受攻击的用户账户将被锁定。

  • even_deny_root:这意味着即使是 root 用户账户也会在遭遇攻击时被锁定。

  • unlock_time=1200:账户将在 1200 秒,即 20 分钟后自动解锁。

现在,如果你查看虚拟机上的实际 login 文件,你会发现它看起来与手册页中的示例 login 文件略有不同。没关系,我们依然可以让它正常工作。

一旦你配置了 login 文件并尝试登录失败,你将在 /var/log 目录下看到一个新文件的生成。你可以使用 pam_tally2 工具查看该文件中的信息。如果你不想等待超时期,也可以使用 pam_tally2 手动解锁被锁定的账户:

donnie@centos7:~$ sudo pam_tally2
 Login Failures Latest failure From
 charlie 5 10/07/17 16:38:19
 donnie@centos7:~$ sudo pam_tally2 --user=charlie --reset
 Login Failures Latest failure From
 charlie 5 10/07/17 16:38:19
 donnie@centos7:~$ sudo pam_tally2
 donnie@centos7:~$

注意,在我重置了 Charlie 的账户后,我没有收到再次查询的任何输出。

在 CentOS 7 上配置 pam_tally2 的实操实验

配置 pam_tally2 非常简单,因为它只需要在 /etc/pam.d/login/etc/pam.d/password.auth/etc/pam.d/system.auth 文件中添加一行。为了让这一步更简单,你可以直接从 pam_tally2 的手册页中的示例中复制并粘贴该行。尽管我之前提到过将失败登录次数设为 100,但我们目前将该数字保持为 4,因为我知道你不想做 100 次失败登录来演示这一过程:

  1. 在 CentOS 7 虚拟机上,打开 /etc/pam.d/login 文件进行编辑。查找调用 pam_securetty 模块的那一行。(大约在第二行。)在该行下面插入这一行:
auth required pam_tally2.so deny=4 even_deny_root unlock_time=1200

保存文件并退出编辑器。

/etc/pam.d/password.auth/etc/pam.d/system.auth文件的顶部,放置相同的行,位于第一个auth required行的上方。(这些文件顶部的注释说不要手动编辑它们,因为运行authconfig会覆盖这些编辑。不幸的是,你必须手动编辑它们,因为authconfig无法为你配置此内容。)

  1. 在这一步,你需要注销当前账户,因为pam_tally2无法与su一起使用。所以注销后,在故意输入错误密码的情况下,尝试登录之前在实验中创建的samson账户。一直这样做,直到看到账户被锁定的消息。请注意,当deny值设置为4时,实际上需要五次失败的登录尝试才能锁定 Samson 账户。

  2. 重新登录到你的用户账户。运行此命令并注意输出:

sudo pam_tally2
  1. 对于这一步,你将模拟自己是一个帮助台工作人员,而 Samson 刚刚打电话要求解锁他的账户。在确认你确实是在与真正的 Samson 通话后,输入以下两条命令:
sudo pam_tally2 --user=samson --reset
sudo pam_tally2
  1. 现在你已经了解了它是如何工作的,打开/etc/pam.d/login文件进行编辑。将deny=参数从4改为100并保存文件。(这将使你的配置更符合现代安全理念。)

接下来,让我们看看如何在 AlmaLinux 机器上配置pam_faillock

配置 AlmaLinux 8/9 上的pam_faillock

pam_faillock模块已经在任何 RHEL 8 或 RHEL 9 类型的 Linux 发行版中安装。由于pam_faillock的基本概念与pam_tally2几乎相同,因此我们将跳过初步的解释,直接进入动手操作步骤。

配置pam_faillock的动手实验,适用于 AlmaLinux 8 或 AlmaLinux 9

虽然你可以通过手动编辑 PAM 配置文件来启用并配置pam_faillock,但 RHEL 发行版提供了一种更简单的方法,叫做authselect。(请注意,这个过程在文本模式或图形用户界面机器上完全相同。)

  1. 在 AlmaLinux 8 或 AlmaLinux 9 的虚拟机上,通过以下命令查看可用的 authselect 配置文件:
[donnie@localhost ~]$ sudo authselect list
- minimal    Local users only for minimal installations
- sssd       Enable SSSD for system authentication (also for local users only)
- winbind    Enable winbind for system authentication
[donnie@localhost ~]$
  1. 至少目前,我们只处理本地用户。所以,我们将使用最小化配置文件。查看此配置文件的功能,像这样:
[donnie@localhost ~]$ sudo authselect list-features minimal
. . .
. . .
with-faillock
. . .
. . .
[donnie@localhost ~]$

请注意,这里有很多包含的功能,但我们只对with-faillock功能感兴趣。

  1. 启用最小化配置文件,像这样:
sudo authselect select minimal --force
  1. 启用配置文件后,我们现在可以启用pam_faillock模块,像这样:
sudo authselect enable-feature with-faillock
  1. /etc/security/目录下,使用你喜欢的文本编辑器打开faillock.conf文件。寻找这四行:
# silent
# deny = 3
# unlock_time = 600
# even_deny_root

删除前面的注释符号并保存文件。

  1. 通过以下操作为 Vicky 创建一个用户账户:
sudo useradd vicky
sudo passwd vicky
  1. 打开另一个终端,让 Vicky 故意进行三次失败的登录尝试。在你自己的终端中查看结果,像这样:
[donnie@localhost ~]$ sudo faillock
donnie:
When                Type  Source                                           Valid
vicky:
When                Type  Source                                           Valid
2022-10-12 15:54:35 RHOST 192.168.0.16                                         V
2022-10-12 15:54:42 RHOST 192.168.0.16                                         V
2022-10-12 15:54:46 RHOST 192.168.0.16                                         V
[donnie@localhost ~]$

然后,在定时器到期之前,让 Vicky 再次尝试使用她自己的正确密码登录。

  1. 十分钟定时器到期后,让 Vicky 尝试使用正确的密码登录。

  2. 让用户注销。然后,再让用户故意进行三次失败的登录尝试。这次,在定时器到期之前,像这样重置用户账户:

sudo faillock --reset --user vicky

这就是本实验的全部内容。

在 Ubuntu 上做这件事稍有不同,现在让我们看看如何操作。

在 Ubuntu 20.04 和 Ubuntu 22.04 上配置 pam_faillock

不幸的是,authselect 工具在 Ubuntu 上不可用,因此我们只能手动编辑 PAM 配置文件。以下是操作步骤。

在 Ubuntu 20.04 和 Ubuntu 22.04 上配置 pam_faillock 的实践实验

  1. 使用你喜欢的文本编辑器打开 /etc/pam.d/common-auth 文件。在文件顶部插入以下两行:
auth        required                                     pam_faillock.so preauth silent
       auth        required                                     pam_faillock.so authfail
  1. 使用文本编辑器打开 /etc/pam.d/common-account 文件。在文件底部添加这一行:
account     required                                     pam_faillock.so
  1. 按照我在前一个实验中为 AlmaLinux 第 5 步所展示的方式配置 /etc/security/faillock.conf 文件。

  2. 按照前一个 AlmaLinux 实验中的第 6 到第 8 步进行测试。

  3. 就是这样。接下来,我们来看看如何手动锁定用户账户。

锁定用户账户

好的,你刚刚看到如何让 Linux 自动锁定受到攻击的用户账户。也有时候,你会希望手动锁定用户账户。让我们来看几个例子:

  • 当用户去度假时,你可能希望确保在他或她不在期间没有人乱改该用户的账户。

  • 当用户因可疑活动而接受调查时

  • 当用户离开公司时

关于最后一点,你可能会问,为什么我们不能直接删除那些不再在这里工作的人账户呢? 当然可以,而且也很简单。然而,在这么做之前,你需要了解当地的法律,确保不会给自己带来麻烦。比如在美国,我们有 萨班斯-奥克斯利法案,它限制了上市公司可以从计算机中删除哪些文件。如果你删除一个用户账户,包括该用户的主目录和邮件目录,你可能会违反萨班斯-奥克斯利法案,或者你所在国家/地区的类似法律。

无论如何,你可以使用两个工具暂时锁定用户账户:

  • usermod

  • passwd

可能与我刚才所说的相矛盾,实际上你有时需要移除不活跃的用户账户。因为恶意行为者可能会利用这些不活跃的账户进行破坏,特别是如果该账户曾有任何管理员权限。然而,在移除账户时,一定要遵循当地法律和公司政策。事实上,最好的做法是确保你的组织在变更管理程序中有明确的书面指导方针来处理不活跃用户账户的移除。

使用 usermod 锁定用户账户

假设 Katelyn 已经请产假,离开了几周。我们可以通过以下方式锁定她的账户:

sudo usermod -L katelyn

当你查看 Katelyn 在/etc/shadow文件中的条目时,你会看到她的密码哈希前面有一个感叹号,像这样:

katelyn:!$6$uA5ecH1A$MZ6q5U.cyY2SRSJezV000AudP.ckXXndBNsXUdMI1vPO8aFmlLXcbGV25K5HSSaCv4RlDilwzlXq/hKvXRkpB/:17446:0:99999:7:::

这个感叹号阻止系统读取她的密码哈希,从而有效地锁定了她的账户。

要解锁她的账户,只需执行以下操作:

sudo usermod -U katelyn

你会看到感叹号已经被移除,这样她就能重新登录到她的账户。

使用 passwd 锁定用户账户

你也可以像这样锁定 Katelyn 的账户:

sudo passwd -l katelyn

这与usermod -L执行的操作相同,但方式稍有不同。首先,passwd -l会提供一些关于当前操作的反馈,而usermod -L则完全没有反馈。在 Ubuntu 系统上,反馈如下所示:

donnie@ubuntu-steemnode:~$ sudo passwd -l katelyn
 passwd: password expiry information changed.
 donnie@ubuntu-steemnode:~$

在 CentOS 或 AlmaLinux 上,反馈如下所示:

[donnie@localhost ~]$ sudo passwd -l katelyn
 Locking password for user katelyn.
 passwd: Success
 [donnie@localhost ~]$

此外,在 CentOS 或 AlmaLinux 机器上,你会看到passwd -l在密码哈希前面放了两个感叹号,而不是一个。无论哪种方式,效果是一样的。

要解锁 Katelyn 的账户,只需执行以下操作:

sudo passwd -u katelyn

在 Red Hat 或 CentOS 7 版本之前,usermod -U只会移除passwd -lshadow文件的密码哈希前面添加的一个感叹号,这样账户仍然保持锁定。不过,没关系,因为再次运行usermod -U会移除第二个感叹号。

自从 RHEL 7 系列版本引入以来,这个问题已经得到修复。passwd -l命令仍会在shadow文件中放置两个感叹号,但usermod -U现在会移除这两个感叹号。(真可惜,因为这破坏了我一直很喜欢做的一个演示,尤其是在我的学生面前。)

锁定 root 用户账户

现在,云计算已成为大生意,租用虚拟私人服务器(VPS)变得相当普遍,许多公司如 Rackspace、DigitalOcean 或 Microsoft Azure 都提供此类服务。这些服务器可以用于多种用途:

  • 你可以运行自己的个人网站,安装自己选择的服务器软件,而不是让托管服务来做这件事。

  • 你可以为其他人设置一个基于 Web 的应用程序以供访问。

  • 最近,我在一个加密挖矿频道的 YouTube 演示中看到了如何在租用的虚拟私人服务器上设置权益证明(Proof of Stake)主节点。

大多数这些云服务都有一个共同点,那就是在你第一次设置账户并且服务提供商为你设置虚拟机时,它们都会让你登录到 root 用户账户。(即使是 Ubuntu,也会出现这种情况,尽管在本地安装的 Ubuntu 上 root 账户是禁用的。)

我知道有些人只是不断地登录到这些基于云的服务器的 root 账户,根本不在意,但这真是一个糟糕的主意。有些僵尸网络,比如 Hail Mary 僵尸网络,会持续扫描互联网,寻找那些将其 Secure Shell 端口暴露给互联网的服务器。当僵尸网络找到这样的服务器时,它们会对该服务器的 root 用户账户进行暴力破解密码攻击。是的,僵尸网络有时确实能够成功突破,尤其是当 root 账户设置了弱密码时。

所以,当你设置云服务器时,第一件事就是为自己创建一个普通用户账户,并为其设置完整的 sudo 权限。然后,退出 root 用户账户,登录到你的新账户,并执行以下操作:

sudo passwd -l root

我的意思是,真的,为什么要冒着让 root 账户被入侵的风险呢?

设置安全横幅

你真的不想看到登录横幅上写着类似 欢迎来到我们的网络 这样的字眼。我这么说是因为,很多年前,我参加了一个由导师指导的 SANS 事故处理课程。我们的导师给我们讲了一个故事,说一家公司把一名涉嫌入侵网络的人员告上法庭,结果案件被驳回了。理由是什么?那个被告说,"我看到有写着欢迎加入网络的信息,所以我以为我真的可以进入这里。" 是的,显然这就足以让案件被驳回。

几年后,我在我的一门 Linux 管理课程中给学生们讲了这个故事。一位学生说,"这没有任何意义我们家门口都有欢迎垫,但这并不意味着小偷可以进来。" 我必须承认,他说得有道理,现在我不得不怀疑这个故事的真实性。

无论如何,为了安全起见,你确实需要设置清楚只有授权用户才能访问系统的登录消息。

使用 motd 文件

/etc/motd 文件会在任何通过 Secure Shell 登录系统的人面前显示一个消息横幅。在你的 CentOS 或 AlmaLinux 机器上,已经有一个空的 motd 文件了。在你的 Ubuntu 机器上,motd 文件并不存在,但创建一个非常简单。无论哪种情况,打开文件并在你的文本编辑器中创建消息。保存文件并通过 Secure Shell 远程登录来测试。你应该会看到类似这样的内容:

 maggie@192.168.0.100's password:
 Last login: Sat Oct 7 20:51:09 2017
 Warning: Authorized Users Only!
 All others will be prosecuted.
 [maggie@localhost ~]$
motd stands for Message of the Day.

Ubuntu 带有一个动态 MOTD 系统,显示来自 Ubuntu 母公司以及操作系统的消息。当你在/etc目录中创建一个新的motd文件时,文件中的任何消息都会出现在动态输出的最后,就像这样:

Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-48-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
  System information as of Thu Oct 13 06:20:54 PM UTC 2022
  System load:  0.0               Processes:               103
  Usage of /:   47.8% of 9.75GB   Users logged in:         1
  Memory usage: 12%               IPv4 address for enp0s3: 192.168.0.11
  Swap usage:   0%
39 updates can be applied immediately.
To see these additional updates run: apt list --upgradable
Warning!!! Authorized users only!
Last login: Thu Oct 13 17:14:52 2022 from 192.168.0.16
The Warning!!! Authorized users only! line is what I placed into the /etc/motd file.

使用 issue 文件

issue文件也位于/etc目录中,它会在本地终端的登录提示符上方显示一条消息。默认的issue文件通常只包含一些宏代码,显示机器的相关信息。以下是来自 Ubuntu 机器的一个示例:

Ubuntu 22.04.1 LTS \n \l

在 Red Hat 类型的机器上,它会像这样显示:

\S
Kernel \r on an \m

在 Ubuntu 机器上,横幅大概会是这样的:

19501_03_01.png

19501_03_01.png

在 Red Hat 类型的机器上,它大概会像这样:

19501_03_02.png

19501_03_02.png

你可以在issue文件中放入安全消息,重启后它会显示出来:

19501_03_03.png

19501_03_03.png

事实上,把安全消息放在issue文件里真有意义吗?如果你的服务器已经被妥善保存在受控的机房中,那么大概没有什么意义。对于暴露在外的桌面机器,这个做法会更有用。

使用 issue.net 文件

还是别这么做了。这个是用于telnet登录的,任何在服务器上启用了telnet的用户,基本上都是在严重犯错。然而,由于某些奇怪的原因,issue.net文件仍然留在/etc目录下。

检测泄露的密码

是的,亲爱的们,坏人确实拥有大量的密码字典,这些字典包含了常用密码或已被泄露的密码。暴力破解密码的一种最有效方式就是使用这些字典进行字典攻击。字典攻击时,密码破解工具会从指定的字典中读取密码,并尝试每一个,直到字典用尽,或者攻击成功。那么,如何知道你的密码是否在这些字典中呢?很简单,直接使用某个在线服务检查你的密码就行了。一个流行的站点是Have I Been Pwned?,你可以在这里查看:

19501_03_04.png

19501_03_04.png

你可以在这里访问Have I Been Pwned?

haveibeenpwned.com

你只需要输入密码,服务就会显示该密码是否出现在任何泄露的密码列表中。但是想想看,你真的想把你的生产密码发送到某个网站吗?是的,我也这么认为。相反,我们只发送密码的哈希值。更好的是,我们只发送哈希值的一部分,让网站能在数据库中找到这个密码,但又不会泄露你具体的密码是什么。我们可以通过使用Have I Been Pwned? 应用程序编程接口API)来实现这一点。

为了演示基本原理,我们可以使用 curl,配合 API,查看包含 21BD1 字符串的密码哈希列表。(你可以在任何虚拟机上进行此操作。我现在正在使用的 Fedora 工作站上就可以。只需运行以下命令:)

curl https://api.pwnedpasswords.com/range/21BD1

你会看到很多类似这样的输出,所以我只展示前几行:

 [donnie@fedora-teaching ~]$ curl https://api.pwnedpasswords.com/range/21BD1
 0018A45C4D1DEF81644B54AB7F969B88D65:1
 00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2
 011053FD0102E94D6AE2F8B83D76FAF94F6:1
 012A7CA357541F0AC487871FEEC1891C49C:2
 0136E006E24E7D152139815FB0FC6A50B15:3
 01A85766CD276B17DE6DA022AA3CADAC3CE:3
 024067E46835A540D6454DF5D1764F6AA63:3
 02551CADE5DDB7F0819C22BFBAAC6705182:1
 025B243055753383B479EF34B44B562701D:2
 02A56D549B5929D7CD58EEFA97BFA3DDDB3:8
 02F1C470B30D5DDFF9E914B90D35AB7A38F:3
 03052B53A891BDEA802D11691B9748C12DC:6
. . .
. . .

让我们把这个结果管道到 wc -l,这是一个方便的计数工具,来看一下我们找到了多少个匹配结果:

 [donnie@fedora-teaching ~]$ curl https://api.pwnedpasswords.com/range/21BD1 | wc -l
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 100 20592 0 20592 0 0 197k 0 --:--:-- --:--:-- --:--:-- 199k
 526
 [donnie@fedora-teaching ~]$

根据这个结果,我们找到了 526 个匹配项。但这并不特别有用,所以我们稍微美化一下。我们通过创建一个 pwnedpasswords.sh 的 shell 脚本来实现,这个脚本如下所示:

#!/bin/bash
candidate_password=$1
echo "Candidate password: $candidate_password"
full_hash=$(echo -n $candidate_password | sha1sum | awk '{print substr($1, 0, 32)}')
prefix=$(echo $full_hash | awk '{print substr($1, 0, 5)}')
suffix=$(echo $full_hash | awk '{print substr($1, 6, 26)}')
if curl https://api.pwnedpasswords.com/range/$prefix | grep -i $suffix;
 then echo "Candidate password is compromised";
 else echo "Candidate password is OK for use";
fi

好吧,虽然现在不能把你培养成 shell 脚本大师,但我可以简化地解释一下:

  • candidate_password=$1: 这要求你在调用脚本时输入你想要检查的密码。

  • full_hash= , prefix=, suffix=: 这些行计算密码的 SHA1 哈希值,然后提取我们想要发送到密码检查服务的哈希部分。

  • if curl: 我们以一个 if..then..else 结构来结束,它将密码哈希的选定部分发送到检查服务,然后告诉我们密码是否已经泄露。

保存文件后,给用户添加可执行权限,如下所示:

chmod u+x pwnedpasswords.sh

现在,让我们看看 TurkeyLips,我最喜欢的密码,是否已经泄露:

 [donnie@fedora-teaching ~]$ ./pwnedpasswords.sh TurkeyLips
 Candidate password: TurkeyLips
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 09FDEDF4CA44D6B432645D6C1D3A8D4A16BD:2
 100 21483 0 21483 0 0 107k 0 --:--:-- --:--:-- --:--:-- 107k
 Candidate password is compromised
 [donnie@fedora-teaching ~]$

是的,密码已经泄露了。看来我不想将其作为生产环境中的密码。

现在,让我们再试一次,不过这次在末尾加上一个随机的两位数:

 [donnie@fedora-teaching ~]$ ./pwnedpasswords.sh TurkeyLips98
 Candidate password: TurkeyLips98
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 100 20790 0 20790 0 0 110k 0 --:--:-- --:--:-- --:--:-- 110k
 Candidate password is OK for use
 [donnie@fedora-teaching ~]$

好吧,它说这个密码是安全的。不过,你大概不想使用这种已知泄露的简单密码变种。

我本想为这里展示的 shell 脚本归功于自己,但我做不到。这个脚本是我朋友 Leo Dorrendorf 创作的,他曾是前 VDOO 物联网安全公司的成员,该公司现已被 JFrog 收购。(我在这里以他的友好许可复现了这个脚本。)

如果你对物联网设备的安全解决方案感兴趣,可以在这里了解更多:

jfrog.com/security-and-compliance/?vr=1/

完全披露:VDOO/JFrog 公司曾是我的客户之一。

说了这么多,我还是得提醒你,密码短语仍然比密码更好。密码短语不仅更难破解,而且更不可能出现在任何人的泄露凭证列表中。

用于检测泄露密码的动手实验

在这个实验中,你将使用 pwnedpasswords API 来检查你自己的密码:

  1. 使用 curl 来查看密码哈希中包含 21BD1 字符串的密码有多少个:
curl https://api.pwnedpasswords.com/range/21BD1
  1. 在任何一个 Linux 虚拟机的主目录中,创建名为 pwnpassword.sh 的脚本,内容如下:
#!/bin/bash
candidate_password=$1
echo "Candidate password: $candidate_password"
full_hash=$(echo -n $candidate_password | sha1sum | awk '{print substr($1, 0, 32)}')
prefix=$(echo $full_hash | awk '{print substr($1, 0, 5)}')
suffix=$(echo $full_hash | awk '{print substr($1, 6, 26)}')
if curl https://api.pwnedpasswords.com/range/$prefix | grep -i $suffix;
        then echo "Candidate password is compromised";
        else echo "Candidate password is OK for use";
fi
  1. 给脚本添加可执行权限:
chmod u+x pwnedpasswords.sh
  1. 运行脚本,指定 TurkeyLips 作为密码:
./pwnedpasswords.sh TurkeyLips
  1. 根据需要,重复 步骤 4,每次使用不同的密码。

到目前为止,我们看到的用户管理技术在少量计算机上非常有效。但如果你在一个大型企业工作呢?接下来我们将讨论这个问题。

理解集中式用户管理

在企业环境中,你通常需要管理数百甚至数千个用户和计算机。因此,如果每次都登录到每个网络服务器或每个用户的工作站,按照我们刚才概述的步骤进行操作,那将非常不可行。(但请记住,你依然需要掌握这些技能。)我们需要的是一种从一个中心位置管理计算机和用户的方法。由于篇幅有限,我无法提供关于各种方法的完整细节。所以目前,我们只能做一个高层次的概述。

微软 Active Directory

我并不是 Windows 或微软的超级粉丝。但说到 Active Directory,我不得不承认它确实值得称赞。它是一个相当出色的产品,大大简化了大型企业网络的管理。而且,没错,确实可以将 Unix/Linux 计算机及其用户添加到 Active Directory 域中。

我一直有个不为人知的秘密,我希望你不要因此讨厌我。在接触 Linux 之前,我获得了 Windows Server 2003 的 MCSE 认证。大部分情况下,我的客户只使用 Linux 计算机,但我偶尔需要用到我的 MCSE 技能。几年前,一位前客户需要我将一个基于 Linux 的 Nagios 服务器设置为 Windows Server 2008 域的一部分,以便其用户能够通过 Active Directory 进行身份验证。这个过程花了我一些时间,但最终我成功了,客户也很满意。

除非你承担很多角色,就像我有时需要做的那样,否则你作为 Linux 管理员,可能不需要学习如何使用 Active Directory。你很可能只需要告诉 Windows Server 管理员你需要什么,让他们去处理。

我知道,你已经迫不及待想看看我们能在 Linux 服务器上做些什么了。那么,接下来就开始吧。

Linux 上的 Samba

Samba 是一个 Unix/Linux 守护进程,可以实现三种功能:

  • 它的主要目的是将 Unix/Linux 服务器上的目录共享给 Windows 工作站。目录会在 Windows 文件资源管理器中显示,就像是从其他 Windows 计算机共享的一样。

  • 它还可以被设置为网络打印服务器。

  • 它也可以被设置为 Windows 域控制器。

你可以在 Linux 服务器上安装 Samba 版本 3,并将其设置为充当传统的 Windows NT 域控制器。这是一个相当复杂的过程,并且需要一些时间。完成后,你可以将 Linux 和 Windows 计算机加入域,并使用普通的 Windows 用户管理工具来管理用户和组。

Linux 社区的一个“圣杯”是如何在 Linux 服务器上模拟 Active Directory。几年前,这个目标通过 Samba 4 版本的推出变成了某种程度的现实。但设置它是一个非常复杂的过程,可能不会是你愿意做的事。因此,也许我们应该继续寻找更好的解决方案。

FreeIPA/身份管理在 RHEL 类型的发行版上的应用

几年前,Red Hat 公司推出了 FreeIPA 作为 Fedora 的一组软件包。为什么选择 Fedora?因为他们希望先在 Fedora 上对其进行彻底测试,然后再将其提供给实际的生产网络。现在它已经可以在 RHEL 7 到 RHEL 9 及其所有衍生版本(包括 CentOS 和 AlmaLinux)上使用。这就是 IPA 的含义:

  • 身份

  • 策略

  • 审计

这在某种程度上是对微软 Active Directory 的一种回应,但它仍然不是一个完整的解决方案。它做了一些很酷的事情,但仍然是一个进行中的项目。它最酷的部分是安装和设置的简便性。你只需从正常的仓库中安装相关软件包,打开合适的防火墙端口,然后运行一个设置脚本。之后,你就可以通过 FreeIPA 的 Web 界面开始添加用户和计算机到新域中了。这里,我正在添加我的灰白色条纹小猫 Cleopatra:

19501_03_05.png

19501_03_05.png

尽管你可以将 Windows 机器加入 FreeIPA 域,但不推荐这么做。然而,从 RHEL/CentOS 7.1 开始,你可以使用 FreeIPA 创建与 Active Directory 域的跨域信任。

该程序的正式名称是 FreeIPA。但由于某些奇怪的原因,Red Hat 的人们在他们的文档中拒绝提到这个名字。他们总是称其为“身份管理”或“IdM”。

关于用户管理的内容基本就这些了。让我们总结一下,然后进入下一章。

总结

在本章中,我们讨论了如何锁定用户的主目录,如何强制执行强密码策略,以及如何强制执行账户和密码过期策略。接着,我们讲解了防止暴力破解密码攻击的方法,如何手动锁定用户账户,如何设置安全横幅以及如何检查是否有泄露的密码。最后,我们简要回顾了集中式用户管理系统。

在下一章中,我们将学习如何使用各种防火墙工具。我们在那里见。

问题

  1. 在哪个文件中你会配置复杂的密码标准?

  2. 在使用useradd工具在 RHEL 7 系统上时,/etc/login.defs文件中的UMASK设置应为多少?

  3. 在 Ubuntu 20.04 系统上使用adduser工具时,如何配置/etc/adduser.conf文件,以便新用户的主目录能够防止其他用户访问?

  4. 美国国家标准与技术研究院(NIST)最近对推荐的密码策略做出了什么修改?

  5. 以下哪个三个工具可以用来设置用户账户的到期日期?

    1. useradd

    2. adduser

    3. usermod

    4. chage

  6. 为什么你可能想锁定一位前员工的账户,而不是删除它?

    1. 锁定账户比删除账户更容易。

    2. 删除一个账户需要太长时间。

    3. 不可能删除一个用户账户。

    4. 删除用户账户以及该用户的文件和邮件队列可能会让你陷入法律麻烦。

  7. 你刚为 Samson 创建了一个用户账户,现在你想强制他在第一次登录时更改密码。以下哪个命令可以实现这一点?

    1. sudo chage -d 0 samson

    2. sudo passwd -d 0 samson

    3. sudo chage -e samson

    4. sudo passwd -e samson

  8. 以下哪个是adduser工具相较于传统useradd工具的优势?

    1. adduser可以在 Shell 脚本中使用。

    2. adduser适用于所有 Linux 发行版。

    3. adduser有一个选项,允许你在创建用户账户时加密用户的主目录。

    4. adduser也适用于 Unix 和 BSD。

  9. 在最新的 Linux 发行版中,强制使用强密码的 PAM 模块叫什么名字?

    1. cracklib

    2. 密码

    3. 安全

    4. pwquality

深入阅读

答案

  1. /etc/security/pwquality.conf

  2. 077

  3. 将 DIR_MODE=值更改为 DIR_MODE=750

  4. 他们放弃了关于密码复杂度和密码过期的旧哲学。

  5. A, C, D

  6. D

  7. A, D

  8. C

  9. D

第五章:4 使用防火墙保护你的服务器 - 第一部分

加入我们的 Discord 书籍社区

packt.link/SecNet

安全性是那些最好分层执行的事情之一,我们称之为“深度防御”。因此,在任何给定的企业网络中,你会发现一个防火墙设备将互联网与 非军事区DMZ)隔开,互联网面向的服务器通常会部署在这里。你还会发现另一个防火墙设备位于 DMZ 和内部局域网之间,并且在每台服务器和客户端上都安装有防火墙软件。我们的目标是尽可能让入侵者难以进入我们的网络。

有趣的是,在所有主要的 Linux 发行版中,只有 SUSE 发行版和 Red Hat 类型的发行版默认设置并启用了防火墙。较新的 Ubuntu 版本也附带了预配置的防火墙,但你需要通过运行几个简单的命令来激活它。

由于本书的重点是强化我们的 Linux 服务器,我们将专注于这一防线的最后一层:服务器和客户端上的防火墙。我们将讨论两个命令行的 netfilter 接口,即 iptablesnftables

本章将涵盖以下主题:

  • Linux 防火墙概述

  • iptables 概述

  • nftables 概述

在下一章中,我们将介绍 ufwfirewalld,它们是 iptables 和 nftables 的便捷前端。

技术要求

本章的代码文件可以在这里找到:github.com/PacktPublishing/Mastering-Linux-Security-and-Hardening-Second-Edition

Linux 防火墙概述

在典型的商业环境中,特别是在大型企业中,你可能会在不同的位置遇到各种类型的防火墙,这些防火墙可以提供各种功能。以下是一些示例:

  • 边缘设备将互联网与内部网络分隔开,并将可路由的公共 IP 地址转换为不可路由的私有 IP 地址。它们还可以提供各种类型的访问控制,以防止未授权人员进入。通过提供各种类型的数据包检查服务,它们可以帮助防止对内部网络的攻击,阻止恶意软件的入侵,并防止敏感信息从内部网络泄漏到互联网。

  • 大型企业网络通常会被划分为多个子网或 子网,每个公司部门都会有一个属于自己的子网。最佳实践建议使用防火墙将这些子网隔开,这有助于确保只有授权人员才能访问特定的子网。

  • 当然,你还会在各个服务器和工作站上运行防火墙。通过提供一种访问控制方式,它们可以帮助防止已经被入侵的机器在网络上进行横向移动。它们还可以配置来防止某些类型的端口扫描和拒绝服务DoS)攻击。

对于前面列表中的前两项,你通常会看到专用的防火墙设备和防火墙管理员团队来负责处理它们。列表中的第三项就是你作为 Linux 专业人员进入的地方。在本章以及下一章中,我们将探讨你在 Linux 服务器和 Linux 工作站发行版中使用的防火墙技术。

Linux 防火墙的名称是netfilter。这个 netfilter 代码被编译进 Linux 内核,并执行实际的包过滤。人类用户无法直接与 netfilter 进行交互,这意味着我们需要某种助手程序来帮助我们与 netfilter 交互。曾经有三个助手程序,它们分别是:

  • ipchains:这是第一个,曾作为 Linux 内核的一部分,直到内核版本 2.4。它现在已经成为历史,所以我们不再谈论它。

  • iptables:它替代了 Linux 内核版本 2.6 中的 ipchains。它仍然在许多 Linux 发行版中使用,但正在迅速消失。

  • nftables:这是新兴的工具,正在迅速取代 iptables。正如我们稍后将看到的,它比旧版 iptables 有很多优势。

  • 这三个助手程序为我们做了两件事:

  • 它们为用户提供了命令行界面。

  • 它们将用户输入的命令传递给 netfilter。

  • 更有趣的是,我们还有为助手程序提供的助手程序。简化防火墙ufw)是由 Ubuntu 开发者创建的,它是 iptables 或 nftables 的前端。Ubuntu 默认安装了 ufw,你也可以在 Debian 及其他 Debian 系列的发行版中自行安装 ufw。在 Red Hat 系列中,我们有 firewalld,它也是 iptables 或 nftables 的前端。请注意,firewalld 在所有 Red Hat 系列的发行版和 SUSE 发行版中默认安装并处于活动状态。在 Ubuntu 和 Debian 中它是一个可选项。ufw 和 firewalld 都可以极大简化设置正确防火墙的过程。不过,有时了解如何操作裸 iptables 或裸 nftables 也是很有帮助的。那么,我们就从 iptables 开始看吧。

iptables 概述

如我所提到的,iptables 是我们目前可以直接管理 netfilter 的两种命令行工具之一。它最初是作为 Linux 内核版本 2.6 的一项功能引入的,已经存在很长时间了。使用 iptables,你有一些优势:

  • 它已经存在了足够长的时间,以至于大多数 Linux 管理员都知道如何使用它。

  • 使用 iptables 命令在 shell 脚本中创建自定义防火墙配置非常简单。

  • 它具有很大的灵活性,可以用来设置简单的端口过滤器、路由器或虚拟私人网络。

  • 它仍然在某些 Linux 发行版中预安装,尽管它正在迅速被 nftables 取代。

  • 它有非常完善的文档,并且在互联网上有免费、长篇的教程可供参考。

然而,正如你可能知道的那样,它也有一些缺点:

  • IPv4 和 IPv6 各自需要特定的 iptables 实现。因此,如果你的组织在迁移到 IPv6 的过程中仍然需要运行 IPv4,你将不得不在每个服务器上配置两个防火墙,并为每个防火墙运行一个单独的守护进程。(一个用于 IPv4,另一个用于 IPv6。)

  • 如果你需要进行 MAC 桥接,则需要ebtables,它是 iptables 的第三个组成部分,具有自己独特的语法。

  • arptables,iptables 的第四个组成部分,也需要其自己的守护进程和语法。

  • 每当你向正在运行的 iptables 防火墙添加规则时,整个 iptables 规则集必须重新加载,这可能会影响性能。

直到最近,普通的 iptables 是所有 Linux 发行版的默认防火墙管理器。在某些发行版中仍然是默认设置,但 Red Hat Enterprise Linux 7 及其所有衍生版本现在使用新的 firewalld 作为更易用的前端来配置 iptables 规则。Ubuntu 自带简单防火墙ufw),它也是一个易于使用的 iptables 前端,适用于所有 Ubuntu 版本,直到 20.04。

在本章中,我们将讨论如何为 IPv4 和 IPv6 配置 iptables 防火墙规则。

掌握 iptables 的基础知识

iptables 由五个规则表组成,每个表都有其独特的目的:

  • 过滤表:为了基本保护我们的服务器和客户端,这可能是我们使用的唯一表。

  • 网络地址转换(NAT)表:NAT 用于将公共互联网与私有网络连接起来。

  • 修改表:用于在数据包通过防火墙时修改网络数据包。

  • 原始表:这是用于不需要连接跟踪的包。

  • 安全表:安全表仅用于安装了 SELinux 的系统。

由于我们当前只关心基本的主机保护,因此暂时只看过滤表。(稍后我将向你展示我们可以使用 mangle 表做的一些花样技巧。)每个表由规则链组成,过滤表由INPUTFORWARDOUTPUT链组成。让我们在 Ubuntu 20.04 虚拟机上看一下这个。

Ubuntu 20.04 LTS 自带 iptables,而 Ubuntu 22.04 自带 nftables。然而,即使你正在运行 Ubuntu 22.04 或更高版本,你仍然需要学习如何使用 iptables 命令。第一个原因是,Ubuntu 22.04 包括一个很棒的功能,可以自动将 iptables 命令转换为 nftables 命令。这样,你可以继续使用已经拥有的 iptables 脚本,而无需担心将其转换为 nftables 格式。第二个原因是,一旦我们开始讨论 Ubuntu 的简单防火墙ufw),你会发现无论使用哪个版本的 Ubuntu,仍然需要了解 iptables 命令才能配置它。

首先,我们通过使用sudo iptables -L命令来查看当前的配置:

 donnie@ubuntu:~$ sudo iptables -L
 [sudo] password for donnie:
 Chain INPUT (policy ACCEPT)
 target prot opt source destination
 Chain FORWARD (policy ACCEPT)
 target prot opt source destination
 Chain OUTPUT (policy ACCEPT)
 target prot opt source destination
 donnie@ubuntu:~$

记住,我们之前说过,需要一个单独的 iptables 组件来处理 IPv6。这里,我们将使用sudo ip6tables -L命令:

 donnie@ubuntu:~$ sudo ip6tables -L
 Chain INPUT (policy ACCEPT)
 target prot opt source destination
 Chain FORWARD (policy ACCEPT)
 target prot opt source destination
 Chain OUTPUT (policy ACCEPT)
 target prot opt source destination
 donnie@ubuntu:~$

在这两种情况下,你可以看到没有规则,机器是完全开放的。(理解 Ubuntu 实际上是带有预配置的简单防火墙,且你会看到一些特定的输出,但我们暂时忽略它,以便直接使用 iptables。)我们将从创建一个规则开始,允许我们传递来自服务器的输入数据包,这些服务器是我们的主机请求过连接的:

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

这是该命令的分解:

  • -A INPUT: -A将规则放在指定链的末尾,在此情况下是INPUT链。如果我们希望将规则放在链的开头,我们会使用-I

  • -m: 这调用了一个 iptables 模块。在这种情况下,我们调用了conntrack模块来跟踪连接状态。此模块允许iptables确定我们的客户端是否已连接到另一台机器,例如。

  • --ctstate: ctstate,即连接状态,我们的规则部分正在寻找两件事。首先,它在寻找客户端与服务器建立的连接。接着,它寻找来自服务器并返回到客户端的相关连接,以便允许客户端连接。所以,如果用户使用网页浏览器连接到一个网站,这条规则将允许来自网页服务器的数据包通过防火墙,传输到用户的浏览器。

  • -j: 这代表跳转。规则会跳转到一个特定的目标,在此情况下是ACCEPT。(请不要问我是谁发明的这个术语。)因此,这条规则将接受从服务器返回并且客户端已请求连接的数据包。

我们的新规则集如下所示:

 donnie@ubuntu:~$ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target prot opt source destination
 ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
 Chain FORWARD (policy ACCEPT)
 target prot opt source destination
 Chain OUTPUT (policy ACCEPT)
 target prot opt source destination
 donnie@ubuntu:~$

接下来,我们将打开端口22,以便通过安全外壳协议(Secure Shell)进行连接:

sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT

这是分解:

  • -A INPUT: 如前所述,我们希望将规则放在INPUT链的末尾,因此使用-A

  • -p tcp: -p表示该规则影响的协议。此规则影响 TCP 协议,其中安全外壳协议(Secure Shell)就是其中之一。

  • --dport ssh:当一个选项名称由多个字母组成时,我们需要在前面加上两个破折号,而不仅仅是一个。--dport选项指定了我们希望该规则操作的目标端口。(注意,我们也可以将该部分规则列为--dport 22,因为22是 SSH 端口的号码。)

  • -j ACCEPT:如果我们将所有内容与-j ACCEPT结合在一起,那么我们就有了一条允许其他机器通过安全外壳连接到此机器的规则。

现在,假设我们希望这台机器成为一个 DNS 服务器。为此,我们需要为 TCP 和 UDP 协议都打开端口53

sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT

最后,我们拥有了一个几乎完整且可用的INPUT链规则集:

 donnie@ubuntu:~$ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target prot opt source destination
 ACCEPT all -- anywhere anywhere ctstate
 RELATED,ESTABLISHED
 ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
 DROP all -- anywhere anywhere
 Chain FORWARD (policy ACCEPT)
 target prot opt source destination
 Chain OUTPUT (policy ACCEPT)
 target prot opt source destination
 donnie@ubuntu:~$

然而,这几乎是完整的,因为我们还有一件小事忘记了。也就是,我们需要允许回环接口的流量。这样做是可以的,因为它给了我们一个很好的机会来查看如何插入规则,尤其是在我们不希望它出现在最后时。如果是这种情况,我们将把规则插入到INPUT 1,即INPUT链的第一个位置:

sudo iptables -I INPUT 1 -i lo -j ACCEPT

在你插入ACCEPT规则之前,你可能已经注意到sudo命令执行时很慢,并且出现了 sudo: unable to resolve host... Resource temporarily unavailable 的消息。这是因为sudo需要知道机器的主机名,以便知道哪些规则可以在特定机器上运行。它使用回环接口来帮助解析主机名。如果lo接口被阻止,sudo需要更长时间来解析主机名。

我们的规则集现在看起来是这样的:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:domain
ACCEPT     udp  --  anywhere             anywhere             udp dpt:domain
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
donnie@ubuntu:~$

请注意端口53是作为域名端口列出的。为了看到端口号而不是端口名称,我们可以使用-n开关:

donnie@ubuntu3:~$ sudo iptables -L -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
donnie@ubuntu3:~$

现在,按照当前的情况,我们仍然允许一切通过,因为我们还没有创建阻止未明确允许内容的规则。不过,在我们这样做之前,先看看我们可能希望允许的一些其他内容。

使用 iptables 阻止 ICMP

你在职业生涯中可能听到的传统观点是,我们需要阻止所有来自互联网控制消息协议ICMP)的包。你可能被告知的观点是,通过阻止 ping 包,使你的服务器对黑客不可见。当然,ICMP 协议存在一些漏洞,例如以下几种:

通过使用僵尸网络,黑客可以通过多个来源同时向你的服务器发送 ping 包,耗尽你的服务器应对的能力。

与 ICMP 协议相关的某些漏洞可能允许黑客获得系统的管理员权限、将流量重定向到恶意服务器,或使操作系统崩溃。

通过使用一些简单的黑客工具,某人可能会将敏感数据嵌入到 ICMP 包的数据字段中,从而偷偷将其从你的组织中提取出去。

然而,虽然阻止某些类型的 ICMP 数据包是好的,但阻止所有 ICMP 数据包是不好的。严酷的现实是,某些类型的 ICMP 消息对于网络的正常功能是必要的。由于我们最终创建的“丢弃所有不被允许的”规则也会阻止 ICMP 数据包,因此我们需要创建一些规则,允许必须的 ICMP 消息类型。那么,接下来就要做这些:

sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 3 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 11 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 12 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT

下面是详细说明:

  • -m conntrack:与之前一样,我们使用 conntrack 模块来允许处于某个状态的数据包。不过这一次,我们不仅仅允许来自已经连接主机的数据包(ESTABLISHED,RELATED),还允许其他主机发送到我们服务器的 NEW 数据包。

  • -p icmp:这表示 ICMP 协议。

  • --icmp-type:ICMP 消息类型有很多种,接下来我们会列出这些类型。

我们希望允许的三种 ICMP 消息类型如下:

  • type 3:这些是目标不可达消息。它们不仅可以告诉你的服务器无法访问某个主机,还可以告诉它原因。例如,如果服务器发送的数据包过大,超出了网络交换机的处理能力,交换机会返回一个 ICMP 消息,告诉服务器需要将该大数据包分片。如果没有 ICMP,每当服务器尝试发送需要分片的大数据包时,它都会遇到连接问题。

  • type 11:超时消息让服务器知道它发送的数据包要么在到达目标之前已经超过了其 生存时间(TTL),要么是一个分片数据包在 TTL 到期之前无法重新组装。

  • type 12:参数问题消息表示服务器发送了一个带有错误 IP 头的数据包。换句话说,IP 头缺少某个选项标志,或者它的长度无效。

三种常见的消息类型明显没有出现在我们的列表中:

  • type 0type 8:这些是臭名昭著的 ping 数据包。实际上,type 8 是你用来 ping 主机的回显请求数据包,而 type 0 是主机返回的回显应答,用来告诉你它是在线的。当然,允许 ping 数据包通过在排除网络问题时会大有帮助。如果出现这种情况,你可以添加几个 iptables 规则来暂时允许 ping。

  • type 5:现在,我们来看著名的重定向消息。允许这些消息在你有一台可以为服务器建议更高效路径的路由器时是有用的,但黑客也可以利用它们将你重定向到你不希望去的地方。所以,最好阻止这些消息。

这里展示的 ICMP 消息类型远远不止这些,但这些是我们目前需要关注的唯一类型。

当我们使用 sudo iptables -L 时,可以看到当前的规则集:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:domain
ACCEPT     udp  --  anywhere             anywhere             udp dpt:domain
ACCEPT     icmp --  anywhere             anywhere             ctstate NEW,RELATED,ESTABLISHED icmp destination-unreachable
ACCEPT     icmp --  anywhere             anywhere             ctstate NEW,RELATED,ESTABLISHED icmp source-quench
ACCEPT     icmp --  anywhere             anywhere             ctstate NEW,RELATED,ESTABLISHED icmp time-exceeded
ACCEPT     icmp --  anywhere             anywhere             ctstate NEW,RELATED,ESTABLISHED icmp parameter-problem
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination 

看起来不错,对吧?其实不完全是。我们还没有通过这些规则阻止任何东西。那么,现在我们来解决这个问题。

使用 iptables 阻止所有不被允许的内容

要开始阻止我们不想要的内容,我们必须做两件事中的其中一件。我们可以为INPUT链设置一个默认的DROPREJECT策略,或者我们可以将策略保持为ACCEPT,并在INPUT链的末尾创建一个DROPREJECT规则。选择哪个完全是个人偏好问题。(当然,在选择之前,你可能想先查看一下你所在组织的政策手册,看看雇主是否有偏好。)

DROPREJECT之间的区别是,DROP会阻止数据包而不向发送方返回任何信息;而REJECT则会阻止数据包,并且会向发送方返回一个信息,说明数据包为何被阻止。就我们当前的目的而言,假设我们只是想DROP那些我们不希望通过的数据包。

提示:

有时DROP更好,有时REJECT更好。如果让主机保持隐形很重要,就使用DROP。(尽管即使这样也不是特别有效,因为还有其他方式可以发现主机。)如果你需要让主机告知其他主机为什么不能建立连接,那么就使用REJECTREJECT的一个大优点是,它会让连接的主机知道它们的数据包被阻止了,这样它们就会立刻停止尝试连接。而使用DROP时,尝试连接的主机会一直尝试,直到连接超时。

要在INPUT链的末尾创建一个DROP规则,可以使用以下命令:

donnie@ubuntu:~$ sudo iptables -A INPUT -j DROP
donnie@ubuntu:~$

如果你想设置默认的DROP策略,我们可以使用以下命令:

donnie@ubuntu:~$ sudo iptables -P INPUT DROP
donnie@ubuntu:~$

设置默认的DROPREJECT策略的一个大优点是,它可以让你在需要时更容易地添加新的ACCEPT规则。这是因为如果我们决定保持默认的ACCEPT策略,并创建一个DROPREJECT规则,那么该规则必须放在列表的末尾。

由于iptables规则是按顺序处理的,从上到下,所以任何在DROPREJECT规则之后的ACCEPT规则都不会生效。你需要将任何新的ACCEPT规则插入到该DROPREJECT规则的上方,这比直接将它们附加到规则列表的末尾稍微不方便一点。为了说明我的下一个要点,我暂时保留了默认的ACCEPT策略,并添加了DROP规则。

当我们查看新的规则集时,会看到一些相当奇怪的东西:

donnie@ubuntu:~$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
. . .
. . .
ACCEPT     icmp --  anywhere             anywhere             ctstate NEW,RELATED,ESTABLISHED icmp parameter-problem
DROP       all  --  anywhere             anywhere            
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
. . .
. . .

INPUT链的第一条规则和最后一条规则看起来是一样的,唯一的区别是一个是DROP,另一个是ACCEPT。让我们再次使用-v(详细)选项来看一下:

donnie@ubuntu:~$ sudo iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target prot opt in out source destination 
   67 4828 ACCEPT all -- lo any anywhere anywhere 
  828 52354 ACCEPT all -- any any anywhere anywhere ctstate RELATED,ESTABLISHED
. . .
. . .
    0 0 ACCEPT icmp -- any any anywhere anywhere ctstate NEW,RELATED,ESTABLISHED icmp parameter-problem
  251 40768 DROP all -- any any anywhere anywhere 
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target prot opt in out source destination 
. . .
. . .

现在,我们可以看到lo(回环接口)出现在第一条规则的in列下,而any出现在最后一条规则的in列下。我们还可以看到-v开关显示了每条规则计数的包和字节数。因此,在前面的示例中,我们可以看到ctstate RELATED,ESTABLISHED规则接受了 828 个数据包和 52,354 字节。DROP all规则阻止了 251 个数据包和 40,763 字节。

这一切看起来都不错,除了如果我们现在重启机器,规则会消失。我们需要做的最后一件事是让它们变得永久。虽然有多种方法可以做到这一点,但在 Ubuntu 机器上最简单的方法是安装iptables-persistent包:

sudo apt install iptables-persistent

在安装过程中,你会看到两个屏幕,询问你是否希望保存当前的iptables 规则。第一个屏幕是用于 IPv4 规则的,而第二个是用于 IPv6 规则的:

19501_04_01.png

19501_04_01.png

你现在会看到在/etc/iptables/目录下有两个新的规则文件:

 donnie@ubuntu:~$ ls -l /etc/iptables*
 total 8
 -rw-r--r-- 1 root root 336 Oct 10 10:29 rules.v4
 -rw-r--r-- 1 root root 183 Oct 10 10:29 rules.v6
 donnie@ubuntu:~$

如果现在重启机器,你会看到你的 iptables 规则仍然存在并生效。iptables-persistent的唯一小问题是它不会保存你之后对规则所做的任何修改。不过没关系,我会稍后告诉你如何处理这个问题。

基础 iptables 使用的动手实验

你将在你的 Ubuntu 20.04 虚拟机上完成此实验。按照以下步骤开始:

  1. 关闭你的 Ubuntu 虚拟机并创建一个快照。重新启动后,使用此命令查看你的 iptables 规则,或者没有规则的情况:
sudo iptables -L
  1. 在默认的 Ubuntu 设置中,简单防火墙(ufw)服务已经在运行,尽管它的防火墙配置并未启用。我们需要禁用它,以便直接使用 iptables。现在就执行以下命令:
sudo systemctl disable --now ufw
  1. 创建你需要的基础防火墙规则,允许 Secure Shell 访问、DNS 查询和区域传输,并启用适当类型的 ICMP。拒绝其他所有流量:
sudo iptables -A INPUT -m conntrack  --ctstate ESTABLISHED,RELATED  -j ACCEPT
sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 3 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 11 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m conntrack -p icmp --icmp-type 12 --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -j DROP
  1. 使用此命令查看结果:
sudo iptables -L
  1. 哎呀,似乎你忘记了回环接口。为它添加一个规则,放在规则列表的最上面:
sudo iptables -I INPUT 1 -i lo -j ACCEPT
  1. 使用以下两个命令查看结果。注意每个命令输出的不同:
 sudo iptables -L
 sudo iptables -L -v
  1. 安装iptables-persistent包,并在提示时选择保存 IPv4 和 IPv6 规则:
sudo apt install iptables-persistent
  1. 重启虚拟机并验证你的规则仍然有效。

  2. 保留这台虚拟机,接下来你将在下一次动手实验中对其进行更多操作。

实验到此结束——祝贺你!

使用 iptables 阻止无效的数据包

如果你在 IT 行业工作了一段时间,你很可能熟悉传统的 TCP 三次握手。如果不熟悉也没关系,下面是简化版的解释。

假设你坐在工作站前,打开 Firefox 访问一个网站。为了访问该网站,工作站和 web 服务器必须建立连接。下面是发生的过程:

你的工作站向 web 服务器发送一个只设置了 SYN 标志的数据包。这是工作站向服务器打招呼的方式:“你好,服务器先生。我想和你建立连接。”

在接收到工作站的 SYN 数据包后,web 服务器会返回一个带有 SYNACK 标志的数据包。通过这个,服务器在说:“是的,兄弟。我在这里,愿意和你建立连接。”

当收到 SYN-ACK 数据包时,工作站返回一个只设置了 ACK 标志的数据包。通过这个,工作站在说:“好,伙计。我很高兴能和你建立连接。”

当收到 ACK 数据包时,服务器与工作站建立连接,以便它们可以交换信息。

这个序列设置 TCP 连接的方式是相同的,包括涉及安全外壳、Telnet 和各种邮件服务器协议的连接,等等。

然而,聪明的人可以使用各种工具制作带有一些非常奇怪标志组合的 TCP 数据包。通过这些所谓的 无效 数据包,可能会发生以下几种情况:

这些无效数据包可能被用来引发目标机器的响应,从而查找它运行的操作系统、正在运行的服务以及服务的版本。

这些无效数据包可能被用来触发目标机器上的某些安全漏洞。

其中一些无效数据包比正常数据包需要更多的处理能力,这可能使它们在执行拒绝服务攻击(DoS)时非常有用。

事实上,过滤表 INPUT 链末尾的 DROP all 规则会阻止一些无效数据包。然而,这个规则可能会漏掉一些数据包。即使我们能依赖它来阻挡所有无效的内容,这也不是最有效的做法。依赖这个 DROP all 规则,我们允许这些无效数据包通过整个 INPUT 链,寻找能放行它们的规则。当没有找到 ALLOW 规则时,它们最终会被 DROP all 规则阻挡,而这个规则是链中的最后一条。那么,如果我们能找到一个更高效的解决方案呢?

理想情况下,我们希望在这些无效数据包通过整个 INPUT 链之前就阻止它们。我们可以通过 PREROUTING 链来做到这一点,但过滤表没有 PREROUTING 链。因此,我们需要使用 mangle 表的 PREROUTING 链。让我们从添加以下两条规则开始:

 donnie@ubuntu:~$ sudo iptables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP

 donnie@ubuntu:~$ sudo iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
 donnie@ubuntu:~$

这些规则中的第一条将阻止我们认为是无效的绝大多数数据包。然而,它仍然有一些遗漏。因此,我们添加了第二条规则,阻止所有不是SYN包的NEW包。现在,让我们看看我们得到了什么:

 donnie@ubuntu:~$ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination         
 ACCEPT     all  --  anywhere             anywhere            
 ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
 ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
 DROP       all  --  anywhere             anywhere            

 Chain FORWARD (policy ACCEPT)
 target     prot opt source               destination         

 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination         
 donnie@ubuntu:~$

嗯……

我们看不到新规则,对吗?这是因为,默认情况下,iptables -L只显示过滤表的规则。我们需要查看刚刚放入 mangle 表的规则,所以我们可以这样做:

 donnie@ubuntu:~$ sudo iptables -t mangle -L
 Chain PREROUTING (policy ACCEPT)
 target     prot opt source               destination         
 DROP       all  --  anywhere             anywhere             ctstate INVALID
 DROP       tcp  --  anywhere             anywhere             tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination         
 Chain FORWARD (policy ACCEPT)
 target     prot opt source               destination         
 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination         
 Chain POSTROUTING (policy ACCEPT)
 target     prot opt source               destination         
 donnie@ubuntu:~$

在这里,我们使用了-t mangle选项,表示我们希望查看 mangle 表的配置。你可能已经注意到一个有趣的地方,那就是sudo iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP命令创建的规则,iptables 渲染出来的方式。由于某些原因,它渲染成了这样:

DROP     tcp  --  anywhere   anywhere    tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

看起来有点奇怪,但不要让它困扰你。它的意思仍然是,它阻止了那些不是SYN包的NEW包。

之前,我提到过iptables-persistent软件包不会保存对iptables规则的后续更改。所以,现在的情况是,我们刚刚添加的 mangle 表规则将在我重启这台虚拟机后消失。为了使这些更改永久生效,我将使用iptables-save命令将新文件保存在我的个人主目录中。然后,我会将该文件复制到/etc/iptables目录,替换原来的文件:

 donnie@ubuntu:~$ sudo iptables-save > rules.v4
 [sudo] password for donnie:
 donnie@ubuntu:~$ sudo cp rules.v4 /etc/iptables/
 donnie@ubuntu:~$

为了测试这个,我们将使用一个非常方便的工具,叫做 Nmap。它是一个免费的工具,你可以在 Windows、Mac 或 Linux 工作站上安装。如果你不想在主机上安装它,也可以在你的 Linux 虚拟机上安装它。它在 Debian/Ubuntu、RHEL/CentOS 7 和 RHEL/AlmaLinux 8 的常规软件库中都有。所以,只需使用适合你发行版的安装命令安装 Nmap 软件包。如果你想在 Windows 或 Mac 主机上安装 Nmap,则需要从 Nmap 网站下载它。

你可以从官方网站下载 Nmap,网址如下:nmap.org/download.html

在我们应用了新的 mangle 表规则后,让我们对我们的 Ubuntu 机器执行 XMAS 扫描。我在当前使用的 Fedora 工作站上安装了 Nmap,所以我就使用它。操作如下:

[donnie@fedora-teaching ~]$ sudo nmap -sX 192.168.0.15
 [sudo] password for donnie:
 Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:20 EDT
 Nmap scan report for 192.168.0.15
 Host is up (0.00052s latency).
 All 1000 scanned ports on 192.168.0.15 are open|filtered
 MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

 Nmap done: 1 IP address (1 host up) scanned in 21.41 seconds
 [donnie@fedora-teaching ~]$

默认情况下,Nmap 只扫描最常用的 1,000 个端口。XMAS 扫描发送由 FIN、PSH 和 URG 标志组成的无效数据包。所有 1,000 个扫描的端口都显示为open|filtered,这意味着扫描被阻止,Nmap 无法确定端口的真实状态。(实际上,端口22是开放的。)我们可以查看结果,看看是哪个规则进行了阻止。(为了简化一些,我只显示PREROUTING链的输出,因为这是唯一一个执行了操作的 mangle 表链):

donnie@ubuntu:~$ sudo iptables -t mangle -L -v
 Chain PREROUTING (policy ACCEPT 2898 packets, 434K bytes)
  pkts bytes target     prot opt in     out     source               destination    

  2000 80000 DROP  all  --  any    any     anywhere             anywhere             ctstate INVALID

  0     0 DROP       tcp  --  any    any     anywhere             anywhere             tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

 . . .
 . . .
 donnie@ubuntu:~$

在这里,你可以看到第一条规则——INVALID规则——阻止了 2000 个数据包和 80,000 字节的流量。现在,让我们将计数器清零,以便进行另一次扫描:

 donnie@ubuntu:~$ sudo iptables -t mangle -Z
 donnie@ubuntu:~$ sudo iptables -t mangle -L -v
 Chain PREROUTING (policy ACCEPT 22 packets, 2296 bytes)
 pkts bytes target prot opt in out source destination
 0 0 DROP all -- any any anywhere anywhere ctstate INVALID
 0 0 DROP tcp -- any any anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

 . . .
 . . .
 donnie@ubuntu:~$

这次,让我们进行一个窗口扫描,向目标机器发送ACK数据包:

[donnie@fedora-teaching ~]$ sudo nmap -sW 192.168.0.15
 Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:39 EDT
 Nmap scan report for 192.168.0.15
 Host is up (0.00049s latency).
 All 1000 scanned ports on 192.168.0.15 are filtered
 MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

 Nmap done: 1 IP address (1 host up) scanned in 21.44 seconds
 [donnie@fedora-teaching ~]$

和之前一样,扫描被阻止了,正如消息所示,所有 1,000 个扫描端口都已被过滤。现在,让我们查看目标 Ubuntu 机器上的情况:

 donnie@ubuntu:~$ sudo iptables -t mangle -L -v
 Chain PREROUTING (policy ACCEPT 45 packets, 6398 bytes)
  pkts bytes target     prot opt in     out     source               destination         

  0     0 DROP       all  --  any    any     anywhere             anywhere             ctstate INVALID

  2000 80000 DROP       tcp  --  any    any     anywhere             anywhere             tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW

 . . .
 . . .      
 donnie@ubuntu:~$

这次,我们可以看到无效的数据包通过了第一个规则,但被第二个规则阻止了。

现在,为了好玩,我们清除 mangle 表规则,再进行扫描。我们将使用-D选项从 mangle 表中删除这两个规则:

 donnie@ubuntu:~$ sudo iptables -t mangle -D PREROUTING 1
 donnie@ubuntu:~$ sudo iptables -t mangle -D PREROUTING 1
 donnie@ubuntu:~$

当你删除一个规则时,必须指定规则编号,就像插入规则时一样。在这里,我指定了规则 1 两次,因为删除第一个规则后,第二个规则上移到了第一个位置。现在,让我们确认规则已经被删除:

 donnie@ubuntu:~$ sudo iptables -t mangle -L
 Chain PREROUTING (policy ACCEPT)
 target prot opt source destination

 . . .
 . . .
 donnie@ubuntu:~$

是的,它们确实存在。太棒了。现在,让我们看看执行另一次 XMAS 扫描时会有什么结果:

 [donnie@fedora-teaching ~]$ sudo nmap -sX 192.168.0.15
 [sudo] password for donnie:
 Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:48 EDT
 Nmap scan report for 192.168.0.15
 Host is up (0.00043s latency).
 All 1000 scanned ports on 192.168.0.15 are open|filtered
 MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

 Nmap done: 1 IP address (1 host up) scanned in 21.41 seconds
 [donnie@fedora-teaching ~]$

即使没有 mangle 表规则,仍然显示我的扫描被阻止了。怎么回事?发生这种情况是因为在INPUT表的末尾仍然有DROP all规则。让我们禁用这个规则,看看另一次扫描的结果。

首先,我需要查看规则的编号:

donnie@ubuntu:~$ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination         
 ACCEPT     all  --  anywhere             anywhere            
 ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
 ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh
 DROP       all  --  anywhere             anywhere            

 Chain FORWARD (policy ACCEPT)
 target     prot opt source               destination         

 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination         
 donnie@ubuntu:~$

通过倒数,我看到是规则编号 4,所以我将删除它:

donnie@ubuntu:~$ sudo iptables -D INPUT 4
donnie@ubuntu:~$donnie@ubuntu:~$ sudo iptables -L
 Chain INPUT (policy ACCEPT)
 target     prot opt source               destination         
 ACCEPT     all  --  anywhere             anywhere            
 ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
 ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh

 Chain FORWARD (policy ACCEPT)
 target     prot opt source               destination         

 Chain OUTPUT (policy ACCEPT)
 target     prot opt source               destination         
 donnie@ubuntu:~$

现在,进行 XMAS 扫描:

[donnie@fedora-teaching ~]$ sudo nmap -sX 192.168.0.15
 Starting Nmap 7.70 ( https://nmap.org ) at 2019-07-26 21:49 EDT
 Nmap scan report for 192.168.0.15
 Host is up (0.00047s latency).
 Not shown: 999 closed ports
 PORT STATE SERVICE
 22/tcp open|filtered ssh
 MAC Address: 08:00:27:A4:95:1A (Oracle VirtualBox virtual NIC)

 Nmap done: 1 IP address (1 host up) scanned in 98.76 seconds
 [donnie@fedora-teaching ~]$

这次,扫描结果显示 999 个端口被关闭,端口22(SSH 端口)要么是开放的,要么是过滤的。这表明扫描不再被任何东西阻止。

恢复删除的规则

当我使用iptables -D命令时,我只删除了运行时配置中的规则,而没有删除rules.v4配置文件中的规则。要恢复删除的规则,我可以重启机器或重新启动netfilter-persistent服务。后者更快,所以我将这样启动它:

donnie@ubuntu:~$ sudo systemctl restart netfilter-persistent
[sudo] password for donnie:
donnie@ubuntu:~$

iptables -Liptables -t mangle -L命令将显示所有规则现在已经重新生效。

阻止无效 IPv4 数据包的动手实验

对于这个实验,你将使用与上一个实验相同的虚拟机。你不会替换已经存在的规则,而是只会添加几个规则。让我们开始吧:

  1. 查看过滤器表和 mangle 表中的规则。(请注意,-v选项显示有关被DROPREJECT规则阻止的包的统计信息。)然后,将阻止的数据包计数器清零:
sudo iptables -L -v
sudo iptables -t mangle -L -v
sudo iptables -Z
sudo iptables -t mangle -Z
  1. 从你的主机或其他虚拟机中,执行 NULL 和 Windows Nmap 扫描,针对虚拟机进行扫描:
sudo nmap -sN ip_address_of_your_VM
sudo nmap -sW ip_address_of_your_VM
  1. 重复步骤 1。你应该会看到在过滤器表INPUT链的最终DROP规则阻止的包数量大幅增加:
sudo iptables -L -v
sudo iptables -t mangle -L -v
  1. 通过使用 mangle 表的PREROUTING链来提高防火墙效率,丢弃无效的数据包,例如刚才执行的两次 Nmap 扫描产生的那些包。使用以下两个命令添加所需的两个规则:
sudo iptables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP
sudo iptables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
  1. 将新配置保存到你的个人目录。然后,将文件复制到正确的位置,并清除已阻止的数据包计数器:
sudo iptables-save > rules.v4
sudo cp rules.v4 /etc/iptables
sudo iptables -Z
sudo iptables -t mangle -Z
  1. 仅对虚拟机执行 NULL 扫描:
sudo nmap -sN ip_address_of_your_VM
  1. 查看 iptables 规则集,观察 Nmap 扫描触发了哪个规则:
sudo iptables -L -v
sudo iptables -t mangle -L -v
  1. 这次,仅对虚拟机执行 Windows 扫描:
sudo nmap -sW ip_address_of_your_VM
  1. 观察此次扫描触发了哪个规则:
sudo iptables -L -v
sudo iptables -t mangle -L -v

这就是本次实验的全部内容——祝贺你!

保护 IPv6

我知道,你习惯了所有网络都基于 IPv4,其简短、易用的 IP 地址。然而,这种情况不可能永远持续下去,因为全球 IPv4 地址已经用尽。IPv6 提供了一个更大的地址空间,将持续很长时间。一些组织,尤其是无线运营商,正在或已经切换到 IPv6。

到目前为止,我们所讲的只是如何使用 iptables 设置 IPv4 防火墙。但请记住我们之前所说的,使用 iptables 时,你需要为 IPv4 网络设置一个守护进程和一组规则,而为 IPv6 设置另一个守护进程和一组规则。这意味着在使用 iptables 设置防火墙时,保护 IPv6 意味着需要做两次配置。大多数 Linux 发行版默认启用了 IPv6 网络,因此你要么需要用防火墙保护它,要么禁用它。否则,即使你刚刚配置的 IPv4 防火墙不会保护它,你的 IPv6 地址仍然会暴露给攻击者。即使你的服务器或设备面向 IPv4 网络,这也依然适用,因为有方法通过 IPv4 网络隧道传输 IPv6 数据包。幸运的是,设置 IPv6 防火墙的命令与我们刚刚介绍的几乎相同,最大的区别在于,你将使用 ip6tables 命令,而不是 iptables 命令。让我们像配置 IPv4 时一样,从基本设置开始:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -i lo -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p tcp --dport ssh -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p tcp --dport 53 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p udp --dport 53 -j ACCEPT

IPv4 和 IPv6 之间的另一个大区别是,对于 IPv6,必须允许更多类型的 ICMP 消息,而不仅仅是 IPv4 所需的。这是由于以下原因:

  • 对于 IPv6,新的 ICMP 消息类型已取代了地址解析协议ARP)。

  • 对于 IPv6,动态 IP 地址分配通常是通过与其他主机交换 ICMP 探测消息,而不是通过 DHCP 完成的。

  • 对于 IPv6,当你需要通过 IPv4 网络隧道 IPv6 数据包时,必须使用回显请求和回显回复,即臭名昭著的 ping 数据包。

当然,我们仍然需要与 IPv4 相同类型的 ICMP 消息。那么,让我们从这些开始:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 1 -j ACCEPT
[sudo] password for donnie: 
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 2 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 3 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 4 -j ACCEPT
donnie@ubuntu3:~$

这些消息类型按出现顺序如下:

  • 目标不可达

  • 数据包过大

  • 超时

  • 数据包头部的参数问题

接下来,我们将启用回显请求(类型 128)和回显响应(类型 129),以便 IPv6 可以通过 IPv4 隧道工作:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 128 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 129 -j ACCEPT
donnie@ubuntu3:~$

Teredo 协议是将 IPv6 数据包通过 IPv4 网络隧道传输的几种方式之一。这个协议需要回显请求和回显回复,也就是著名的 ping 数据包,可以通过防火墙。但是,如果你在你的发行版的仓库中搜索 Teredo 包,你是找不到的。这是因为 Linux 实现的 Teredo 协议叫做 miredo。所以,在 Linux 机器上安装 Teredo 协议时,你需要安装 miredomiredo-server 包。

接下来的四种 ICMP 消息类型是用于链路本地多播接收者通知消息:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 130
donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 131
donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 132
donnie@ubuntu3:~$ sudo ip6tables -A INPUT --protocol icmpv6 --icmpv6-type 143
donnie@ubuntu3:~$

以下是按出现顺序排列的规则:

  • 监听查询

  • 监听报告

  • 监听完成

  • 监听报告 v2

接下来是我们的邻居和路由器发现消息类型:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 134 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 135 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 136 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 141 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 142 -j ACCEPT
donnie@ubuntu3:~$

以下是按出现顺序排列的规则:

  • 路由器请求

  • 路由器广告

  • 邻居请求

  • 邻居广告

  • 反向邻居发现请求

  • 反向邻居发现广告

空间有限,无法详细说明这些消息类型。暂时可以这么说,它们是 IPv6 主机动态分配 IPv6 地址所必需的。

如果你使用安全证书来验证附加到网络的路由器,你还需要允许 安全邻居发现 (SEND) 消息:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 148 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 149 -j ACCEPT
donnie@ubuntu3:~$

你的手指是不是已经累了?如果是,不用担心。接下来的这组 ICMP 规则是最后一组。这一次,我们需要允许多播路由器发现消息:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 151 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 152 -j ACCEPT
donnie@ubuntu3:~$ sudo ip6tables -A INPUT -p icmpv6 --icmpv6-type 153 -j ACCEPT
donnie@ubuntu3:~$

最后,我们将添加 DROP 规则来阻止其他所有流量:

donnie@ubuntu3:~$ sudo ip6tables -A INPUT -j DROP
donnie@ubuntu3:~$

我知道你在想:“哇,这样设置一个基本的防火墙要跳过这么多圈圈”。是的,你说得对,特别是当你还需要为 IPv6 配置规则时。很快,我会展示 Ubuntu 团队为简化这些过程所做的努力。

你可以在这里获取关于如何在 Ubuntu 上使用 iptables 的完整信息:help.ubuntu.com/community/IptablesHowTo

ip6tables 实验

对于这个实验,你将使用上一个 iptables 实验中使用的同一 Ubuntu 虚拟机。你将保留现有的 IPv4 防火墙设置不变,并为 IPv6 创建一个新的防火墙。让我们开始吧:

  1. 使用以下命令查看你的 IPv6 规则,或者查看是否没有规则:
sudo ip6tables -L
  1. 创建 IPv6 防火墙。由于格式限制,我无法在此列出完整的命令代码块。你可以在本章的目录中找到相应的命令,代码文件可以从 Packt Publishing 网站下载。

  2. 使用以下命令查看新的规则集:

sudo ip6tables -L
  1. 接下来,为阻止无效数据包设置 mangle 表规则:
sudo ip6tables -t mangle -A PREROUTING -m conntrack --ctstate INVALID -j DROP
sudo ip6tables -t mangle -A PREROUTING -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
  1. 将新的规则集保存到你自己主目录的文件中,然后将规则文件传输到正确的位置:
sudo ip6tables-save > rules.v6
sudo cp rules.v6 /etc/iptables/
  1. 使用以下命令获取虚拟机的 IPv6 地址:
ip a
  1. 在你安装了 Nmap 的机器上,对虚拟机的 IPv6 地址进行 Windows 扫描。命令应该是这样的,只不过用你自己的 IP 地址替换:
sudo nmap -6 -sW fe80::a00:27ff:fe9f:d923
  1. 在虚拟机上,观察使用此命令时触发了哪个规则:
sudo ip6tables -t mangle -L -v

你应该能看到某个规则的包计数器显示非零数字。

  1. 在你安装了 Nmap 的机器上,对虚拟机的 IPv6 地址进行 XMAS 扫描。命令应该是这样的,只不过用你自己的 IP 地址替换:
sudo nmap -6 -sX fe80::a00:27ff:fe9f:d923
  1. 和之前一样,在虚拟机上,观察这个扫描触发了哪个规则:
sudo ip6tables -t mangle -L -v
  1. 关闭虚拟机,并从开始时创建的基本 iptables 使用实验室快照恢复虚拟机。

实验到此结束,恭喜你!

到目前为止,你已经看到了iptables的优缺点以及它的丑陋面。它非常灵活,iptables命令也充满了强大功能。如果你擅长 shell 脚本,你可以编写一些相当复杂的脚本,用于在整个网络中的机器上部署防火墙。

另一方面,做到一切正确可能会非常复杂,特别是如果你需要考虑你的机器必须同时运行 IPv4 和 IPv6,并且你为 IPv4 所做的一切都必须为 IPv6 再做一次。(如果你是个受虐狂,或许你会喜欢这样。)

nftables——一种更通用的防火墙系统

现在,让我们将注意力转向 nftables,这个新兴的防火墙工具。那么,nftables 带来了什么呢?(是的,这个双关语是故意的。):

  • 你可以忘记需要为所有不同的网络组件设置单独的守护进程和实用工具。iptables、ip6tables、ebtables 和 arptables 的功能现在都合并在一个简洁的包里。nft 实用工具现在是你所需要的唯一防火墙工具。

  • 使用 nftables 时,你可以创建多维树来展示你的规则集。这使得故障排除变得更加轻松,因为现在更容易追踪一个包通过所有规则的过程。

  • 使用 iptables 时,无论是否使用,每个默认安装的表包括过滤器、NAT、mangle 和安全表。

  • 使用 nftables 时,你只需要创建你打算使用的表,从而提高性能。

  • 与 iptables 不同,你可以在一个规则中指定多个操作,而不必为每个操作创建多个规则。

  • 与 iptables 不同,新增规则是原子性添加的。(这是一个华丽的说法,意思是你不再需要重新加载整个规则集来仅仅添加一个规则。)

  • nftables 拥有内建的脚本引擎,允许你编写更高效、更易于阅读的脚本。

  • 如果你已经有了大量需要使用的 iptables 脚本,你可以安装一组实用工具,帮助你将它们转换为 nftables 格式。(除非你使用的是 Ubuntu 22.04,它可以自动为你转换 iptables 命令。)

虽然 nftables 是由 Red Hat 创建的,但 Ubuntu 是第一个将其作为选项提供的企业级 Linux 发行版,从 Ubuntu 16.04 开始。现在它已成为 Ubuntu 22.04、SUSE、OpenSUSE 和 RHEL 8/9 类型发行版的默认选项。让我们从一些基本的 nftables 概念开始。

了解 nftables 表和链

如果你习惯了 iptables,你可能会认出一些 nftables 的术语。唯一的问题是,有些术语的使用方式不同,含义也有所变化。让我们通过一些例子来讲解,这样你就能明白我在说什么:

  • Tables:在 nftables 中,表指的是特定的协议家族。表的类型有 ip、ip6、inet、arp、bridge 和 netdev。

  • Chains:nftables 中的链大致等同于 iptables 中的表。例如,在 nftables 中,你可能会有 filter、route 或 NAT 链。

开始使用 nftables

让我们从一台已安装 nftables 的 Ubuntu 22.04 虚拟机的干净快照开始。

提示:

如果你真的想使用 Ubuntu 20.04,你可以,但首先需要通过以下命令安装 nftables:

sudo apt install nftables

现在,让我们来看一下已安装表的列表:

sudo nft list tables

你没有看到任何表格,对吧?那么,让我们加载一些表格吧。

在 Ubuntu 上配置 nftables

在我们使用的 Ubuntu 虚拟机中,默认的 nftables.conf 文件不过是一个没有意义的占位符。你需要的文件会被放在其他地方,复制过来替换掉默认的 nftables.conf 文件。我们来看看。

首先,我们进入存放示例配置文件的目录,并列出示例配置文件:

cd /usr/share/doc/nftables/examples/
ls -l

你应该会看到类似这样的内容:

donnie@ubuntu2204-packt:/usr/share/doc/nftables/examples$ ls -l
total 124
-rw-r--r-- 1 root root  1016 Mar 23  2022 all-in-one.nft
-rw-r--r-- 1 root root   129 Mar 23  2022 arp-filter.nft
. . .
. . .
-rwxr-xr-x 1 root root   817 Mar 23  2022 workstation.nft
donnie@ubuntu2204-packt:/usr/share/doc/nftables/examples$

如果你查看 workstation.nft 文件的内容,你会看到它正是我们需要的文件。

接下来,我们将把工作站文件复制到 /etc 目录,并将其命名为 nftables.conf。 (注意,这将覆盖掉旧的 nftables.conf 文件,这正是我们想要的。)

sudo cp workstation.nft /etc/nftables.conf

下面是你将在 /etc/nftables.conf 文件中看到的内容解析:

  • #!/usr/sbin/nft -f:虽然你可以使用 nftables 命令创建普通的 Bash 脚本,但最好使用 nftables 自带的脚本引擎。这样,我们可以让脚本更具可读性,而且无需在每个要执行的命令前都输入 nft

  • flush ruleset:我们希望从一个干净的状态开始,所以我们将清除任何可能已经加载的规则。

  • table inet filter:这会创建一个 inet 家族的过滤器,它适用于 IPv4 和 IPv6。这个表的名字是 filter,但它也可以用一个更具描述性的名字。

  • chain input:在第一个大括号对内,我们有一个叫做 input 的链。(同样,这个名字本来可以更具描述性。)

  • type filter hook input priority 0;:在接下来的花括号对中,我们定义了我们的链并列出了规则。这个链被定义为filter类型。hook input表示这个链是用来处理传入数据包的。因为这个链有hookpriority,它将直接接受来自网络栈的数据包。

最后,我们有一些非常基本的主机防火墙标准规则,从输入接口iif)规则开始,该规则允许回环接口接受数据包。

接下来是标准的连接跟踪(ct)规则,它接受响应于来自此主机的连接请求的流量。

然后,有一条被注释掉的规则,用于接受安全外壳(Secure Shell)和安全与非安全的网页流量。ct state new 表示防火墙将允许其他主机在这些端口上发起连接到我们的服务器。

meta nfproto ipv6规则接受邻居发现数据包,允许 IPv6 功能。

末尾的counter drop规则默默地阻止所有其他流量,并统计它阻止的包的数量和字节数。(这是一个示例,说明一个 nftables 规则如何执行多个不同的操作。)

如果你的 Ubuntu 服务器只需要一个基本的、简单的防火墙,最好的方法就是直接编辑/etc/nftables.conf文件,使其符合你的需求。首先,让我们设置一个配置,和我们为 iptables 部分创建的设置相匹配。换句话说,假设这是一个 DNS 服务器,我们需要允许端口22和端口53的连接。删除tcp dport行前面的注释符号,去掉端口80443,并添加端口53。该行现在应该是这样的:

tcp dport { 22, 53 } ct state new accept

注意,你可以使用一个 nftables 规则打开多个端口。

DNS 也使用端口53/udp,所以我们为它添加一行:

udp dport 53 ct state new accept

当你只打开一个端口时,不需要将该端口号括在花括号内。打开多个端口时,只需将逗号分隔的列表括在花括号内,在每个逗号之后、第一个元素之前和最后一个元素之后留一个空格。

加载配置文件并查看结果:

donnie@ubuntu2204-packt:/etc$ sudo systemctl reload nftables
donnie@ubuntu2204-packt:/etc$ sudo nft list ruleset
table inet filter {
    chain input {
        type filter hook input priority 0; policy accept;
        iif "lo" accept
        ct state established,related accept
        tcp dport { ssh, domain } ct state new accept
        udp dport domain ct state new accept
        icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
        counter packets 1 bytes 32 drop
    }
}
donnie@ubuntu2204-packt:/etc$

counter drop规则是另一个示例,说明一个 nftables 规则如何做多件事。在这种情况下,该规则丢弃并统计不需要的包。到目前为止,该规则已经阻止了一个包和 32 个字节。为了演示这如何工作,假设我们希望在丢包时创建一条日志条目。只需将log关键字添加到drop规则中,如下所示:

counter log drop

为了让这些消息更容易查找,可以给每个日志消息添加一个标签,像这样:

counter log prefix "Dropped packet: " drop

现在,当你需要查看/var/log/kern.log文件,看看你丢了多少包时,只需搜索Dropped packet文本字符串。

现在,假设我们想要阻止某些 IP 地址访问这台机器的安全外壳端口。为此,我们可以编辑文件,将drop规则放在打开端口22的规则之上。文件的相关部分如下所示:

tcp dport 22 ip saddr { 192.168.0.7, 192.168.0.10 } log prefix "Blocked SSH packets: " drop
tcp dport { 22, 53 } ct state new accept

在我们重新加载文件后,我们将阻止来自两个不同 IPv4 地址的 SSH 访问。任何来自这两个地址的登录尝试都会在/var/log/kern.log文件中生成一条带有Blocked SSH packets标签的消息。请注意,我们将drop规则放在了accept规则之前,因为如果accept规则先被读取,drop规则将不起作用。

接下来,我们需要允许所需类型的 ICMP 数据包,如下所示:

ct state new,related,established icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
ct state established,related,new icmpv6 type { destination-unreachable, time-exceeded, parameter-problem } accept

在这种情况下,您需要为 ICMPv4 和 ICMPv6 创建独立的规则。

最后,我们将通过向过滤表添加一个新的 prerouting 链来阻止无效数据包,如下所示:

chain prerouting {
                type filter hook prerouting priority 0;
                ct state invalid counter log prefix "Invalid Packets:  " drop
                tcp flags & (fin|syn|rst|ack) != syn ct state new counter log drop
        }

现在,我们可以保存文件并关闭文本编辑器。

由于格式限制,我无法在此处显示完整的文件。要查看完整的文件,请从 Packt 网站下载代码文件,并查看Chapter 4目录。您要找的示例文件是nftables_example_1.conf文件。

现在,让我们加载新规则:

sudo systemctl reload nftables

另一个值得注意的非常酷的事情是,我们如何将 IPv4(ip)规则与 IPv6(ip6)规则混合在同一个配置文件中。此外,除非我们另行指定,否则我们创建的所有规则都将同时适用于 IPv4 和 IPv6。这就是使用 inet 类型表的魅力所在。为了简化和灵活性,您应该尽可能使用 inet 表,而不是为 IPv4 和 IPv6 分别使用不同的表。

大多数时候,当您只需要一个简单的主机防火墙时,最佳选择是将此nftables.conf文件作为起点,并编辑该文件以满足自己的需求。然而,您还可以使用命令行工具,它可能会对您有所帮助。

使用 nft 命令

我与 nftables 合作的首选方法是从模板开始,并像我们在上一节中做的那样手动编辑它。但对于那些更愿意从命令行做一切的人,nft 工具是一个选择。

提示:

即使您知道自己总是通过手动编辑nftables.conf来创建防火墙,了解 nft 工具仍然有一些实际原因。

假设您观察到正在进行的攻击,并且需要快速阻止它,而不让系统宕机。通过nft命令,您可以即时创建一个自定义规则来阻止攻击。即时创建 nftables 规则还可以让您在配置防火墙时进行测试,而不会做出任何永久性更改。

如果您决定参加 Linux 安全认证考试,可能会看到有关nft命令的问题。(我恰好知道。)

使用nft工具有两种方式。第一种是你可以直接从 Bash shell 中执行所有操作,每次执行操作时都以nft开头,后接nft的子命令。另一种方式是以交互模式使用nft。出于目前的目的,我们将使用 Bash shell。

首先,让我们删除之前的配置并创建一个inet表,因为我们希望它同时适用于 IPv4 和 IPv6。我们希望给它起个相对描述性的名称,所以我们叫它ubuntu_filter

sudo nft delete table inet filter
sudo nft list tables
sudo nft add table inet ubuntu_filter
sudo nft list tables

接下来,我们将向刚才创建的表中添加一个输入过滤链(请注意,由于我们是在 Bash shell 中执行此操作,所以需要使用反斜杠转义分号):

sudo nft add chain inet ubuntu_filter input { type filter hook input priority 0\; policy drop\; }

我们本可以给它起个更具描述性的名称,但目前input就足够了。在一对大括号内,我们设置了此链的参数。

每个 nftables 协议族都有自己的一组钩子,用于定义如何处理数据包。现在,我们只关注 ip/ip6/inet 协议族,它们有以下钩子:

  • 前路由

  • 输入

  • 转发

  • 输出

  • 后路由

在这些中,我们只关注输入和输出钩子,这些钩子适用于过滤类型的链。通过为我们的输入链指定钩子和优先级,我们表示希望将此链作为基本链,直接接受来自网络栈的数据包。你还会看到某些参数必须以分号终止,如果你是在 Bash shell 中运行这些命令,分号需要用反斜杠进行转义。最后,我们指定了默认策略为drop。如果我们没有指定drop作为默认策略,那么默认策略将是accept

提示:

每个你输入的nft命令都会立即生效。所以,如果你是远程操作,一旦你创建了一个默认策略为drop的过滤链,你的 Secure Shell 连接会被断开。

有些人喜欢创建一个默认策略为accept的链,然后添加一个drop规则作为最后一条规则。另一些人则喜欢创建一个默认策略为drop的链,然后在末尾省略drop规则。务必检查你所在组织的本地操作规程,了解其偏好。

验证链是否已添加。你应该能看到类似这样的内容:

donnie@ubuntu2004-packt:~$ sudo nft list table inet ubuntu_filter
 [sudo] password for donnie:
 table inet filter {
       chain input {
             type filter hook input priority 0; policy drop;
       }
 }
 donnie@ubuntu2004-packt:~$

很好,但我们仍然需要一些规则。我们从一个连接跟踪规则和一个打开 Secure Shell 端口的规则开始。接着,我们将验证它们是否已添加:

sudo nft add rule inet ubuntu_filter input ct state established accept
sudo nft add rule inet ubuntu_filter input tcp dport 22 ct state new accept
sudo nft list table inet ubuntu_filter
 table inet ubuntu_filter {
     chain input {
         type filter hook input priority 0; policy drop;
         ct state established accept
         tcp dport ssh ct state new accept
     }
 }

好的,看起来不错。你现在有了一个基本的、可工作的防火墙,允许 Secure Shell 连接。嗯,除非我们像在本章的 iptables 部分一样,忘记创建一个规则来允许环回适配器接受数据包。由于我们希望这个规则位于规则列表的顶部,所以我们将使用insert而不是add

sudo nft insert rule inet ubuntu_filter input iif lo accept
sudo nft list table inet ubuntu_filter
 table inet ubuntu_filter {
       chain input {
              type filter hook input priority 0; policy drop;
              iif lo accept
              ct state established accept
              tcp dport ssh ct state new accept
      }
 }

现在,我们一切准备就绪。但如果我们想在特定位置插入规则呢?为此,你需要使用带有-a选项的列表来查看规则句柄:

sudo nft list table inet ubuntu_filter -a
 table inet ubuntu_filter {
        chain input {
                 type filter hook input priority 0; policy drop;
                 iif lo accept # handle 4
                 ct state established accept # handle 2
                 tcp dport ssh ct state new accept # handle 3
       }
 }

如你所见,句柄的编号方式并没有什么规律。假设我们要插入一个规则,阻止某些 IP 地址访问安全外壳端口。我们可以看到 SSH 的accept规则是handle 3,因此我们需要在它之前插入我们的drop规则。这个命令看起来会像这样:

sudo nft insert rule inet ubuntu_filter input position 3 tcp dport 22 ip saddr { 192.168.0.7, 192.168.0.10 } drop
sudo nft list table inet ubuntu_filter -a
 table inet ubuntu_filter {
         chain input {
                  type filter hook input priority 0; policy drop;
                  iif lo accept # handle 4
                  ct state established accept # handle 2
                  tcp dport ssh ip saddr { 192.168.0.10, 192.168.0.7} drop # handle 6
                  tcp dport ssh ct state new accept # handle 3
         }
 }

所以,要将规则插入到带有handle 3标签的规则之前,我们必须将它插入到3的位置。我们刚刚插入的新规则的标签是handle 6。要删除一个规则,我们必须指定该规则的句柄编号:

sudo nft delete rule inet ubuntu_filter input handle 6
sudo nft list table inet ubuntu_filter -a
 table inet ubuntu_filter {
       chain input {
              type filter hook input priority 0; policy drop;
              iif lo accept # handle 4
              ct state established accept # handle 2
              tcp dport ssh ct state new accept # handle 3
      }
 }

和 iptables 一样,从命令行做的所有事情在重启机器后都会消失。为了使其永久生效,我们需要将list子命令的输出重定向到nftables.conf配置文件中(当然,我们要确保备份现有的文件,以防我们想恢复到之前的状态):

sudo sh -c "nft list table inet ubuntu_filter > /etc/nftables.conf"

由于 Bash shell 的一个小问题,即使我们使用sudo,我们也不能像正常方式那样将输出重定向到/etc/目录中的文件。这就是为什么我必须添加sh -c命令,并将nft list命令放在双引号中。还要注意,文件必须命名为nftables.conf,因为这是 nftables 的 systemd 服务所查找的文件。现在,当我们查看文件时,会发现有几个缺失的内容:

table inet ubuntu_filter { 
    chain input { 
        type filter hook input priority 0; policy drop; 
        iif lo accept 
        ct state established accept 
        tcp dport ssh ct state new accept 
    } 
}

眼尖的朋友会发现我们缺少了flush规则和 shebang 行来指定我们希望解释此脚本的 shell。让我们添加它们:

#!/usr/sbin/nft -f 
flush ruleset 
table inet ubuntu_filter { 
    chain input { 
         type filter hook input priority 0; policy drop; 
         iif lo accept 
         ct state established accept 
         tcp dport ssh ct state new accept 
   } 
} 

好得多。让我们通过加载新配置并观察list输出,来测试这个:

sudo systemctl reload nftables
sudo nft list table inet ubuntu_filter
 table inet ubuntu_filter {
        chain input {
                 type filter hook input priority 0; policy drop;
                 iif lo accept
                 ct state established accept
                 tcp dport ssh ct state new accept
       }
 }

这就是创建你自己的简单主机防火墙的全部内容。当然,从命令行运行命令,而不是仅仅在文本编辑器中创建一个脚本文件,确实需要更多的输入。不过,这样做可以让你在创建规则的同时即时测试它们。而且以这种方式创建配置,然后将list输出重定向到你的新配置文件中,可以避免你在尝试手动编辑文件时需要跟踪那些大括号的麻烦。

也可以将我们刚刚创建的所有nft命令放入一个普通的、老式的 Bash shell 脚本中。不过,相信我,你真的不想这么做。只需使用我们在这里使用的 nft 原生脚本格式,你将拥有一个性能更好、更加易于阅读的脚本。

Ubuntu 上的 nftables 动手实验

对于这个实验,你需要一个干净的 Ubuntu 22.04 虚拟机快照。让我们开始吧。

恢复你的 Ubuntu 虚拟机到一个干净的快照,以清除你之前创建的任何防火墙配置。(或者,如果你更喜欢,从一个新的虚拟机开始。)禁用 ufw 并验证没有防火墙规则存在:

sudo systemctl disable --now ufw
sudo iptables -L

你应该不会看到 nftables 中列出任何规则。

workstation.nft模板复制到/etc/目录并重命名为nftables.conf

sudo cp /usr/share/doc/nftables/examples/syntax/workstation /etc/nftables.conf

编辑/etc/nftables.conf文件以创建新的配置。(请注意,由于格式限制,我必须将其分为三个不同的代码块。)使文件的顶部部分如下所示:

#!/usr/sbin/nft -f flush ruleset
table inet filter {
    chain prerouting {
        type filter hook prerouting priority 0;
        ct state invalid counter log prefix "Invalid Packets:  " drop
        tcp flags & (fin|syn|rst|ack) != syn ct state new counter log prefix "Invalid Packets 2: " drop
}

使文件的第二部分如下所示:

chain input {
    type filter hook input priority 0;
    # accept any localhost traffic
    iif lo accept
    # accept traffic originated from us
    ct state established,related accept
        # activate the following line to accept common local services
        tcp dport 22 ip saddr { 192.168.0.7, 192.168.0.10 } log prefix "Blocked SSH packets: " drop

        tcp dport { 22, 53 } ct state new accept
        udp dport 53 ct state new accept
        ct state new,related,established icmp type { destination-unreachable, time-exceeded, parameter-problem } accept

使文件的最后部分如下所示:

 ct state new,related,established icmpv6 type { destination-unreachable, time-exceeded, parameter-problem } accept
        # accept neighbour discovery otherwise IPv6 connectivity breaks.
        ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit,  nd-router-advert, nd-neighbor-advert } accept
# count and drop any other traffic
    counter log prefix "Dropped packet: " drop
     }
}

保存文件并重新加载 nftables:

 sudo systemctl reload nftables

查看结果:

sudo nft list tables
sudo nft list tables
sudo nft list table inet filter
sudo nft list ruleset

从你的主机计算机或其他虚拟机上,扫描 Ubuntu 虚拟机的 Windows:

sudo nmap -sW ip_address_of_UbuntuVM

查看数据包计数器,看看哪个阻止规则被触发。(提示:它在预路由链中。):

sudo nft list ruleset

这次,对虚拟机进行空扫描:

sudo nmap -sN ip_address_of_UbuntuVM

最后,查看这次触发了哪个规则。(提示:它是在预路由链中的另一个规则。):

sudo nft list ruleset

/var/log/kern.log文件中,搜索Invalid Packets文本串,以查看有关丢弃的无效数据包的消息。

这就是本实验的结束——恭喜你!

在本节中,我们详细了解了 nftables 的各个方面,并讨论了如何配置它以帮助防止某些类型的攻击。在下一章中,我们将介绍我们的辅助程序的辅助程序。

总结

在本章中,我们讨论了与 netfilter 防火墙直接交互的两个辅助程序。首先,我们回顾了我们可信赖的老朋友 iptables。我们看到,尽管它已经存在了很长时间并且仍然有效,但它确实有一些不足之处。接着,我们使用了 nftables,发现它在一些方面优于旧版的 iptables。

在本章分配的篇幅内,我只能呈现你设置基本主机保护所需的要点。不过,这应该足够让你入门。

在下一章中,我们将介绍 ufw 和 firewalld,它们是我们在本章讨论的两个辅助程序的辅助程序。到时候见。

问题

  1. 以下哪项陈述是正确的?

    1. iptables 是最容易操作的防火墙系统。

    2. 使用 iptables 时,任何你创建的规则都适用于 IPv4 和 IPv6。

    3. 使用 iptables 时,必须将 IPv6 规则与 IPv4 规则分开创建。

    4. 使用 nftables 时,必须将 IPv6 规则与 IPv4 规则分开创建。

  2. Linux 防火墙的正式名称是什么?

    1. iptables

    2. ufw

    3. nftables

    4. netfilter

  3. 关于 nftables,下列哪项陈述是错误的?

    1. 使用 nftables 时,规则是原子地添加的。

    2. 使用 nftables 时,表格指的是特定的协议族。

    3. 使用 nftables 时,端口及其关联规则会被打包到区域中。

    4. 使用 nftables,你可以编写常规 bash 脚本,或使用 nftables 内置的脚本引擎。

  4. 哪个 iptables 命令会显示特定规则丢弃了多少数据包?

  5. 你会使用哪个 nftables 命令来查看特定规则丢弃了多少数据包?

  6. 在 iptables 中,以下哪一个目标会导致数据包被阻止,而不向源发送通知?

    1. STOP

    2. DROP

    3. REJECT

    4. BLOCK

  7. 以下六个选项中,哪些是 iptables 中的表?

    1. netfilter

    2. filter

    3. 混淆

    4. 安全

    5. ip6table

    6. NAT

  8. 哪种防火墙系统会原子化加载其规则?

进一步阅读

答案

  1. C

  2. D

  3. C

  4. sudo iptables -L -v

  5. sudo nft list ruleset

  6. B

  7. B, C, D, F

  8. nftables

第六章:5 使用防火墙保护您的服务器 - 第二部分

加入我们的书籍社区 Discord

packt.link/SecNet

第四章使用防火墙保护您的服务器 - 第一部分 中,我们讨论了 iptables 和 nftables,这是直接与 netfilter 接口的管理实用工具。虽然熟悉 iptables 和 nftables 命令可以帮助创建高级防火墙配置,但经常需要使用这些命令来执行日常操作可能会有些不便。在本章中,我们将看看 ufw 和 firewalld,这些是辅助工具,可以简化与 iptables 或 nftables 的工作过程。

首先,我们将看看 ufw。我们将查看它的结构、命令和配置。然后,我们将对 firewalld 做同样的操作。在两种情况下,您都将获得大量的实践操作。

我们将在本章中涵盖以下主题:

  • ufw 适用于 Ubuntu 系统

  • firewalld 适用于 Red Hat 系统

技术要求

本章的代码文件在这里可用:github.com/PacktPublishing/Mastering-Linux-Security-and-Hardening-Second-Edition.

适用于 Ubuntu 系统的简单防火墙

ufw 已经安装在 Ubuntu 20.04 和 Ubuntu 22.04 上。在 Ubuntu 20.04 上仍使用 iptables 后端,在 Ubuntu 22.04 上使用 nftables 后端。对于日常操作,它提供了大大简化的命令集。执行一个简单的命令以打开所需端口,再执行另一个简单的命令以激活它,您就有了一个良好的基本防火墙。每次执行ufw命令时,它将自动配置 IPv4 和 IPv6 规则。这单独就是一个巨大的时间节省器,很多我们之前需要手动配置的内容在默认情况下已经包含了。尽管我们的两个 Ubuntu 版本使用了不同的后端,但 ufw 的配置对它们两者来说是相同的。

提示:

ufw 也适用于 Debian 和其他基于 Debian 的发行版,但可能未安装。如果是这种情况,请通过发出sudo apt install ufw命令来安装它。

配置 ufw

在 Ubuntu 20.04 和 Ubuntu 22.04 上,ufw 服务已默认启用,但防火墙本身尚未激活。换句话说,系统的服务正在运行,但尚未执行任何防火墙规则。(稍后我们将向您展示如何在我们讨论完需要打开的端口后激活它。)使用以下两个命令检查 ufw 状态:

systemctl status ufw
sudo ufw status

systemctl 命令应该显示服务已启用,而 ufw 命令应该显示防火墙未激活。

我们首先要做的事情是打开端口22,以允许通过安全外壳连接到机器,如下所示:

sudo ufw allow 22/tcp

好的,看起来不错。现在让我们像这样激活防火墙:

sudo ufw enable

通过在 Ubuntu 20.04 上使用 sudo iptables -L,你将看到新的 Secure Shell 规则出现在 ufw-user-input 链中:

Chain ufw-user-input (1 references)
 target prot opt source destination
 ACCEPT tcp -- anywhere anywhere tcp dpt:ssh

在 Ubuntu 22.04 上,使用 sudo nft list ruleset 命令查看 ufw-user-input 链中的新规则:

chain ufw-user-input {
        meta l4proto tcp tcp dport 22 counter packets 0 bytes 0 accept
    }

你还会看到,这两个命令的输出非常冗长,因为我们用裸 iptables 或 nftables 必须做的许多工作,ufw 已经为我们完成了。事实上,这里甚至有比我们用 iptables 和 nftables 做的更多的内容。例如,使用 ufw 时,我们已经有了速率限制规则,帮助我们防范 拒绝服务 (DoS) 攻击,我们还有记录被阻止的包的日志规则。这几乎是设置防火墙的“轻松不麻烦”的方式。(稍后我会讲讲那个 几乎 的部分。)

在前面的 sudo ufw allow 22/tcp 命令中,我们必须指定 TCP 协议,因为 Secure Shell 只需要 TCP 协议。如果你没有指定协议,也可以同时为 TCP 和 UDP 打开一个端口。例如,如果你正在设置 DNS 服务器,你需要为两个协议都打开端口 53。(你会看到端口 53 的条目列为域名端口)。在任一版本的 Ubuntu 上,执行:

 sudo ufw allow 53

在 Ubuntu 20.04 中,通过以下命令查看结果:

 sudo iptables -L
. . .
. . .
    Chain ufw-user-input (1 references)
    target prot opt source destination
    ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
    ACCEPT tcp -- anywhere anywhere tcp dpt:domain
    ACCEPT udp -- anywhere anywhere udp dpt:domain

在 Ubuntu 22.04 中,通过以下命令查看结果:

sudo nft list ruleset
chain ufw-user-input {
        meta l4proto tcp tcp dport 22 counter packets 0 bytes 0 accept
        meta l4proto tcp tcp dport 53 counter packets 0 bytes 0 accept
        meta l4proto udp udp dport 53 counter packets 0 bytes 0 accept
    }

如果你在 20.04 机器上执行 sudo ip6tables -L,你会看到针对 IPv6 的规则也已经添加,适用于之前的两个示例。同样,你会看到我们使用 ip6tables 命令时需要做的大部分工作,ufw 已经帮我们处理好了。(尤其好的是,我们不需要处理那些麻烦的 ICMP 规则。)在 22.04 机器上,你之前执行的 sudo nft list ruleset 命令会在 ufw6-user-input 段落中显示 IPv6 配置。

要快速查看防火墙配置的摘要,请使用 status 选项。输出应该类似如下:

donnie@ubuntu-ufw:~$ sudo ufw status
Status: active
To                         Action      From
--                         ------      ----
22/tcp                     LIMIT       Anywhere                  
53                         LIMIT       Anywhere                 
22/tcp (v6)                LIMIT       Anywhere (v6)             
53 (v6)                    LIMIT       Anywhere (v6)             

donnie@ubuntu-ufw:~$

接下来,我们将查看 ufw 配置文件。

使用 ufw 配置文件

你可以在 /etc/ufw/ 目录中找到 ufw 防火墙规则。正如你所看到的,规则存储在多个不同的文件中:

donnie@ubuntu-ufw:/etc/ufw$ ls -l
total 48
-rw-r----- 1 root root  915 Aug  7 15:23 after6.rules
-rw-r----- 1 root root 1126 Jul 31 14:31 after.init
-rw-r----- 1 root root 1004 Aug  7 15:23 after.rules
drwxr-xr-x 3 root root 4096 Aug  7 16:45 applications.d
-rw-r----- 1 root root 6700 Mar 25 17:14 before6.rules
-rw-r----- 1 root root 1130 Jul 31 14:31 before.init
-rw-r----- 1 root root 3467 Aug 11 11:36 before.rules
-rw-r--r-- 1 root root 1391 Aug 15  2017 sysctl.conf
-rw-r--r-- 1 root root  313 Aug 11 11:37 ufw.conf
-rw-r----- 1 root root 3014 Aug 11 11:37 user6.rules
-rw-r----- 1 root root 3012 Aug 11 11:37 user.rules
donnie@ubuntu-ufw:/etc/ufw$

在列表的底部,你会看到 user6.rulesuser.rules 文件。你不能手动编辑这两个文件。虽然你可以在编辑后保存文件,但当你使用 sudo ufw reload 加载新更改时,你会发现你的编辑已被删除。让我们查看 user.rules 文件,看看里面有什么内容。

提示:

如你所见,Ubuntu 20.04 和 22.04 的所有文件都包含 iptables 格式的防火墙规则,即使 22.04 使用 nftables 作为后端。这是因为 Ubuntu 22.04 可以自动将 iptables 规则转换为 nftables 规则。因此,20.04 和 22.04 的文件是相同的,这让我们操作起来非常方便。

在文件的顶部,你会看到定义了 iptables 过滤表以及它的关联链表:

*filter
:ufw-user-input - [0:0]
:ufw-user-output - [0:0]
:ufw-user-forward - [0:0]
. . .
. . .

接下来,在### RULES ###部分,我们列出了使用ufw命令创建的规则。以下是我们打开 DNS 端口的规则示例:

### tuple ### allow any 53 0.0.0.0/0 any 0.0.0.0/0 in
-A ufw-user-input -p tcp --dport 53 -j ACCEPT
-A ufw-user-input -p udp --dport 53 -j ACCEPT

正如你所看到的,ufw 在其配置文件中使用 iptables 语法,即使是在 Ubuntu 22.04 上也是如此。

### RULES ###部分下方,我们可以看到有关防火墙阻止的任何数据包的日志消息规则:

### LOGGING ###
-A ufw-after-logging-input -j LOG --log-prefix "[UFW BLOCK] " -m limit --limit 3/min --limit-burst 10
-A ufw-after-logging-forward -j LOG --log-prefix "[UFW BLOCK] " -m limit --limit 3/min --limit-burst 10
-I ufw-logging-deny -m conntrack --ctstate INVALID -j RETURN -m limit --limit 3/min --limit-burst 10
-A ufw-logging-deny -j LOG --log-prefix "[UFW BLOCK] " -m limit --limit 3/min --limit-burst 10
-A ufw-logging-allow -j LOG --log-prefix "[UFW ALLOW] " -m limit --limit 3/min --limit-burst 10
### END LOGGING ###

这些消息会发送到/var/log/kern.log文件。为了避免在大量数据包被阻止时压垮日志系统,我们将每分钟最多发送三条消息到日志文件,并设置每分钟 10 条消息的突发限制。大多数规则会在日志消息中插入[UFW BLOCK]标签,方便我们查找它们。最后一条规则会创建带有[UFW ALLOW]标签的消息,奇怪的是,INVALID规则并不会插入任何标签。

最后,我们有了速率限制规则,每个用户每分钟只允许三次连接:

### RATE LIMITING ###
-A ufw-user-limit -m limit --limit 3/minute -j LOG --log-prefix "[UFW LIMIT BLOCK] "
-A ufw-user-limit -j REJECT
-A ufw-user-limit-accept -j ACCEPT
### END RATE LIMITING ###

超过该限制的任何数据包将会以[UFW LIMIT BLOCK]标签记录在/var/log/kern.log文件中。

/etc/ufw user6.rules文件看起来几乎一样,只不过它是用于 IPv6 规则的。每次你使用ufw命令创建或删除规则时,它都会同时修改user.rules文件和user6.rules文件。

为了存储在user.rulesuser6.rules文件之前运行的规则,我们有before.rules文件和before6.rules文件。为了存储在user.rulesuser6.rules文件之后运行的规则,我们有——你猜对了——after.rules文件和after6.rules文件。如果你需要添加不能通过ufw命令添加的自定义规则,只需手动编辑这对文件之一。(稍后我们会详细讨论这个问题。)

如果你查看beforeafter文件,你会看到很多已经为我们处理好的内容。这些都是我们曾经需要使用 iptables/ip6tables 或 nftables 手动完成的工作。

然而,正如你可能知道的,这些 ufw 的优势中有一个小小的注意事项。你可以使用 ufw 工具执行简单的任务,但任何更复杂的操作都需要手动编辑文件。(这就是我说 ufw 是几乎不麻烦、不复杂的原因。)

提示:

要查看更多使用ufw命令的示例,可以通过执行以下命令查看其手册页:

man ufw

例如,在before文件中,你会看到其中一个阻止无效数据包的规则已经被实现。以下是before.rules文件中的代码片段,通常可以在文件的顶部找到:

# drop INVALID packets (logs these in loglevel medium and higher)
-A ufw-before-input -m conntrack --ctstate INVALID -j ufw-logging-deny
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP

这两条规则中的第二条实际上丢弃了无效的数据包,而第一条则记录了它们。但正如我们在 第四章 中的 iptables 概述 部分所见,通过防火墙保护服务器第一部分,这一特定的 DROP 规则并不会阻止所有无效的数据包。而且,为了提高性能,我们宁愿将这个规则放在 mangle 表中,而不是现在所在的 filter 表中。为了解决这个问题,我们将编辑两个 before 文件。在你喜欢的文本编辑器中打开 /etc/ufw/before.rules 文件,寻找文件底部的以下一对行:

# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT

COMMIT 行下方,添加以下代码片段以创建 mangle 表规则:

# Mangle table added by Donnie
*mangle
:PREROUTING ACCEPT [0:0]
-A PREROUTING -m conntrack --ctstate INVALID -j DROP
-A PREROUTING -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
COMMIT

现在,我们将对 /etc/ufw/before6.rules 文件重复此过程。然后,通过以下命令重新加载规则:

sudo ufw reload

通过在 Ubuntu 20.04 上使用 iptables -Lip6tables -L 命令,或在 Ubuntu 22.04 上使用 nft list ruleset 命令,你将看到新规则出现在 mangle 表中,正是我们希望它们出现的位置。

基本 ufw 使用的实操实验

你需要在 Ubuntu 20.04 或 Ubuntu 22.04 的干净快照虚拟机上完成此实验。让我们开始吧:

  1. 关闭你的 Ubuntu 虚拟机,并恢复快照以删除你刚才所做的所有 iptables 或 nftables 设置。(或者,如果你更喜欢,可以从一台全新的虚拟机开始。)

  2. 重启虚拟机后,验证 iptables 规则是否已经消失。在 Ubuntu 20.04 上执行:

sudo iptables -L
On Ubuntu 22.04 do:
sudo nft list ruleset
  1. 查看 ufw 的状态。打开端口 22/TCP,然后启用 ufw。接着,查看结果:
sudo ufw status
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status
On Ubuntu 20.04 do:
sudo iptables -L
sudo ip6tables -L
On Ubuntu 22.04 do:
sudo nft list ruleset
  1. 这次,打开端口 53,同时为 TCP 和 UDP 都开放:
sudo ufw allow 53
sudo ufw status
On Ubuntu 20.04 do:
sudo iptables -L
sudo ip6tables -L
On Ubuntu 22.04 do:
sudo nft list ruleset
  1. cd 进入 /etc/ufw/ 目录。熟悉该目录下文件的内容。

  2. 在你喜欢的文本编辑器中打开 /etc/ufw/before.rules 文件。在文件底部,COMMIT 行下面,添加以下代码片段:

# Mangle table added by Donnie
*mangle
:PREROUTING ACCEPT [0:0]
-A PREROUTING -m conntrack --ctstate INVALID -j DROP
-A PREROUTING -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m conntrack --ctstate NEW -j DROP
COMMIT
(Note that the second PREROUTING command wraps around on the printed page.)
  1. /etc/ufw/before6.rules 文件重复执行步骤 6

  2. 使用以下命令重新加载防火墙:

sudo ufw reload
  1. 在 Ubuntu 20.04 上,执行以下命令查看规则:
sudo iptables -L
sudo iptables -t mangle -L
sudo ip6tables -L
sudo ip6tables -t mangle -L
On Ubuntu 22.04, observe the rules by doing:
sudo nft list ruleset
  1. 快速查看 ufw 的状态:
sudo ufw status

实验结束了——恭喜你!

我想你会同意,ufw 是非常酷的技术。它用来执行基本任务的命令比等效的 iptables 或 nftables 命令更容易记住,而且只需一个命令就能同时处理 IPv4 和 IPv6。在我们的任一版本的 Ubuntu 上,你仍然可以通过手动编辑 ufw 配置文件来做一些复杂的事情。但,ufw 并不是唯一一个非常酷的防火墙管理工具。接下来的部分,我们将看看 Red Hat 的开发者给我们提供了什么。

Red Hat 系统的 firewalld

接下来,我们将注意力转向 firewalld,它是 Red Hat Enterprise Linux 7 至 9 以及所有衍生版本的默认防火墙管理工具。

就像我们在 Ubuntu 上看到的 ufw 一样,firewalld 可以是 iptables 或 nftables 的前端。在 RHEL/CentOS 7 上,firewalld 使用 iptables 引擎作为后端。在 RHEL 8 和 9 类型的发行版中,firewalld 使用 nftables 作为后端。不管怎样,当 firewalld 启用时,你不能使用普通的 iptables 或 nftables 命令来创建规则,因为 firewalld 将规则存储在不兼容的格式中。

提示:

直到最近,firewalld 仅在较新的 RHEL 版本及其衍生版本中可用。然而,现在 firewalld 也可以在 Ubuntu 的软件库中找到。所以,如果你想在 Ubuntu 上运行 firewalld,现在终于可以选择了。此外,firewalld 和 nftables 的组合现在已经在 SUSE 系统中预安装并启用。

如果你在桌面机器上运行 Red Hat、CentOS 或 AlmaLinux,你会发现应用程序菜单中有一个 firewalld 的 GUI 前端。但是在文本模式服务器上,你只有 firewalld 命令。出于某种原因,Red Hat 并没有为文本模式服务器创建一个类似 ncurses 的程序,就像他们为旧版本的 Red Hat 中的 iptables 配置做的那样。

firewalld 的一个大优势是它是动态管理的。这意味着你可以在不重启防火墙服务的情况下更改防火墙配置,并且不会中断与服务器的现有连接。

在我们查看 RHEL 7/CentOS 7 与 RHEL/AlmaLinux 8 和 9 版本的 firewalld 差异之前,让我们先看一下两者相同的部分。

验证 firewalld 状态

对于本节内容,你可以使用 CentOS 7、AlmaLinux 8 或 AlmaLinux 9 虚拟机。我们先从验证 firewalld 的状态开始。这样有两种方法。第一种方法是使用 firewall-cmd--state 选项:

[donnie@localhost ~]$ sudo firewall-cmd --state
 running
 [donnie@localhost ~]$

另外,如果我们需要更详细的状态信息,我们可以检查守护进程,就像我们在 systemd 系统上检查其他守护进程一样:

[donnie@localhost ~]$ sudo systemctl status firewalld
 firewalld.service - firewalld - dynamic firewall daemon
  Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled;
 vendor preset: enabled)
  Active: active (running) since Fri 2017-10-13 13:42:54 EDT; 1h 56min ago
  Docs: man:firewalld(1)
  Main PID: 631 (firewalld)
  CGroup: /system.slice/firewalld.service
  └─631 /usr/bin/python -Es /usr/sbin/firewalld --nofork --nopid
. . .
 Oct 13 15:19:41 localhost.localdomain firewalld[631]: WARNING: reject-
 route: INVALID_ICMPTYPE: No supported ICMP type., ignoring for run-time.
 [donnie@localhost ~]$

接下来,让我们看看 firewalld 区域。

使用 firewalld 区域

firewalld 是一种非常独特的工具,它带有几个预配置的区域和服务。如果你查看任何 CentOS 或 AlmaLinux 机器的 /usr/lib/firewalld/zones/ 目录,你会看到所有的区域文件,它们都是 .xml 格式:

[donnie@localhost ~]$ cd /usr/lib/firewalld/zones
[donnie@localhost zones]$ ls
block.xml dmz.xml drop.xml external.xml home.xml internal.xml public.xml
trusted.xml work.xml
[donnie@localhost zones]$

每个区域文件指定了在不同情况下需要开放的端口和需要阻止的端口。区域还可以包含 ICMP 消息、转发端口、伪装信息和丰富语言规则。例如,设置为默认的公共区域的 .xml 文件看起来是这样的:

<?xml version="1.0" encoding="utf-8"?> 
<zone> 
 <short>Public</short> 
 <description>For use in public areas. You do not trust the other 
computers on networks to not harm your computer. Only selected incoming 
connections are accepted.</description> 
 <service name="ssh"/> 
 <service name="dhcpv6-client"/> 
</zone> 

service name 行中,你可以看到唯一开放的端口是用于安全外壳访问和 DHCPv6 发现的端口。如果你查看 home.xml 文件,你会发现它还开放了用于多播 DNS 的端口,以及允许此机器从 Samba 服务器或 Windows 服务器访问共享目录的端口:

<?xml version="1.0" encoding="utf-8"?> 
<zone> 
 <short>Home</short> 
 <description>For use in home areas. You mostly trust the other computers 
on networks to not harm your computer. Only selected incoming connections 
are accepted.</description> 
 <service name="ssh"/>
 <service name="mdns"/> 
 <service name="samba-client"/> 
 <service name="dhcpv6-client"/> 
</zone>

firewall-cmd工具是用来配置firewalld的。你可以使用它查看系统中区域文件的列表,而无需cd进入区域文件目录:

[donnie@localhost ~]$ sudo firewall-cmd --get-zones
[sudo] password for donnie:
block dmz drop external home internal public trusted work
[donnie@localhost ~]$

快速查看每个区域配置的方法是使用--list-all-zones选项:

[donnie@localhost ~]$ sudo firewall-cmd --list-all-zones
 block
  target: %%REJECT%%
  icmp-block-inversion: no
  interfaces:
  sources:
  services:
  ports:
  protocols:
  masquerade: no
  forward-ports:
. . .
. . .

当然,这只是输出的一部分,因为所有区域的列表比我们在这里显示的要多。你更有可能只想查看一个特定区域的信息:

 [donnie@localhost ~]$ sudo firewall-cmd --info-zone=internal
 internal
  target: default
  icmp-block-inversion: no
  interfaces:
  sources:
  services: ssh mdns samba-client dhcpv6-client
  ports:
  protocols:
  masquerade: no
  forward-ports:
 source-ports:
  icmp-blocks:
  rich rules:
 [donnie@localhost ~]$

所以,internal区域允许sshmdnssamba-clientdhcpv6-client服务。这对于在内部局域网上设置客户端机器非常方便。

每个给定的服务器或客户端都将拥有一个或多个已安装的网络接口适配器。每个适配器在一台机器中只能分配一个、且仅能分配一个 firewalld 区域。要查看默认区域,可以执行以下操作:

[donnie@localhost ~]$ sudo firewall-cmd --get-default-zone
 public
[donnie@localhost ~]$

这很好,除了它没有告诉你与此区域关联的网络接口是什么。要查看该信息,请执行以下操作:

[donnie@localhost ~]$ sudo firewall-cmd --get-active-zones
 public
  interfaces: enp0s3
[donnie@localhost ~]$

当你第一次安装 Red Hat、CentOS 或 AlmaLinux 时,防火墙会默认启用,并且公共区域是默认区域。现在,假设你在 DMZ 中设置服务器,并希望确保它的防火墙为此进行锁定。你可以将默认区域更改为dmz区域。我们来看一下dmz.xml文件,看看这对我们有什么帮助:

<?xml version="1.0" encoding="utf-8"?> 
<zone> 
 <short>DMZ</short> 
 <description>For computers in your demilitarized zone that are publicly- 
accessible with limited access to your internal network. Only selected 
incoming connections are accepted.</description> 
 <service name="ssh"/> 
</zone> 

所以,DMZ 区域允许通过的唯一内容是安全外壳(SSH)。好吧;现在这样就足够了,让我们将dmz区域设置为默认区域:

[donnie@localhost ~]$ sudo firewall-cmd --set-default-zone=dmz
 [sudo] password for donnie:
 success
[donnie@localhost ~]$

让我们验证一下:

[donnie@localhost ~]$ sudo firewall-cmd --get-default-zone
 dmz
[donnie@localhost ~]$

一切都好了。然而,位于 DMZ 中的面向互联网的服务器可能需要允许的不仅仅是 SSH 连接。这时我们将使用 firewalld 服务。但在查看这些之前,我们先考虑一个更重要的点。

小贴士:

设置默认区域时,你不需要使用--permanent选项。事实上,如果你使用了该选项,会出现错误信息。

你永远不应修改/usr/lib/firewalld/目录下的文件。每当你修改 firewalld 配置时,修改后的文件会出现在/etc/firewalld/目录下。到目前为止,我们只修改了默认区域。因此,我们将在/etc/firewalld/目录下看到以下文件:

[donnie@localhost ~]$ sudo ls -l /etc/firewalld
 total 12
 -rw-------. 1 root root 2003 Oct 11 17:37 firewalld.conf
 -rw-r--r--. 1 root root 2006 Aug 4 17:14 firewalld.conf.old
 . . .

我们可以对这两个文件做一个diff,查看它们之间的差异:

[donnie@localhost ~]$ sudo diff /etc/firewalld/firewalld.conf /etc/firewalld/firewalld.conf.old
 6c6
 < DefaultZone=dmz
 ---
 > DefaultZone=public
 [donnie@localhost ~]$

所以,较新的这两个文件显示dmz区域现在是默认区域。

小贴士:

要了解更多关于 firewalld 区域的信息,可以输入man firewalld.zones命令。

将服务添加到 firewalld 区域

每个服务文件都包含需要为特定服务打开的端口列表。可选地,服务文件可能包含一个或多个目标地址,或者调用所需的任何模块,例如连接跟踪。对于某些服务,你只需打开一个端口。其他服务,如 Samba 服务,则要求打开多个端口。无论哪种情况,记住与每个服务对应的服务名称,有时比记住端口号更方便。

服务文件位于/usr/lib/firewalld/services/目录中。你可以使用firewall-cmd命令查看它们,就像查看区域列表一样:

[donnie@localhost ~]$ sudo firewall-cmd --get-services
 RH-Satellite-6 amanda-client amanda-k5-client bacula bacula-client bitcoin bitcoin-rpc bitcoin-testnet bitcoin-testnet-rpc ceph ceph-mon cfengine condor-collector ctdb dhcp dhcpv6 dhcpv6-client dns docker-registry dropbox-lansync elasticsearch freeipa-ldap freeipa-ldaps freeipa-replication freeipa-trust ftp ganglia-client ganglia-master high-availability http https imap imaps ipp ipp-client ipsec iscsi-target kadmin kerberos kibana klogin kpasswd kshell ldap ldaps libvirt libvirt-tls managesieve mdns mosh mountd ms-wbt mssql mysql nfs nrpe ntp openvpn ovirt-imageio ovirt-storageconsole ovirt-vmconsole pmcd pmproxy pmwebapi pmwebapis pop3 pop3s postgresql privoxy proxy-dhcp ptp pulseaudio puppetmaster quassel radius rpc-bind rsh rsyncd samba samba-client sane sip sips smtp smtp-submission smtps snmp snmptrap spideroak-lansync squid ssh synergy syslog syslog-tls telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server
[donnie@localhost ~]$

在添加更多服务之前,让我们检查一下哪些服务已经启用了:

[donnie@localhost ~]$ sudo firewall-cmd --list-services
[sudo] password for donnie: 
ssh dhcpv6-client
[donnie@localhost ~]$

这里,ssh 和 dhcpv6-client 就是我们拥有的所有服务。

dropbox-lansync服务对我们这些 Dropbox 用户来说非常方便。让我们看看它打开了哪些端口:

[donnie@localhost ~]$ sudo firewall-cmd --info-service=dropbox-lansync
 [sudo] password for donnie:
 dropbox-lansync
  ports: 17500/udp 17500/tcp
  protocols:
  source-ports:
  modules:
  destination:
[donnie@localhost ~]$

看起来 Dropbox 使用 UDP 和 TCP 的端口17500

现在,假设我们将 Web 服务器设置在 DMZ 中,并将dmz区域设为其默认区域:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=dmz
 dmz (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
[donnie@localhost ~]$

正如我们之前看到的,只有 Secure Shell 端口是开放的。让我们修复它,这样用户就能实际访问我们的网站了:

[donnie@localhost ~]$ sudo firewall-cmd --add-service=http
 success
[donnie@localhost ~]$

当我们再次查看dmz区域的信息时,我们会看到以下内容:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=dmz
 dmz (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh http
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
[donnie@localhost ~]$

在这里,我们可以看到http服务现在已经允许通过了。但是,当我们在这个info命令中添加--permanent选项时,看看会发生什么:

[donnie@localhost ~]$ sudo firewall-cmd --permanent --info-zone=dmz
 dmz
  target: default
  icmp-block-inversion: no
  interfaces:
  sources:
  services: ssh
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
[donnie@localhost ~]$

哎呀!http服务不见了。发生了什么事?

几乎每个命令行更改区域或服务时,你都需要添加--permanent选项以使更改在重启后保持生效。但是如果没有--permanent选项,更改会立即生效。加上--permanent选项后,你需要重新加载防火墙配置,才能使更改生效。为了演示这一点,我将重启虚拟机以去掉http服务。

好的,我已经重启了,http服务现在已经不见了:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=dmz
 dmz (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
[donnie@localhost ~]$

这次,我将通过一个命令添加两个服务,并指定更改将是永久的:

[donnie@localhost ~]$ sudo firewall-cmd --permanent --add-service={http,https}
 [sudo] password for donnie:
 success
[donnie@localhost ~]$

你可以通过一个命令添加多个服务,但必须用逗号分隔它们,并将整个列表包含在一对花括号内。而且,与我们刚才看到的 nftables 不同,花括号内不能有空格。让我们看看结果:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=dmz
 dmz (active)
 target: default
 icmp-block-inversion: no
 interfaces: enp0s3
 sources:
 services: ssh
 ports:
 protocols:
 masquerade: no
 forward-ports:
 source-ports:
 icmp-blocks:
 rich rules:
[donnie@localhost ~]$

由于我们决定将此配置设置为永久生效,但它尚未生效。然而,如果我们在--info-zone命令中添加--permanent选项,就会看到配置文件确实已经发生了变化:

[donnie@localhost ~]$ sudo firewall-cmd --permanent --info-zone=dmz
 dmz
  target: default
  icmp-block-inversion: no
  interfaces:
  sources:
  services: ssh http https
  ports:
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
[donnie@localhost ~]$

现在,我们需要重新加载配置,以使其生效:

[donnie@localhost ~]$ sudo firewall-cmd --reload
 success
[donnie@localhost ~]$

现在,如果你再次运行sudo firewall-cmd --info-zone=dmz命令,你会看到新的配置已经生效。

要从某个区域中移除一个服务,只需将--add-service替换为--remove-service

提示:

注意,我们在这些服务命令中从未指定我们正在使用哪个区域。这是因为,如果我们不指定区域,firewalld 会默认认为我们是在使用默认区域。如果你想将服务添加到默认区域以外的区域,只需在命令中添加--zone=选项。

向 firewalld 区域添加端口

拥有服务文件非常方便,除了并非每个你需要运行的服务都有自己预定义的服务文件。假设你在服务器上安装了 Webmin,它需要端口 10000/tcp 开放。快速使用 grep 操作会显示端口 10000 并不在我们的预定义服务列表中:

donnie@localhost services]$ pwd
 /usr/lib/firewalld/services
[donnie@localhost services]$ grep '10000' *
[donnie@localhost services]$

那么,让我们将该端口添加到我们的默认区域,仍然是 dmz 区域:

donnie@localhost ~]$ sudo firewall-cmd --add-port=10000/tcp
 [sudo] password for donnie:
 success
[donnie@localhost ~]$

再次说明,这不是永久性的,因为我们没有包含 --permanent 选项。我们重新执行并重新加载:

[donnie@localhost ~]$ sudo firewall-cmd --permanent --add-port=10000/tcp
 success
[donnie@localhost ~]$ sudo firewall-cmd --reload
 success
[donnie@localhost ~]$

你还可以通过将逗号分隔的端口列表放入一对大括号中来一次性添加多个端口,就像我们处理服务一样。(我故意没有包含 --permanent 选项,稍后你会明白为什么):

[donnie@localhost ~]$ sudo firewall-cmd --add-port={636/tcp,637/tcp,638/udp}
 success
[donnie@localhost ~]$

当然,你也可以通过用 --remove-port 替代 --add-port 来从区域中删除端口。

如果你不想每次创建永久规则时都输入 --permanent,可以省略这个参数。然后,当你完成创建规则后,使用以下命令一次性将所有规则设置为永久:

sudo firewall-cmd --runtime-to-permanent

现在,让我们把注意力转向控制 ICMP。

阻止 ICMP

再次查看默认公共区域的状态:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources: 
  services: ssh dhcpv6-client
  ports: 53/tcp 53/udp
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 
[donnie@localhost ~]$

在页面底部,我们可以看到 icmp-block 行,旁边没有任何内容。这意味着我们的公共区域允许所有 ICMP 数据包通过。当然,这并不理想,因为我们有些 ICMP 类型的数据包是希望阻止的。在阻止任何东西之前,让我们先看看所有可用的 ICMP 类型:

[donnie@localhost ~]$ sudo firewall-cmd --get-icmptypes
[sudo] password for donnie: 
address-unreachable bad-header communication-prohibited destination-unreachable echo-reply echo-request fragmentation-needed host-precedence-violation host-prohibited host-redirect host-unknown host-unreachable ip-header-bad neighbour-advertisement neighbour-solicitation network-prohibited network-redirect network-unknown network-unreachable no-route packet-too-big parameter-problem port-unreachable precedence-cutoff protocol-unreachable redirect required-option-missing router-advertisement router-solicitation source-quench source-route-failed time-exceeded timestamp-reply timestamp-request tos-host-redirect tos-host-unreachable tos-network-redirect tos-network-unreachable ttl-zero-during-reassembly ttl-zero-during-transit unknown-header-type unknown-option
[donnie@localhost ~]$

和处理区域及服务一样,我们可以查看不同 ICMP 类型的信息。在这个例子中,我们查看一种 ICMPv4 类型和一种 ICMPv6 类型:

[donnie@localhost ~]$ sudo firewall-cmd --info-icmptype=network-redirectnetwork-redirect  destination: ipv4
[donnie@localhost ~]$ sudo firewall-cmd --info-icmptype=neighbour-advertisementneighbour-advertisement  
destination: ipv6
[donnie@localhost ~]$

我们已经看到没有阻止任何 ICMP 数据包。我们还可以查看是否有阻止特定的 ICMP 数据包:

[donnie@localhost ~]$ sudo firewall-cmd --query-icmp-block=host-redirect
no
[donnie@localhost ~]$

我们已经确定,重定向可能是一个坏东西,因为它们可能被恶意利用。所以,让我们阻止主机重定向数据包:

[donnie@localhost ~]$ sudo firewall-cmd --add-icmp-block=host-redirect
success
[donnie@localhost ~]$ sudo firewall-cmd --query-icmp-block=host-redirect
yes
[donnie@localhost ~]$

现在,让我们检查状态:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources: 
  services: ssh dhcpv6-client
  ports: 53/tcp 53/udp
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: host-redirect
  rich rules: 
[donnie@localhost ~]$

很棒—它成功了。现在,我们看看是否能通过一个命令同时阻止两种 ICMP 类型:

[donnie@localhost ~]$ sudo firewall-cmd --add-icmp-block={host-redirect,network-redirect}
success
[donnie@localhost ~]$ 

如之前所述,我们将检查状态:

[donnie@localhost ~]$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources: 
  services: cockpit dhcpv6-client ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: host-redirect network-redirect
  rich rules: 
[donnie@localhost ~]$

这也成功了,意味着我们已经实现了“酷”操作。然而,由于我们没有在这些命令中加入 --permanent,这些 ICMP 类型的阻止仅在重启前有效。因此,我们将它们设置为永久:

[donnie@localhost ~]$ sudo firewall-cmd --runtime-to-permanent
success
[donnie@localhost ~]$

这样,我们就实现了更多的“酷”。(当然,我所有的猫已经觉得我很酷了。)

使用恐慌模式

你刚刚看到了坏人试图破坏你的系统的证据。你该怎么办?其中一个选择是激活 panic 模式,这会切断所有网络通信。

我现在可以想象在周六早上的卡通片里,某个卡通人物大喊:“恐慌模式,启动!

要激活 panic 模式,使用以下命令:

[donnie@localhost ~]$ sudo firewall-cmd --panic-on
[sudo] password for donnie: 
success
[donnie@localhost ~]$

当然,如果你是远程登录的,访问会被切断,你需要去本地终端重新连接。要关闭 panic 模式,可以使用以下命令:

[donnie@localhost ~]$ sudo firewall-cmd --panic-off
[sudo] password for donnie: 
success
[donnie@localhost ~]$

如果你是远程登录的,则无需检查panic模式的状态。如果它开启,你就无法访问这台机器。但如果你坐在本地控制台上,可能想要检查它。只需执行:

[donnie@localhost ~]$ sudo firewall-cmd --query-panic
[sudo] password for donnie: 
no
[donnie@localhost ~]$

这就是panic模式的全部内容。

记录丢弃的数据包

这是另一个省时的技巧,你一定会喜欢。如果你希望每当数据包被阻止时创建日志条目,只需使用--set-log-denied选项。在我们这么做之前,先看看它是否已经启用:

[donnie@localhost ~]$ sudo firewall-cmd --get-log-denied
[sudo] password for donnie: 
off
[donnie@localhost ~]$

它不是开启状态,所以让我们开启它并再次检查状态:

[donnie@localhost ~]$ sudo firewall-cmd --set-log-denied=all
success
[donnie@localhost ~]$ sudo firewall-cmd --get-log-denied
all
[donnie@localhost ~]$

我们已经设置了记录所有被拒绝的数据包。然而,你可能并不总是希望这样。你的其他选择有unicastbroadcastmulticast

例如,如果你只想记录被阻止并且指向多播地址的数据包,可以执行如下操作:

[donnie@localhost ~]$ sudo firewall-cmd --set-log-denied=multicast
[sudo] password for donnie: 
success
[donnie@localhost ~]$ sudo firewall-cmd --get-log-denied
multicast
[donnie@localhost ~]$

到目前为止,我们只是设置了运行时配置,一旦重启机器,这些配置将会消失。为了使其永久生效,我们可以使用我们已经使用过的任何方法。现在,我们只需执行这个操作:

[donnie@localhost ~]$ sudo firewall-cmd --runtime-to-permanent
success
[donnie@localhost ~]$

与我们在 Debian/Ubuntu 发行版中看到的不同,我们没有专门的kern.log文件来记录数据包被拒绝的消息。相反,RHEL 类型的发行版将数据包拒绝消息记录在/var/log/messages文件中,这是 RHEL 世界中的主日志文件。已经定义了多个不同的消息标签,这将使得审计丢弃的数据包日志变得更容易。例如,以下是一个消息,告诉我们被阻止的广播数据包:

Aug 20 14:57:21 localhost kernel: FINAL_REJECT: IN=enp0s3 OUT= MAC=ff:ff:ff:ff:ff:ff:00:1f:29:02:0d:5f:08:00 SRC=192.168.0.225 DST=255.255.255.255 LEN=140 TOS=0x00 PREC=0x00
 TTL=64 ID=62867 DF PROTO=UDP SPT=21327 DPT=21327 LEN=120

这个标签是FINAL_REJECT,它告诉我们这条消息是由我们输入链末尾的通用REJECT规则创建的。DST=255.255.255.255部分告诉我们这是一个广播消息。

这是另一个示例,我对这台机器进行了 Nmap NULL 扫描:

sudo nmap -sN 192.168.0.8
Aug 20 15:06:15 localhost kernel: STATE_INVALID_DROP: IN=enp0s3 OUT= MAC=08:00:27:10:66:1c:00:1f:29:02:0d:5f:08:00 SRC=192.168.0.225 DST=192.168.0.8 LEN=40 TOS=0x00 PREC=0x00 TTL=42 ID=27451 PROTO=TCP SPT=46294 DPT=23 WINDOW=1024 RES=0x00 URGP=0

在这种情况下,我触发了阻止INVALID数据包的规则,如STATE_INVALID_DROP标签所示。

那么,现在你可能会说,等一下。我们刚刚测试的这两条规则在我们查看过的 firewalld 配置文件中都没有找到。这是怎么回事? 你说得对。这些默认的、预配置的规则的位置显然是 Red Hat 的人希望对我们隐藏的。不过,在接下来的专门针对 RHEL/CentOS 7 和 RHEL/AlmaLinux 8 以及 9 的部分中,我们将揭示他们的秘密,因为我能告诉你这些规则在哪里。

使用 firewalld 丰富语言规则

到目前为止,我们所看的内容可能就是一般使用场景下所需的全部,但如果需要更细致的控制,你会想了解丰富语言规则。(没错,这就是它们的名称。)

与 iptables 规则相比,丰富语言规则稍微不那么晦涩,更接近普通英语。因此,如果你是编写防火墙规则的新手,可能会觉得丰富语言更容易学习。另一方面,如果你已经习惯了编写 iptables 规则,可能会觉得丰富语言的某些元素有点怪异。让我们看一个例子:

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="200.192.0.0/24" service name="http" drop'

在这里,我们添加了一个丰富规则,阻止来自整个 IPv4 地址地理块的网站访问。请注意,整个规则被一对单引号包围,每个参数的赋值被一对双引号包围。通过这个规则,我们声明我们正在使用 IPv4,并且我们希望静默阻止http端口接受来自200.192.0.0/24网络的数据包。我在这里使用了--permanent选项,因为如果不使用它,AlmaLinux 9 会有点怪异。让我们看看应用这个新规则后我们的区域是什么样的:

[donnie@localhost ~]$ sudo firewall-cmd --permanent --info-zone=dmz
  dmz (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh http https
  ports: 10000/tcp 636/tcp 637/tcp 638/udp
. . .
. . .
  rich rules:
  rule family="ipv4" source address="200.192.0.0/24" service name="http"
 drop
 [donnie@localhost ~]$

丰富规则显示在底部。

你可以通过将family="ipv4"替换为family="ipv6"并提供相应的 IPv6 地址范围,轻松编写 IPv6 的规则。

有些规则是通用的,适用于 IPv4 或 IPv6。例如,假设我们希望记录关于网络时间协议NTP)数据包的消息,适用于 IPv4 和 IPv6,并且我们希望每分钟不超过记录一条消息。创建该规则的命令如下:

sudo firewall-cmd --add-rich-rule='rule service name="ntp" audit limit value="1/m" accept'

当然,firewalld 丰富语言规则的内容远不止我们在这里呈现的内容。但现在,您已经掌握了基本知识。欲了解更多信息,请查阅 man 页面:

man firewalld.richlanguage

如果你访问 Red Hat Enterprise Linux 8 的官方文档页面,你会发现没有提到丰富规则。然而,我刚刚在一台 RHEL 8 类型的机器和一台 RHEL 9 类型的机器上测试了它们,它们工作得很好。

要阅读丰富规则的相关内容,您需要访问 Red Hat Enterprise Linux 7 的文档页面。这里的内容同样适用于 RHEL 8/9。但即便如此,那里也没有太多详细内容。要了解更多信息,请查阅 RHEL/CentOS 7 或 RHEL/CentOS 8,或者 RHEL/AlmaLinux 9 的 man 页面。

要使规则永久生效,只需使用我们已经讨论过的任何方法。当您这样做时,规则将出现在默认区域的.xml文件中。就我而言,默认区域仍然设置为公共区域。所以,让我们看看/etc/firewalld/zones/public.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
  <service name="ssh"/>
  <service name="dhcpv6-client"/>
  <service name="cockpit"/>
  <rule family="ipv4">
    <source address="192.168.0.225"/>
    <service name="http"/>
    <drop/>
  </rule>
</zone>

我们的丰富规则显示在文件底部的rule family块中。

现在我们已经覆盖了 RHEL/CentOS 7 与 RHEL/CentOS/AlmaLinux 8/9 版本的 firewalld 之间的共同点,接下来我们来看看每个版本的特有内容。

查看 RHEL/CentOS 7 firewalld 中的 iptables 规则

RHEL 7 及其衍生版本使用 iptables 引擎作为 firewalld 的后端。在 firewalld 启用时,不能使用常规的 iptables 命令创建规则。然而,每次使用firewall-cmd命令创建规则时,iptables 后端会创建相应的 iptables 规则并将其插入到正确的位置。您可以通过iptables -L查看活动规则。以下是非常长的输出的第一部分:

[donnie@localhost ~]$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ACCEPT     all  --  anywhere             anywhere            
INPUT_direct  all  --  anywhere             anywhere            
INPUT_ZONES_SOURCE  all  --  anywhere             anywhere            
INPUT_ZONES  all  --  anywhere             anywhere            
DROP       all  --  anywhere             anywhere             ctstate INVALID
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

就像在 Ubuntu 上的 ufw 一样,很多配置已经为我们完成了。在 INPUT 链的顶部,我们可以看到连接状态规则和阻止无效数据包的规则已经存在。该链的默认策略是 ACCEPT,但是链的最后一条规则设置为 REJECT 所有没有明确允许的内容。在这些规则之间,我们可以看到将其他数据包指向其他链进行处理的规则。现在,让我们看下一个部分:

Chain IN_public_allow (1 references)
target     prot opt source               destination         
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:ssh ctstate NEW
ACCEPT     tcp  --  anywhere             anywhere             tcp dpt:domain ctstate NEW
ACCEPT     udp  --  anywhere             anywhere             udp dpt:domain ctstate NEW
Chain IN_public_deny (1 references)
target     prot opt source               destination         
REJECT     icmp --  anywhere             anywhere             icmp host-redirect reject-with icmp-host-prohibited

在很长的输出的底部,我们可以看到 IN_public_allow 链,其中包含我们为开放防火墙端口而创建的规则。紧接着是 IN_public_deny 链,里面包含了用于阻止不需要的 ICMP 类型的 REJECT 规则。在 INPUT 链和 IN_public_deny 链中,REJECT 规则会返回 ICMP 消息,通知发送者数据包已被阻止。

现在,请记住,我们没有展示很多 IPTABLES -L 的输出内容。所以,自己看看输出,看看里面有什么。当你查看时,你可能会问自己,这些默认规则存储在哪里?为什么我在 /etc/firewalld/ 目录下没有看到它们?

为了回答这个问题,我进行了相当广泛的调查。由于某些真正奇怪的原因,Red Hat 的人们完全没有文档化这一内容。我最终在/usr/lib/python2.7/site-packages/firewall/core/目录下找到了答案。在这里,有一组 Python 脚本用于设置初始的默认防火墙:

[donnie@localhost core]$ ls
base.py fw_config.pyc fw_helper.pyo fw_ipset.py fw_policies.pyc fw_service.pyo fw_zone.py icmp.pyc ipset.pyc logger.pyo rich.py base.pyc fw_config.pyo fw_icmptype.py fw_ipset.pyc fw_policies.pyo fw_test.py fw_zone.pyc icmp.pyo ipset.pyo modules.py rich.pyc base.pyo fw_direct.py fw_icmptype.pyc fw_ipset.pyo fw.py fw_test.pyc fw_zone.pyo __init__.py ipXtables.py modules.pyc rich.pyo ebtables.py fw_direct.pyc fw_icmptype.pyo fw_nm.py fw.pyc fw_test.pyo helper.py __init__.pyc ipXtables.pyc modules.pyo watcher.py ebtables.pyc fw_direct.pyo fw_ifcfg.py fw_nm.pyc fw.pyo fw_transaction.py helper.pyc __init__.pyo ipXtables.pyo prog.py watcher.pyc ebtables.pyo fw_helper.py fw_ifcfg.pyc fw_nm.pyo fw_service.py fw_transaction.pyc helper.pyo io logger.py prog.pyc watcher.pyo fw_config.py fw_helper.pyc fw_ifcfg.pyo fw_policies.py fw_service.pyc fw_transaction.pyo icmp.py ipset.py logger.pyc prog.pyo
[donnie@localhost core]$

执行大部分工作的脚本是 ipXtables.py 脚本。如果你查看它,你会发现其中的 iptables 命令列表与 iptables -L 输出相匹配。

在 RHEL/CentOS 7 上创建直接规则

正如我们所看到的,每当我们在 RHEL/CentOS 7 上使用普通的 firewall-cmd 命令时,firewalld 会自动将这些命令转换为 iptables 规则,并将它们插入到正确的位置。(或者,如果你发出了删除命令,它会删除规则。)然而,有一些事情我们无法通过普通的 firewall-cmd 命令来做。例如,我们无法使用普通的 firewall-cmd 命令将规则放置在特定的 iptables 链或表中。要做这样的事情,我们需要使用直接配置命令。

firewalld.direct 手册页以及 Red Hat 网站上的文档都警告你,只有在其他方法都不起作用时,才应使用直接配置。这是因为,与普通的 firewall-cmd 命令不同,直接命令不会自动将新规则放入正确的位置,从而确保一切正常工作。使用直接命令时,如果将规则放错位置,可能会导致整个防火墙崩溃。

在上一节的示例输出中,在默认的规则集中,你会看到在过滤器表的INPUT链中有一条规则阻止无效数据包。在第四章使用防火墙保护服务器 - 第一部分阻止无效数据包与 iptables部分中,你看到这条规则漏掉了一些类型的无效数据包。所以,我们想添加第二条规则来阻止第一条规则漏掉的内容。我们还希望将这些规则放入 mangle 表的PREROUTING链中,以提高防火墙性能。为此,我们需要创建几条直接规则。(如果你熟悉常规的 iptables 语法,这并不难。)那么,让我们开始吧。

首先,让我们验证一下是否没有任何有效的直接规则,方法如下:

sudo firewall-cmd --direct --get-rules ipv4 mangle PREROUTING
sudo firewall-cmd --direct --get-rules ipv6 mangle PREROUTING

你应该不会看到任何命令输出。现在,让我们通过以下四个命令,为 IPv4 和 IPv6 添加我们的两条新规则:

sudo firewall-cmd --direct --add-rule ipv4 mangle PREROUTING 0 -m conntrack --ctstate INVALID -j DROP
sudo firewall-cmd --direct --add-rule ipv4 mangle PREROUTING 1 -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
sudo firewall-cmd --direct --add-rule ipv6 mangle PREROUTING 0 -m conntrack --ctstate INVALID -j DROP
sudo firewall-cmd --direct --add-rule ipv6 mangle PREROUTING 1 -p tcp ! --syn -m conntrack --ctstate NEW -j DROP

direct命令的语法与正常的 iptables 命令非常相似。所以,我不会重复在 iptables 部分中已经介绍过的解释。不过,我确实想指出在每个命令中的PREROUTING后面跟着的01。它们代表规则的优先级。数字越低,优先级越高,规则在链中的位置越靠前。因此,优先级为0的规则是各自链中的第一条规则,而优先级为1的规则是各自链中的第二条规则。如果你给每个规则分配相同的优先级,不能保证每次重启时规则的顺序保持不变。所以,确保为每个规则分配不同的优先级。

现在,让我们验证一下我们的规则是否生效:

[donnie@localhost ~]$ sudo firewall-cmd --direct --get-rules ipv4 mangle PREROUTING
0 -m conntrack --ctstate INVALID -j DROP
1 -p tcp '!' --syn -m conntrack --ctstate NEW -j DROP
[donnie@localhost ~]$ sudo firewall-cmd --direct --get-rules ipv6 mangle PREROUTING
0 -m conntrack --ctstate INVALID -j DROP
1 -p tcp '!' --syn -m conntrack --ctstate NEW -j DROP
[donnie@localhost ~]$

我们可以看到它们是有效的。当你使用iptables -t mangle -L命令和ip6tables -t mangle -L命令时,你会看到这些规则出现在PREROUTING_direct链中。(由于两条命令的输出相同,我只显示一次输出。)

. . .
. . .
Chain PREROUTING_direct (1 references)
target prot opt source destination 
DROP all -- anywhere anywhere ctstate INVALID
DROP tcp -- anywhere anywhere tcp flags:!FIN,SYN,RST,ACK/SYN ctstate NEW
. . .
. . .

为了证明它有效,我们可以对虚拟机执行一些 Nmap 扫描,就像我在第四章使用防火墙保护服务器 - 第一部分阻止无效数据包与 iptables部分中向你展示的那样。(如果你不记得怎么做,不用担心,接下来的动手实验中你会看到操作步骤。)然后,我们可以使用sudo iptables -t mangle -L -vsudo ip6tables -t mangle -L -v来查看这两条规则阻止的包和字节。

我们在这些命令中没有使用--permanent选项,所以它们还不是永久性的。现在让我们将它们设置为永久:

[donnie@localhost ~]$ sudo firewall-cmd --runtime-to-permanent
[sudo] password for donnie: 
success
[donnie@localhost ~]$

现在,让我们看看/etc/firewalld/目录。在这里,你会看到一个之前没有的direct.xml文件:

[donnie@localhost ~]$ sudo ls -l /etc/firewalld
total 20
-rw-r--r--. 1 root root  532 Aug 26 13:17 direct.xml
. . .
. . .
[donnie@localhost ~]$

打开文件,你会看到新的规则:

<?xml version="1.0" encoding="utf-8"?>
<direct>
  <rule priority="0" table="mangle" ipv="ipv4" chain="PREROUTING">-m conntrack --ctstate INVALID -j DROP</rule>
  <rule priority="1" table="mangle" ipv="ipv4" chain="PREROUTING">-p tcp '!' --syn -m conntrack --ctstate NEW -j DROP</rule>
  <rule priority="0" table="mangle" ipv="ipv6" chain="PREROUTING">-m conntrack --ctstate INVALID -j DROP</rule>
  <rule priority="1" table="mangle" ipv="ipv6" chain="PREROUTING">-p tcp '!' --syn -m conntrack --ctstate NEW -j DROP</rule>
</direct>

官方的 Red Hat 7 文档页面确实覆盖了直接规则,但只做了简要介绍。如需详细信息,请参阅firewalld.direct手册页面。

查看 RHEL/AlmaLinux 8 和 9 中的 nftables 规则以及 firewalld

RHEL 8/9 及其衍生版本使用 nftables 作为默认的 firewalld 后端。每次你使用 firewall-cmd 命令创建规则时,适当的 nftables 规则会被创建并插入到正确的位置。为了查看当前生效的规则集,我们将使用与在 Ubuntu 上使用 nftables 时相同的 nft 命令:

[donnie@localhost ~]$ sudo nft list ruleset
. . .
. . .
table ip firewalld {
    chain nat_PREROUTING {
        type nat hook prerouting priority -90; policy accept;
        jump nat_PREROUTING_ZONES_SOURCE
        jump nat_PREROUTING_ZONES
    }
    chain nat_PREROUTING_ZONES_SOURCE {
    }
. . .
. . .
[donnie@localhost ~]$

我们再次看到了一长串默认的、预配置的防火墙规则。(要查看完整的列表,请自行运行命令。)你会在 RHEL 8 类型机器的 /usr/lib/python3.6/site-packages/firewall/core/nftables.py 脚本中找到这些默认规则,在 RHEL 9 类型机器中则是在 /usr/lib/python3.9/site-packages/firewall/core/nftables.py 脚本中。每次启动机器时,这个脚本都会运行。

在 RHEL/AlmaLinux firewalld 中创建直接规则

好吧,事情开始变得相当奇怪了。即使直接规则命令创建了 iptables 规则,而 RHEL 8/9 发行版使用 nftables 作为 firewalld 后端,你仍然可以创建直接规则。只需像在 RHEL/CentOS 7 firewalld 中创建直接规则 部分那样创建并验证它们。显然,firewalld 允许这些 iptables 规则与 nftables 规则和平共存。然而,如果你需要在生产系统中这样做,务必在投入生产之前彻底测试你的设置。

在 Red Hat 8/9 文档中没有关于此的内容,但如果你想了解更多,可以查看 firewalld.direct 的手册页。

firewalld 命令操作实验

完成此实验后,你将练习一些基本的 firewalld 命令:

  1. 登录到你的 CentOS 7 虚拟机或任一 AlmaLinux 虚拟机,并运行以下命令。观察每次执行后的输出:
 sudo firewall-cmd --get-zones
 sudo firewall-cmd --get-default-zone
 sudo firewall-cmd --get-active-zones
  1. 简要查看处理 firewalld.zones 的手册页:
 man firewalld.zones
 man firewalld.zone

(是的,确实有两个。一个解释了区域配置文件,另一个解释了区域本身。)

  1. 查看所有可用区域的配置详情:
sudo firewall-cmd --list-all-zones
  1. 查看预定义服务的列表。然后,查看 dropbox-lansync 服务的相关信息:
 sudo firewall-cmd --get-services
 sudo firewall-cmd --info-service=dropbox-lansync
  1. 将默认区域设置为 dmz。查看关于 zone 的信息,添加 httphttps 服务,然后再次查看 zone 信息:
 sudo firewall-cmd --set-default-zone=dmz
 sudo firewall-cmd --permanent --add-service={http,https}
 sudo firewall-cmd --info-zone=dmz
 sudo firewall-cmd --permanent --info-zone=dmz
  1. 重新加载 防火墙 配置并再次查看 zone 信息。同时,查看被允许的服务列表:
 sudo firewall-cmd --reload
 sudo firewall-cmd --info-zone=dmz
 sudo firewall-cmd --list-services
  1. 永久打开端口 10000/tcp 并查看结果:
 sudo firewall-cmd --permanent --add-port=10000/tcp
 sudo firewall-cmd --list-ports
 sudo firewall-cmd --reload
 sudo firewall-cmd --list-ports
 sudo firewall-cmd --info-zone=dmz
  1. 移除你刚刚添加的端口:
 sudo firewall-cmd --permanent --remove-port=10000/tcp
 sudo firewall-cmd --reload
 sudo firewall-cmd --list-ports
 sudo firewall-cmd --info-zone=dmz
  1. 添加一条丰富语言规则来阻止一个地理范围的 IPv4 地址:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="200.192.0.0/24" service name="http" drop'
  1. 阻止 host-redirectnetwork-redirect ICMP 类型:
sudo firewall-cmd --permanent --add-icmp-block={host-redirect,network-redirect}
  1. 添加指令以记录所有被丢弃的报文:
sudo firewall-cmd --set-log-denied=all
  1. 查看 runtimepermanent 配置,并注意它们之间的差异:
sudo firewall-cmd --info-zone=dmz
sudo firewall-cmd --info-zone=dmz --permanent
  1. runtime 配置变为 permanent 并验证其生效:
sudo firewall-cmd --runtime-to-permanent
sudo firewall-cmd --info-zone=dmz --permanent
  1. 在 CentOS 7 上,通过以下方式查看有效的防火墙规则完整列表:
sudo iptables -L
  1. 在 AlmaLinux 8 或 9 上,通过以下命令查看所有有效的防火墙规则:
sudo nft list ruleset
  1. 创建 direct 规则,以阻止来自 mangle 表的 PREROUTING 链的无效数据包:
sudo firewall-cmd --direct --add-rule ipv4 mangle PREROUTING 0 -m conntrack --ctstate INVALID -j DROP
sudo firewall-cmd --direct --add-rule ipv4 mangle PREROUTING 1 -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
sudo firewall-cmd --direct --add-rule ipv6 mangle PREROUTING 0 -m conntrack --ctstate INVALID -j DROP
sudo firewall-cmd --direct --add-rule ipv6 mangle PREROUTING 1 -p tcp ! --syn -m conntrack --ctstate NEW -j DROP
  1. 验证 规则 是否生效,并将其设置为 永久
sudo firewall-cmd --direct --get-rules ipv4 mangle PREROUTING
sudo firewall-cmd --direct --get-rules ipv6 mangle PREROUTING
sudo firewall-cmd --runtime-to-permanent
  1. 查看你刚刚创建的 direct.xml 文件的内容:
sudo less /etc/firewalld/direct.xml
  1. 对虚拟机执行 XMAS Nmap 扫描,支持 IPv4 和 IPv6。然后,观察哪个规则被扫描触发:
sudo nmap -sX ipv4_address_of_Test-VM
sudo nmap -6 -sX ipv6_address_of_Test-VM
sudo iptables -t mangle -L -v
sudo ip6tables -t mangle -L -v
  1. 重复 第 19 步,但这次使用 Windows 扫描:
sudo nmap -sW ipv4_address_of_Test-VM
sudo nmap -6 -sW ipv6_address_of_Test-VM
sudo iptables -t mangle -L -v
sudo ip6tables -t mangle -L -v
  1. 查看 firewalld 的主要页面列表:
apropos firewall

实验到此结束,恭喜你!

总结

在本章中,我们介绍了两种辅助工具,可以简化使用 iptables 或 nftables。我们首先介绍了 ufw,它适用于 Debian 和 Ubuntu 系列的系统。接着,我们介绍了 firewalld,虽然最初只在 Red Hat 系列发行版中使用,但现在也可以在 Ubuntu 仓库中找到,并且已经预安装并在 SUSE 系统上启用。

在我所分配的空间中,我展示了如何使用这些技术设置单主机保护的基本知识。我还展示了一些 firewalld 的内部细节,这些信息你在任何地方,包括官方的 Red Hat 文档中,都找不到。

在下一章,我们将讨论各种加密技术,它们能帮助你保护数据隐私。到时见。

问题

  1. RHEL 7 系列和 RHEL 8/9 系列的 firewalld 有什么主要区别?

  2. firewalld 以哪种格式存储规则?

    1. .txt

    2. .config

    3. .html

    4. .xml

  3. 以下哪个命令可以列出系统上所有的 firewalld 区域?

    1. sudo firewalld --get-zones

    2. sudo firewall-cmd --list-zones

    3. sudo firewall-cmd --get-zones

    4. sudo firewalld --list-zones

  4. 使用 ufw,你所需的所有操作都可以通过 ufw 工具完成。

  5. 你的系统已经安装了 firewalld,并且你需要打开端口 10000/tcp。你会使用哪个命令?

    1. sudo firewall-cmd --add-port=10000/tcp

    2. sudo firewall-cmd --add-port=10000

    3. sudo firewalld --add-port=10000

    4. sudo firewalld --add-port=10000/tcp

  6. 以下哪个 ufw 命令可以用来打开默认的安全外壳端口?

    1. sudo ufw allow 22

    2. sudo ufw permit 22

    3. sudo ufw allow 22/tcp

    4. sudo ufw permit 22/tcp

进一步阅读

答案

  1. RHEL 7 发行版使用 iptables 作为 firewalld 的后端,而 RHEL 8/9 发行版使用 nftables 作为 firewalld 的后端。

  2. D

  3. C

  4. B

  5. A

  6. C

第七章:6 种加密技术

加入我们的书籍社区,加入 Discord

packt.link/SecNet

无论你是为某个超级机密的政府机构工作,还是普通的市民,你都有敏感数据需要保护免受窥探。商业机密、政府机密、个人机密——都需要保护。正如我们在第三章《保护普通用户账户》中所见,锁定用户的主目录并设置限制性权限只是解决方案的一部分;我们还需要加密。这种加密将为我们提供三大保护:

  • 机密性:确保只有授权的人才能查看数据。

  • 完整性:确保原始数据未被未经授权的人篡改。

  • 可用性:确保敏感数据始终可用,且无法被未经授权的人删除。

本章将讨论的两种数据加密类型旨在保护静态数据和传输中的数据。我们将首先使用文件、分区和目录加密来保护静态数据,最后探讨如何使用 OpenSSL 保护传输中的数据。

在本章中,我们将涵盖以下主题:

GNU Privacy Guard (GPG)
Encrypting partitions with Linux Unified Key Setup (LUKS)
  • 使用 eCryptfs 加密目录

  • 使用 VeraCrypt 进行跨平台加密容器共享

  • OpenSSL 和公钥基础设施

  • 商业证书授权机构

  • 创建密钥、证书请求和证书

  • 创建本地证书授权机构

  • 将证书授权机构添加到操作系统

  • OpenSSL 和 Apache Web 服务器

  • 设置互认证

如果你准备好进入加密世界,我们就开始吧。

GNU 隐私保护工具(GPG)

我们将从GNU 隐私保护工具GPG)开始。这是 Phil Zimmermann 在 1991 年创建的 Pretty Good Privacy 的免费开源实现。你可以使用它对文件或消息进行加密或数字签名。在本节中,我们将严格关注 GPG。

使用 GPG 有一些优势:

  • 它使用强大且难以破解的加密算法。

  • 它使用私钥/公钥方案,从而消除了需要以安全方式将密码传输给消息或文件接收人的需求。相反,你只需要发送公钥,除了预定的接收人外,其他人都无法使用它。

  • 你可以使用 GPG 来加密你自己的文件供自己使用,就像使用任何其他加密工具一样。

  • 它可以用于加密电子邮件消息,允许你对敏感邮件进行真正的端到端加密。

  • 有一些图形用户界面(GUI)前端可用,旨在让使用更加简便。

但正如你所知道的,也有一些缺点:

  • 当你只与信任的人直接合作时,使用公钥而不是密码是非常好的。但对于更广泛的情况,例如将公钥分发给大众,以便每个人都能验证你的签名消息,你将依赖于一个非常难以建立的信任网络模型。

  • 对于电子邮件的端到端加密,收件人也必须在他们的系统上配置 GPG,并且知道如何使用它。这在企业环境中可能可行,但要让你的朋友设置这个,祝你好运。(我从未成功让其他人设置过邮件加密。)

  • 如果你使用独立的电子邮件客户端,比如 Mozilla Thunderbird,你可以安装一个插件来自动加密和解密邮件。但每当 Thunderbird 发布新版本更新时,插件就会失效,而且总需要一段时间才能发布一个新的工作版本。

  • 即使你能让其他人也设置他们的邮件客户端使用 GPG,这仍然不是一个完美的隐私解决方案。因为电子邮件的元数据——发件人和收件人的电子邮件地址——是无法加密的。因此,黑客、广告商或政府机构仍然能够看到你和谁交换邮件,并利用这些信息建立一个关于你活动、信仰和个性的个人档案。如果你真的需要完全的隐私,最好的选择是使用一个私密的消息解决方案,比如Session消息应用。(不过,这超出了本书的范围。)

即使存在许多弱点,GPG 仍然是分享加密文件和邮件的最佳方式之一。大多数 Linux 发行版都会预装 GPG。因此,你可以在任何 更新 的虚拟机上进行这些演示。(我说 更新,是因为在旧版发行版上,如 CentOS 7,程序会略有不同。)

实践实验室 – 创建你的 GPG 密钥

  1. 在文本模式下的 AlmaLinux 机器上,首先需要做的就是安装 pinentry 包。可以通过以下命令来安装:
sudo dnf install pinentry

(请注意,在图形界面的 AlmaLinux 机器或 Ubuntu 服务器上,你无需执行此操作。)

  1. 接下来,创建你的 GPG 密钥对:
gpg --full-generate-key

请注意,由于你是在为自己配置,因此不需要 sudo 权限。

该命令首先会在你的主目录中创建一个已填充的 .gnupg 目录:

gpg: /home/donnie/.gnupg/trustdb.gpg: trustdb created
gpg: key 56B59F39019107DF marked as ultimately trusted
gpg: directory '/home/donnie/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/home/donnie/.gnupg/openpgp-revocs.d/BD057E0E01E664424E8B812E56B59F39019107DF.rev'
public and secret key created and signed.

然后,你会被要求选择你想要的密钥类型。我们将使用默认的 RSA 和 RSA。与较老的 DSA 密钥相比,RSA 密钥更强大且更难破解。Elgamal 密钥也不错,但旧版本的 GPG 可能不支持它:

Please select what kind of key you want:
 (1) RSA and RSA (default)
 (2) DSA and Elgamal
 (3) DSA (sign only)
 (4) RSA (sign only)
(14) Existing key from card
Your selection?

对于适当的加密,你需要选择至少 3,072 位的密钥,因为任何更小的密钥现在都被认为是脆弱的。(这是根据美国国家标准与技术研究院(NIST)的最新指导)。这已经是我们最新 Linux 发行版的默认设置,所以你在那里已经没问题了。对于较旧的发行版,如 CentOS 7,默认设置只有 2048 位,所以你需要更改它。

接下来,选择你希望密钥在自动过期之前保持有效的时间。对于我们的目的,我们将选择默认的密钥永不过期

Please specify how long the key should be valid. 
 0 = key does not expire 
 <n> = key expires in n days 
 <n>w = key expires in n weeks 
 <n>m = key expires in n months 
 <n>y = key expires in n years 
Key is valid for? (0) 

提供你的个人信息:

GnuPG needs to construct a user ID to identify your key. 
Real name: Donald A. Tevault 
Email address: donniet@something.net 
Comment: No comment 
You selected this USER-ID: 
 "Donald A. Tevault (No comment) <donniet@something.net>" 
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?
Create a passphrase for your private key:
You need a Passphrase to protect your secret key. 
We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy. 

在较旧的 Linux 发行版上,这可能需要一段时间,即使你做了所有推荐的生成熵的操作。对于较新的 Linux 发行版,随机数生成器的工作效率更高,所以你可以忽略关于密钥生成可能需要很长时间的提示。下面是当过程完成后你会看到的内容:

gpg: /home/donnie/.gnupg/trustdb.gpg: trustdb created 
gpg: key 19CAEC5B marked as ultimately trusted 
public and secret key created and signed. 
gpg: checking the trustdb 
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model 
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u 
pub 2048R/19CAEC5B 2017-10-26 
 Key fingerprint = 8DE5 8894 2E37 08C4 5B26 9164 C77C 6944 19CA EC5B 
uid Donald A. Tevault (No comment) <donniet@something.net> 
sub 2048R/37582F29 2017-10-26
  1. 验证密钥是否已成功创建:
[donnie@localhost ~]$ gpg --list-keys
 /home/donnie/.gnupg/pubring.gpg
 -------------------------------
 pub 2048R/19CAEC5B 2017-10-26
 uid Donald A. Tevault (No comment) <donniet@something.net>
 sub 2048R/37582F29 2017-10-26
 [donnie@localhost ~]$
  1. 顺便查看一下你创建的文件:
[donnie@localhost ~]$ ls -l .gnupg/
total 12
drwx------. 2 donnie donnie   58 Oct 26 14:53 openpgp-revocs.d
drwx------. 2 donnie donnie  110 Oct 26 14:53 private-keys-v1.d
-rw-r--r--. 1 donnie donnie 1970 Oct 26 14:53 pubring.kbx
-rw-------. 1 donnie donnie   32 Oct 26 14:43 pubring.kbx~
-rw-------. 1 donnie donnie 1280 Oct 26 15:51 trustdb.gpg
[donnie@localhost ~]$

这些文件是你的公钥和私钥环、撤销数据库和受信任用户数据库。

实践实验 – 对称加密自己的文件

即使你不打算与其他人共享文件,你可能仍然会发现 GPG 对于加密自己的文件很有用。为此,你将使用对称加密,即使用你自己的私钥进行加密。在尝试这个之前,你需要按照前一部分的说明生成密钥:

对称密钥加密,顾名思义,就是对称的。它的对称性体现在你用来加密文件的密钥与用来解密文件的密钥是相同的。这对于仅加密自己使用的文件来说很方便。但如果你需要与其他人共享加密文件,你就需要找出一种安全的方式将密码交给那个人。我的意思是,你肯定不想通过明文电子邮件发送密码。

  1. 除了你的个人用户账户外,你还需要为 Maggie 创建一个用户账户。在 AlmaLinux 中,可以按照以下方式创建她的账户:
sudo useradd maggie
sudo passwd maggie

对于 Ubuntu,可以按照以下方式创建 Maggie 的账户:

sudo adduser maggie
  1. 让我们加密一个超级机密的文件,我们绝对不能让它落入错误的人手中:
[donnie@localhost ~]$ gpg -c secret_squirrel_stuff.txt
[donnie@localhost ~]$

请注意,-c选项表示我选择使用带密码的对称加密来加密该文件。你输入的密码是用于该文件的,而不是你的私钥。

  1. 看看你新的文件集。这个方法有一个小缺陷,那就是 GPG 会创建文件的加密副本,但它也会保留原始的未加密文件:
[donnie@localhost ~]$ ls -l
 total 1748
 -rw-rw-r--. 1 donnie donnie 37 Oct 26 14:22 secret_squirrel_stuff.txt
 -rw-rw-r--. 1 donnie donnie 94 Oct 26 14:22
 secret_squirrel_stuff.txt.gpg
[donnie@localhost ~]$
  1. 让我们使用shred命令删除那个未加密的文件。我们将使用-u选项删除文件,-z选项则用于用零覆盖已删除的文件:
[donnie@localhost ~]$ shred -u -z secret_squirrel_stuff.txt
[donnie@localhost ~]$

看起来好像没有任何变化,因为shred不会给出任何输出。但ls -l命令会证明文件已经不存在了。

  1. 现在,如果我用less secret_squirrel_stuff.txt.gpg查看加密文件,我会在输入我的私钥密码后看到文件内容。试试看:
less secret_squirrel_stuff.txt.gpg
Shhh!!!! This file is super-secret.
secret_squirrel_stuff.txt.gpg (END)
  1. 只要我的私钥仍然保存在我的密钥环中,我就可以再次查看加密文件,而不需要重新输入密码。现在,为了向你证明文件确实被加密了,我会创建一个共享目录,并将文件移动到那里供其他人访问。再试试看吧:
sudo mkdir /shared
sudo chown donnie: /shared
sudo chmod 755 /shared
mv secret_squirrel_stuff.txt.gpg /shared

当我进入那个目录并使用less查看文件时,我仍然可以看到文件内容,而不需要重新输入我的密码。

  1. 但是现在,让我们看看 Maggie 尝试查看文件时会发生什么。使用su - maggie切换到她的账户,并让她尝试:
su - maggie
cd /shared
[maggie@localhost shared]$ less secret_squirrel_stuff.txt.gpg
"secret_squirrel_stuff.txt.gpg" may be a binary file. See it anyway?

当她按下Y键想查看时,她看到的是:

<8C>^M^D^C^C^B<BD>2=<D3>͈u<93><CE><C9>MОOy<B6>^O<A2><AD>}Rg9<94><EB><C4>^W^E
 <A6><8D><B9><B8><D3>(<98><C4>æF^_8Q2b<B8>C<B5><DB>^]<F1><CD>#<90>H<EB><90><
 C5>^S%X [<E9><EF><C7>
 ^@y+<FC><F2><BA><U+058C>H'+<D4>v<84>Y<98>G<D7>֊
secret_squirrel_stuff.txt.gpg (END)

可怜的 Maggie 真的很想看到我的文件,但她只能看到加密的乱码。

我刚才演示的是 GPG 的另一个优点。在输入私钥密码一次后,你可以查看任何加密文件,而无需手动解密,也不需要重新输入密码。使用其他对称加密文件工具,如bcrypt,则必须手动解密文件才能查看。

  1. 但是现在假设你不再需要加密这个文件,想要解密它以便让其他人查看。通过输入exit退出 Maggie 的账户。然后,只需使用gpg并加上-d选项:
[maggie@localhost shared]$ exit
[donnie@localhost shared]$ gpg -o secret_squirrel_stuff.txt -d secret_squirrel_stuff.txt.gpg
 gpg: AES256.CFB encrypted data
 gpg: encrypted with 1 passphrase
 Shhh!!!! This file is super-secret.
 [donnie@localhost shared]$

这与旧版 Linux 发行版的工作方式不同。在我们的新发行版中,我们现在必须使用-o选项,并指定要创建的解密文件的文件名。同时,注意-o选项必须放在-d选项之前,否则会收到错误信息。

动手实验 – 使用公钥加密文件

在这个实验中,你将学习如何使用 GPG 公钥加密来加密和共享文件:

  1. 首先,像在前一个实验中为 Maggie 创建用户账户一样,为 Frank 创建一个用户账户。

  2. 为自己和 Frank 创建密钥对,正如我之前展示的那样。接下来,将你自己的公钥提取到一个ASCII文本文件中:

cd .gnupg
gpg --export -a -o donnie_public-key.txt

以 Frank 身份登录,并为他重复此命令。

  1. 通常,参与者会通过电子邮件附件或将密钥放入共享目录的方式互相发送密钥。在这种情况下,你和 Frank 将会收到彼此的公钥文件,并将其放入各自的.gnupg目录中。完成后,导入彼此的密钥:
donnie@ubuntu:~/.gnupg$ gpg --import frank_public-key.txt
gpg: key 4CFC6990: public key "Frank Siamese (I am a cat.) <frank@any.net>" imported
gpg: Total number processed: 1
gpg: imported: 1 (RSA: 1)
donnie@ubuntu:~/.gnupg$
frank@ubuntu:~/.gnupg$ gpg --import donnie_public-key.txt
gpg: key 9FD7014B: public key "Donald A. Tevault <donniet@something.net>" imported
gpg: Total number processed: 1
gpg:               imported: 1  (RSA: 1)
frank@ubuntu:~/.gnupg$
  1. 现在是关键部分。为 Frank 创建一个超级机密的消息,进行非对称加密(-e),并签名(-s)。签名消息是验证该消息确实来自你,而不是冒充者:
donnie@ubuntu:~$ gpg -s -e secret_stuff_for_frank.txt
. . .
. . .
It is NOT certain that the key belongs to the person named
in the user ID.  If you *really* know what you are doing,
you may answer the next question with yes.
Use this key anyway? (y/N) y
Current recipients:
2048R/CD8104F7 2017-10-27 "Frank Siamese (I am a cat.) <frank@any.net>"
Enter the user ID.  End with an empty line:
donnie@ubuntu:~$

所以,你需要做的第一件事是输入你的私钥密码。在要求输入用户 ID 的地方,输入 frank,因为他是你的消息的预定接收者。但是看看下面那行,写着没有任何保证表明此密钥属于指定用户。那是因为你还没有信任 Frank 的公钥。稍后我们会解决这个问题。输出的最后一行再次要求输入用户 ID,以便我们可以指定多个接收者。但此时你只关心 Frank,所以直接按Enter键跳出常规操作。这将导致生成一个 .gpg 版本的消息,发送给 Frank:

donnie@ubuntu:~$ ls -l
total 8
. . .
-rw-rw-r-- 1 donnie donnie 143 Oct 27 18:37 secret_stuff_for_frank.txt
-rw-rw-r-- 1 donnie donnie 790 Oct 27 18:39 secret_stuff_for_frank.txt.gpg
donnie@ubuntu:~$
  1. 你最后的步骤是通过任何可用的方式将加密后的消息文件发送给 Frank。

  2. 当 Frank 收到他的消息后,他将使用-d选项来查看它:

frank@ubuntu:~$ gpg -d secret_stuff_for_frank.txt.gpg
. . .
. . .
gpg: gpg-agent is not available in this session
gpg: encrypted with 2048-bit RSA key, ID CD8104F7, created 2017-10-27
      "Frank Siamese (I am a cat.) <frank@any.net>"
This is TOP SECRET stuff that only Frank can see!!!!!
If anyone else see it, it's the end of the world as we know it.
(With apologies to REM.)
gpg: Signature made Fri 27 Oct 2017 06:39:15 PM EDT using RSA key ID 9FD7014B
gpg: Good signature from "Donald A. Tevault <donniet@something.net>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: DB0B 31B8 876D 9B2C 7F12  9FC3 886F 3357 9FD7 014B
frank@ubuntu:~$
  1. Frank 输入他的私钥密码后,就能看到消息。在底部,他看到有关你的公钥不受信任的警告,并且没有迹象表明签名属于所有者。假设你和 Frank 彼此认识,他知道公钥确实是你的。那么他将你的公钥添加到受信任列表中:
frank@ubuntu:~$ cd .gnupg
frank@ubuntu:~/.gnupg$ gpg --edit-key donnie
gpg (GnuPG) 1.4.20; Copyright (C) 2015 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
pub  2048R/9FD7014B  created: 2017-10-27  expires: never       usage: SC
                     trust: ultimate      validity: ultimate
sub  2048R/9625E7E9  created: 2017-10-27  expires: never       usage: E
[ultimate] (1). Donald A. Tevault <donniet@something.net>
gpg>
  1. 输出的最后一行是 gpg shell 的命令提示符。Frank 关心的是信任问题,因此他会输入 trust 命令:
gpg> trust
pub  2048R/9FD7014B  created: 2017-10-27  expires: never       usage: SC
                     trust: unknown       validity: unknown
sub  2048R/9625E7E9  created: 2017-10-27  expires: never       usage: E
[ unknown] (1). Donald A. Tevault <donniet@something.net>
Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)
  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu
Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y
  1. Frank 已经认识你很久了,他确实知道是你发送了密钥。因此,他选择了选项 5 来获得完全的信任。一旦 Frank 登出并重新登录,这个信任将生效:
frank@ubuntu:~$ gpg -d secret_stuff_for_frank.txt.gpg
You need a passphrase to unlock the secret key for
user: "Frank Siamese (I am a cat.) <frank@any.net>"
2048-bit RSA key, ID CD8104F7, created 2017-10-27 (main key ID 4CFC6990)
gpg: gpg-agent is not available in this session
gpg: encrypted with 2048-bit RSA key, ID CD8104F7, created 2017-10-27
      "Frank Siamese (I am a cat.) <frank@any.net>"
This is TOP SECRET stuff that only Frank can see!!!!!
If anyone else see it, it's the end of the world as we know it.
(With apologies to REM.)
gpg: Signature made Fri 27 Oct 2017 06:39:15 PM EDT using RSA key ID 9FD7014B
gpg: Good signature from "Donald A. Tevault <donniet@something.net>"
frank@ubuntu:~$
  1. 没有更多警告信息时,情况看起来好多了。在你这边,也对 Frank 的公钥执行相同的操作。

正如你在第 8 步的屏幕输出中看到的,你可以将边际、完全或最终信任级别分配给他人的公钥。由于篇幅所限,我不能提供完整的信任级别解释,但你可以在这里阅读一个生动的解释:PGP Web of Trust: Core Concepts Behind Trusted Communication — https://www.linux.com/tutorials/pgp-web-trust-core-concepts-behind-trusted-communication/.

这非常酷,因为即使全世界可能都拥有你的公钥,除了你的指定接收者,其他人无法利用它。

现在,让我们看看如何在加密的情况下签署一个文件。

实践实验 – 在不加密的情况下签署文件

如果一个文件不是秘密的,但你仍然需要确保其真实性和完整性,你可以仅仅签名而不加密它:

  1. 为 Frank 创建一个未加密的消息并签名:
donnie@ubuntu:~$ gpg -s not_secret_for_frank.txt
You need a passphrase to unlock the secret key for
user: "Donald A. Tevault <donniet@something.net>"
2048-bit RSA key, ID 9FD7014B, created 2017-10-27
gpg: gpg-agent is not available in this session
donnie@ubuntu:~$ ls -l
. . .
-rw-rw-r-- 1 donnie donnie  40 Oct 27 19:30 not_secret_for_frank.txt
-rw-rw-r-- 1 donnie donnie 381 Oct 27 19:31 not_secret_for_frank.txt.gpg

和以前一样,这会创建一个 .gpg 版本的文件。

  1. 将消息发送给 Frank。

  2. 登录为 Frank。让他尝试使用less打开它:

frank@ubuntu:~$ less not_secret_for_frank.txt.gpg

在旧版 Linux 发行版中,由于签名的原因,你会看到很多乱码,但你也会看到明文消息。在新版 Linux 发行版中,你只会看到明文消息,而不会有乱码。

  1. 让 Frank 使用gpg并加上--verify选项来验证签名是否真的是属于你的:
frank@ubuntu:~$ gpg --verify not_secret_for_frank.txt.gpg
gpg: Signature made Fri 27 Oct 2017 07:31:12 PM EDT using RSA key ID 9FD7014B
gpg: Good signature from "Donald A. Tevault <donniet@something.net>"
frank@ubuntu:~$

关于加密单个文件的讨论就到这里。接下来,我们来看一下如何加密块设备和目录。

使用 Linux 统一密钥设置(LUKS)加密分区

能够加密单个文件非常方便,特别是当你想与其他用户共享敏感文件时。但也有其他类型的加密可供选择:

  • 块加密:我们可以将其用于全盘加密,或者加密单个分区。

  • 文件级加密:我们可以使用这种方式来加密单独的目录,而无需加密底层的分区。

  • 容器化加密:使用第三方软件(这些软件不随任何 Linux 发行版一起提供),我们可以创建加密的跨平台容器,这些容器可以在 Linux、macOS 或 Windows 机器上打开。

Linux 统一密钥设置LUKS)属于第一类。几乎所有 Linux 发行版都内建了它,而且每个发行版的使用说明都是相同的。LUKS 现在是几乎所有最新 Linux 发行版的默认加密机制。

你可能会想,进行这些磁盘加密操作会不会对性能产生影响。事实上,随着今天快速的 CPU,这几乎不会有影响。我在一台低配置的 Core i5 笔记本上运行带全盘加密的 Fedora,除了在首次启动时需要输入磁盘加密密码外,我几乎没有注意到加密在进行。

好的,我们来看一下在安装操作系统时如何加密磁盘。

操作系统安装过程中的磁盘加密

当你安装大多数基于 Linux 的操作系统时,你可以选择在安装过程中加密磁盘。只需在磁盘设置界面点击加密选项:

19501_06_01.png

19501_06_01.png

除此之外,我让安装程序创建默认的分区方案。在这台 AlmaLinux 9 机器上,这意味着/文件系统和swap分区都会是加密的逻辑卷。(我稍后会详细说明。)

在安装继续之前,我需要创建一个密码短语来挂载加密磁盘:

19501_06_02.png

19501_06_02.png

现在,每次重启系统时,我都需要输入这个密码短语:

19501_06_03.png

19501_06_03.png

安装程序不会真正加密普通的磁盘分区,而是会设置加密的逻辑卷。一旦机器启动并运行,我可以查看逻辑卷的列表。在这里,我可以看到/逻辑卷和swap逻辑卷:

[donnie@localhost ~]$ sudo lvdisplay
  --- Logical volume ---
  LV Path                /dev/almalinux/swap
  LV Name                swap
  VG Name                almalinux
. . .
. . .
   --- Logical volume ---
  LV Path                /dev/almalinux/root
  LV Name                root
  VG Name                almalinux
. . .
. . .
[donnie@localhost ~]$

现在,让我们看看 物理卷 的列表。实际上,列表中只有一个物理卷,它被列为 luks 物理卷:

[donnie@localhost ~]$ sudo pvdisplay
  --- Physical volume ---
  PV Name               /dev/mapper/luks-b0acc532-5347-417e-a86e-a3ee8431fba7
  VG Name               almalinux
  PV Size               <19.30 GiB / not usable 2.00 MiB
  Allocatable           yes (but full)
  PE Size               4.00 MiB
  Total PE              4940
  Free PE               0
  Allocated PE          4940
  PV UUID               mRI75u-aVJI-uRjC-GY1O-ih7N-T3co-vssRRX

[donnie@localhost ~]$

/etc/ 目录下,你会找到 crypttab 文件,其中包含这个物理卷的条目。

[donnie@localhost ~]$ sudo cat /etc/crypttab 
luks-b0acc532-5347-417e-a86e-a3ee8431fba7 UUID=b0acc532-5347-417e-a86e-a3ee8431fba7 none discard
[donnie@localhost ~]$

这表明底层的物理卷已被加密,这意味着 /swap 逻辑卷也都被加密了。这是好事,因为如果交换空间没有加密—这是手动设置磁盘加密时常见的错误—可能会导致数据泄漏。(稍后我们会详细讨论这个 crypttab 文件。)

实操实验——使用 LUKS 添加加密分区

有时你可能需要将另一个加密驱动器添加到现有机器,或者加密一个便携设备,如 USB 闪存。这一过程适用于这两种场景。而且,这个过程对我们使用的所有 Linux 发行版都相同,所以你使用哪台虚拟机都没关系。请按照以下步骤添加加密分区:

将大小调整到 20 GB:

19501_06_05.png

19501_06_05.png

  1. 重启机器后,你现在会有一个 /dev/sdb 驱动器可以使用。我们可以在这里看到:
 donnie@ubuntu2204-packt:~$ ls -l /dev/sd*
brw-rw---- 1 root disk 8,  0 Oct 27 19:33 /dev/sda
brw-rw---- 1 root disk 8,  1 Oct 27 19:33 /dev/sda1
brw-rw---- 1 root disk 8,  2 Oct 27 19:33 /dev/sda2
brw-rw---- 1 root disk 8,  3 Oct 27 19:33 /dev/sda3
brw-rw---- 1 root disk 8, 16 Oct 27 19:33 /dev/sdb
donnie@ubuntu2204-packt:~$
  1. gdisk 中打开该驱动器。使用整个驱动器来创建分区,并将分区类型保持为默认类型 8300
sudo gdisk /dev/sdb
  1. 查看你新 /dev/sdb1 分区的详细信息:
donnie@ubuntu2204-packt:~$ sudo gdisk -l /dev/sdb
GPT fdisk (gdisk) version 1.0.8
Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present
. . .
. . .
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048        41943006   20.0 GiB    8300  Linux filesystem
donnie@ubuntu2204-packt:~$
  1. 接下来,使用 cryptsetup 将分区转换为 LUKS 格式。在这个命令中,-v 表示详细模式,-y 表示你需要输入密码两次来确保密码正确。请注意,当它提示输入 yes 时,确实需要全大写输入:
donnie@ubuntu2204-packt:~$ sudo cryptsetup -v -y luksFormat /dev/sdb1
WARNING!
========
This will overwrite data on /dev/sdb1 irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sdb1: 
Verify passphrase: 
Key slot 0 created.
Command successful.
donnie@ubuntu2204-packt:~
  1. 查看关于你新加密分区的信息:
donnie@ubuntu2204-packt:~$ sudo cryptsetup luksDump /dev/sdb1
LUKS header information
Version:        2
Epoch:          3
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           e38e087a-205c-4aeb-81d5-03f03b8e8020
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)
. . .
. . .

输出的信息比我这里展示的要多很多,但你大概明白了意思。

  1. 将分区映射到一个设备名。你可以给设备命名任何你想要的名称。现在就把它命名为 secrets。我知道,这个名字有点俗气。实际上,你不希望让人那么容易知道你在哪里存储你的秘密:
donnie@ubuntu2204-packt:~$ sudo cryptsetup luksOpen /dev/sdb1 secrets
Enter passphrase for /dev/sdb1: 
donnie@ubuntu2204-packt:~$
  1. 查看 /dev/mapper/ 目录。你会看到你的新 secrets 设备列出为指向某种 dm 设备的符号链接。(在这个例子中,它是 dm-1。):
donnie@ubuntu2204-packt:~$ cd /dev/mapper
donnie@ubuntu2204-packt:/dev/mapper$ ls -l secrets 
lrwxrwxrwx 1 root root 7 Oct 27 19:50 secrets -> ../dm-1
donnie@ubuntu2204-packt:/dev/mapper$
  1. 使用 dmsetup 查看关于你新设备的信息:
donnie@ubuntu2204-packt:~$ sudo dmsetup info secrets
Name:              secrets
State:             ACTIVE
Read Ahead:        256
Tables present:    LIVE
Open count:        0
Event number:      0
Major, minor:      253, 1
Number of targets: 1
UUID: CRYPT-LUKS2-e38e087a205c4aeb81d503f03b8e8020-secrets
donnie@ubuntu2204-packt:~$
  1. 按照通常的方式格式化分区。你可以使用任何被你的 Linux 发行版支持的文件系统。在生产服务器上,这通常意味着使用 XFS 或 EXT4。为了好玩,我们就用 XFS:
donnie@ubuntu2204-packt:~$ sudo mkfs.xfs /dev/mapper/secrets
meta-data=/dev/mapper/secrets    isize=512    agcount=4, agsize=1309631 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=1        finobt=1, sparse=1, rmapbt=0
         =                       reflink=1    bigtime=0 inobtcount=0
data     =                       bsize=4096   blocks=5238523, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
log      =internal log           bsize=4096   blocks=2560, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
donnie@ubuntu2204-packt:~$
  1. 创建一个挂载点并挂载加密分区:
donnie@ubuntu2204-packt:~$ sudo mkdir /secrets
donnie@ubuntu2204-packt:~$ sudo mount /dev/mapper/secrets /secrets/
donnie@ubuntu2204-packt:~$
  1. 使用 mount 命令验证分区是否正确挂载:
donnie@ubuntu2204-packt:~$ mount | grep 'secrets'
/dev/mapper/secrets on /secrets type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
donnie@ubuntu2204-packt:~$

配置 LUKS 分区以自动挂载

唯一缺失的部分是配置系统在启动时自动挂载 LUKS 分区。为此,配置两个不同的文件:

  • /etc/crypttab

  • /etc/fstab

如果你在安装操作系统时加密了 sda 驱动器,你应该已经有一个包含该驱动器信息的 crypttab 文件。它看起来应该像这样:

luks-b0acc532-5347-417e-a86e-a3ee8431fba7 UUID=b0acc532-5347-417e-a86e-a3ee8431fba7 none discard

前两个字段描述了加密分区的名称和位置。第三个字段用于加密密码短语。如果它设置为 none,如本例所示,那么在启动时将需要手动输入密码。

fstab 文件中,我们有一个实际挂载该分区的条目:

/dev/mapper/almalinux-root /           xfs     defaults,x-systemd.device-timeout=0 0 0
UUID=28218289-34cb-4c57-9755-379c65d580af /boot       xfs     defaults        0 0
/dev/mapper/almalinux-swap none      swap    defaults,x-systemd.device-timeout=0 0 0

实际上,在这种情况下有两个条目,因为我们有两个逻辑卷,/swap,它们位于加密的物理卷上。UUID 行是 /boot/ 分区,这是驱动器中唯一未加密的部分。现在,让我们添加新的加密分区,以便它也能自动挂载。

动手实验 - 配置 LUKS 分区以自动挂载

在本实验中,你将设置在上一个实验中创建的加密分区,使其在重启计算机时自动挂载:

提示:

在这里,通过从你的桌面主机远程登录到虚拟机将非常有帮助。通过使用图形界面的终端,无论是 Linux 或 macOS 机器的 Terminal 还是 Windows 机器上的 Cygwin,你将能够执行复制和粘贴操作,而直接在虚拟机终端中操作时是无法做到的。(相信我,你不想手动输入那些长长的 UUID。)

  1. 第一步是获取加密分区的 UUID:
donnie@ubuntu2204-packt:~$ sudo cryptsetup luksUUID /dev/sdb1
e38e087a-205c-4aeb-81d5-03f03b8e8020
donnie@ubuntu2204-packt:~$
  1. 复制该 UUID 并粘贴到 /etc/crypttab 文件中。(如果没有 crypttab 文件,只需创建一个新文件。)另外,请注意,你需要粘贴两次 UUID。第一次在前面加上 luks-,第二次在后面加上 UUID=
luks-e38e087a-205c-4aeb-81d5-03f03b8e8020 UUID=e38e087a-205c-4aeb-81d5-03f03b8e8020 none
  1. 编辑 /etc/fstab 文件,在文件的最后一行添加你的新加密分区。请注意,你需要再次使用 luks-,后面跟上 UUID 编号:
/dev/mapper/luks-e38e087a-205c-4aeb-81d5-03f03b8e8020 /secrets xfs defaults 0 0

在编辑 fstab 文件以添加普通的未加密分区时,我总是喜欢使用 sudo mount -a 来检查 fstab 文件是否有拼写错误。不过,这对于 LUKS 分区不起作用,因为 mount 在系统读取 crypttab 文件之前无法识别该分区,直到重启计算机后才会读取该文件。所以,在添加 LUKS 分区时,编辑 fstab 文件时要格外小心。

  1. 现在到了关键时刻。重启计算机,检查一切是否正常工作。使用 mount 命令来验证你的努力是否成功:
donnie@ubuntu2204-packt:~$ mount | grep 'secrets'
/dev/mapper/luks-e38e087a-205c-4aeb-81d5-03f03b8e8020 on /secrets type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
donnie@ubuntu2204-packt:~$
  1. 实验结束。

提示:

虽然可以在/etc/crypttab文件中包含密码或密钥,但我个人倾向于不这么做。如果必须这么做,请确保密码或密钥存储在加密的/分区中,启动时你总是需要输入密码。你可以在这里阅读更多信息:将加密磁盘的密码短语存储在/etc/crypttab中:askubuntu.com/questions/181518/store-the-passphrase-of-encrypted-disk-in-etc-crypttab-encrypted

现在我们已经了解了 LUKS,让我们继续学习 eCryptfs。

使用 eCryptfs 加密目录

加密整个分区很酷,但有时你可能只需要使用文件级加密来加密一个单独的目录。为此,我们可以使用 eCryptfs。我们需要在 Ubuntu 机器上操作,因为 Red Hat 及其衍生版本已经不再包含 eCryptfs。(它曾在 Red Hat 6 和 CentOS 6 中出现,但在任何更新的版本中已经无法安装。)

提示:

你可以在 LUKS 加密磁盘上使用 eCryptfs,但这并不是必须的,我个人不推荐这样做。

实操实验 – 为新用户账户加密主目录

第三章《保护普通用户账户》中,我向你展示了如何在创建用户账户时加密用户的主目录。让我们回顾一下创建 Goldie 账户的命令:

  1. 如果尚未安装,请先安装ecryptfs-utils包:
sudo apt install ecryptfs-utils
  1. 在 Ubuntu 虚拟机上,为 Goldie 创建一个带有加密目录的账户:
sudo adduser --encrypt-home goldie
  1. 让 Goldie 登录。让她解开挂载密码,写下并将其保存在安全的地方。如果她需要恢复损坏的目录,密码是必需的:
ecryptfs-unwrap-passphrase .ecryptfs/wrapped-passphrase

当你使用adduser --encrypt-home命令时,新用户的主目录将自动设置为限制性权限值,只有目录的所有者可以访问,其他人无法进入。即使在 Ubuntu 20.04 上,当你保持adduser.conf文件的默认设置时,也会发生这种情况。

在现有主目录中创建私有目录

假设你的 Ubuntu 服务器上有一些用户,出于某种奇怪的原因,他们不想加密整个主目录,并且希望保持主目录的755权限设置,以便其他人可以访问他们的文件。但他们也希望拥有一个仅自己可访问的私有目录。

与加密整个主目录不同,任何用户都可以在自己的主目录中创建一个加密的私有目录。我们来看看怎么做:

  1. 如果尚未安装,请先安装ecryptfs-utils包:
sudo apt install ecryptfs-utils

要创建这个私人目录,使用交互式的ecryptfs-setup-private工具。如果你有管理员权限,可以为其他用户执行此操作。没有管理员权限的用户可以为自己执行此操作。为了我们的演示,假设 Charlie,我那只大缅因猫/灰色虎斑猫,需要一个加密的私人空间。(谁知道猫也有秘密,对吧?)

  1. 以正常方式创建 Charlie 的账户,选择加密的主目录选项。

  2. 然后,以 Charlie 身份登录,让他创建自己的私人目录:

charlie@ubuntu2:~$ ecryptfs-setup-private
Enter your login passphrase [charlie]:
Enter your mount passphrase [leave blank to generate one]:
Enter your mount passphrase (again):
************************************************************************
YOU SHOULD RECORD YOUR MOUNT PASSPHRASE AND STORE IT IN A SAFE LOCATION.
  ecryptfs-unwrap-passphrase ~/.ecryptfs/wrapped-passphrase
THIS WILL BE REQUIRED IF YOU NEED TO RECOVER YOUR DATA AT A LATER TIME.
************************************************************************
. . .
. . .
charlie@ubuntu2:~$
  1. 对于登录密码短语,Charlie 输入他的正常密码或用于登录用户账户的密码短语。他本可以让系统生成自己的挂载密码短语,但他决定输入自己的密码短语。因为他确实输入了自己的挂载密码短语,所以他不需要执行ecryptfs-unwrap-passphrase命令来查看密码短语是什么。但是,为了演示该命令是如何工作的,假设 Charlie 输入了TurkeyLips作为他的挂载密码短语:
charlie@ubuntu2:~$ ecryptfs-unwrap-passphrase .ecryptfs/wrapped-passphrase
Passphrase:
TurkeyLips
charlie@ubuntu2:~$

是的,这是一个非常弱的密码短语,但为了演示的目的,它是有效的。

  1. 让 Charlie 注销,然后重新登录。之后,他就可以开始使用他的新私人目录了。此外,你会看到他在主目录中有三个新的隐藏目录。这三个新目录只能由 Charlie 访问,即使他将顶级主目录设置为对所有人开放:
charlie@ubuntu2:~$ ls -la
total 40
drwxr-xr-x 6 charlie charlie 4096 Oct 30 17:00 .
drwxr-xr-x 4 root root 4096 Oct 30 16:38 ..
-rw------- 1 charlie charlie 270 Oct 30 17:00 .bash_history
-rw-r--r-- 1 charlie charlie 220 Aug 31 2015 .bash_logout
-rw-r--r-- 1 charlie charlie 3771 Aug 31 2015 .bashrc
drwx------ 2 charlie charlie 4096 Oct 30 16:39 .cache
drwx------ 2 charlie charlie 4096 Oct 30 16:57 .ecryptfs
drwx------ 2 charlie charlie 4096 Oct 30 16:57 Private
drwx------ 2 charlie charlie 4096 Oct 30 16:57 .Private
-rw-r--r-- 1 charlie charlie 655 May 16 08:49 .profile
charlie@ubuntu2:~$
  1. /etc/pam.d目录下运行grep 'ecryptfs' *命令。你会看到 PAM 已配置为在用户登录系统时自动挂载用户的加密目录:
donnie@ubuntu2:/etc/pam.d$ grep 'ecryptfs' *
common-auth:auth    optional    pam_ecryptfs.so unwrap
common-password:password    optional    pam_ecryptfs.so
common-session:session    optional    pam_ecryptfs.so unwrap
common-session-noninteractive:session    optional    pam_ecryptfs.so unwrap
donnie@ubuntu2:/etc/pam.d$
  1. 实验结束。

好的,那么我们现在知道如何加密用户的主目录。接下来,让我们看看如何加密其他目录。

实验环节——使用 eCryptfs 加密其他目录

加密其他目录很简单,只需使用ecryptfs文件系统挂载它们即可:

  1. 在文件系统的顶层创建一个secrets2目录:
donnie@ubuntu2204-packt:~$ sudo mkdir /secrets2
[sudo] password for donnie: 
donnie@ubuntu2204-packt:~$
  1. 使用mount命令并带上-t ecryptfs选项来加密该目录。请注意,你需要列出目录名两次,因为它将作为自己的挂载点。从菜单中选择1,输入你想要的密码短语,并选择加密算法和密钥长度:
donnie@ubuntu2204-packt:~$ sudo mount -t ecryptfs /secrets2/ /secrets2/
Select key type to use for newly created files: 
 1) passphrase
 2) tspi
Selection: 1
Passphrase: 
Select cipher: 
 1) aes: blocksize = 16; min keysize = 16; max keysize = 32
 2) blowfish: blocksize = 8; min keysize = 16; max keysize = 56
 3) des3_ede: blocksize = 8; min keysize = 24; max keysize = 24
 4) twofish: blocksize = 16; min keysize = 16; max keysize = 32
 5) cast6: blocksize = 16; min keysize = 16; max keysize = 32
 6) cast5: blocksize = 8; min keysize = 5; max keysize = 16
Selection [aes]:

选择默认的aes16字节的密钥。

  1. 对于plaintext passthrough选择默认的no,对于文件名加密选择yes
Enable plaintext passthrough (y/n) [n]:
Enable filename encryption (y/n) [n]: y
  1. 选择默认的Filename Encryption Key并验证挂载选项:
Filename Encryption Key (FNEK) Signature [e339e1ebf3d58c36]:
Attempting to mount with the following options:
  ecryptfs_unlink_sigs
  ecryptfs_fnek_sig=e339e1ebf3d58c36
  ecryptfs_key_bytes=16
  ecryptfs_cipher=aes
  ecryptfs_sig=e339e1ebf3d58c36
  1. 这个警告只会在你第一次挂载目录时出现。对于最后两个问题,输入yes以防止该警告再次出现:
WARNING: Based on the contents of [/root/.ecryptfs/sig-cache.txt],
it looks like you have never mounted with this key
before. This could mean that you have typed your
passphrase wrong.
Would you like to proceed with the mount (yes/no)? : yes
Would you like to append sig [e339e1ebf3d58c36] to
[/root/.ecryptfs/sig-cache.txt]
in order to avoid this warning in the future (yes/no)? : yes
Successfully appended new sig to user sig cache file
Mounted eCryptfs
  1. 为了好玩,在你新加密的secrets2目录中创建一个文件,然后卸载该目录。接着,尝试列出目录内容:
cd /secrets2
sudo vim secret_stuff.txt
cd
sudo umount /secrets2
donnie@ubuntu2204-packt:~$ ls -l /secrets2/
total 12
-rw-rw-r-- 1 donnie donnie 12288 Oct 28 19:04 ECRYPTFS_FNEK_ENCRYPTED.FXbXCS5fwxKABUQtEPlumGPaN-RGvqd13yybkpTr1eCVWVHdr-lrmi1X9Vu-mLM-A-VeqIdN6KNZGcs-
donnie@ubuntu2204-packt:~$

通过选择加密文件名,当目录未挂载时,别人甚至无法知道你有什么文件。当你准备再次访问加密的文件时,只需像之前一样重新挂载目录。

使用 eCryptfs 加密交换分区

如果你只是用 eCryptfs 加密单个目录,而不是使用 LUKS 进行整盘加密,你需要加密交换分区以防止意外的数据泄露。解决这个问题只需要执行一个简单的命令:

donnie@ubuntu:~$ sudo ecryptfs-setup-swap
WARNING:
An encrypted swap is required to help ensure that encrypted files are not leaked to disk in an unencrypted format.
HOWEVER, THE SWAP ENCRYPTION CONFIGURATION PRODUCED BY THIS PROGRAM WILL BREAK HIBERNATE/RESUME ON THIS SYSTEM!
NOTE: Your suspend/resume capabilities will not be affected.
Do you want to proceed with encrypting your swap? [y/N]: y
INFO: Setting up swap: [/dev/sda5]
WARNING: Commented out your unencrypted swap from /etc/fstab
swapon: stat of /dev/mapper/cryptswap1 failed: No such file or directory
donnie@ubuntu:~$

不用担心关于缺少 /dev/mapper/cryptswap1 文件的警告。下次重启计算机时,它会被创建。

使用 VeraCrypt 跨平台共享加密容器

曾几何时,TrueCrypt 是一个跨平台程序,允许在不同操作系统之间共享加密容器。但该项目一直充满神秘,因为其开发者从未公开身份。然后,突然间,开发者发布了一条神秘信息,称 TrueCrypt 已经不再安全,并关闭了该项目。

VeraCrypt 是 TrueCrypt 的继任者,它允许在 Linux、Windows、macOS 和 FreeBSD 机器之间共享加密容器。尽管 LUKS 和 eCryptfs 都很好,但 VeraCrypt 在某些方面提供了更多的灵活性:

  • 如前所述,VeraCrypt 提供了跨平台共享,而 LUKS 和 eCryptfs 则没有。

  • VeraCrypt 允许你加密整个分区或整个存储设备,或者创建虚拟加密磁盘。

  • 你不仅可以使用 VeraCrypt 创建加密卷,还可以将其隐藏,从而获得合理的否认能力。

  • VeraCrypt 提供命令行和图形界面两种版本,因此适用于服务器使用或普通桌面用户。

  • 与 LUKS 和 eCryptfs 一样,VeraCrypt 是免费的开源软件,这意味着它可以免费使用,并且源代码可以进行审计以查找漏洞或后门。

实操实验室 – 获取和安装 VeraCrypt

按照以下步骤安装 VeraCrypt:

  1. 从这里下载 VeraCrypt:www.veracrypt.fr/en/Downloads.html

  2. VeraCrypt 的 Linux 版本有两种形式。首先是 .tar.bz2 文件,其中包含一组通用安装脚本,应该适用于任何 Linux 发行版。一旦解压 .tar.bz2 压缩包文件,你会看到三个图形界面安装脚本和两个控制台模式安装脚本。脚本适用于 32 位和 64 位版本的 Linux:

donnie@donnie-VirtualBox:~$ tar xjvf veracrypt-1.25.9-setup.tar.bz2 
veracrypt-1.25.9-setup-console-x64
veracrypt-1.25.9-setup-console-x86
veracrypt-1.25.9-setup-gtk3-console-x64
veracrypt-1.25.9-setup-gtk3-gui-x64
veracrypt-1.25.9-setup-gui-x64
veracrypt-1.25.9-setup-gui-x86
donnie@donnie-VirtualBox:~$
  1. 可执行权限已经设置好,所以你只需要执行以下操作来安装:
donnie@donnie-VirtualBox:~$ ./veracrypt-1.25.9-setup-gui-x64

你需要具有 sudo 权限,但安装程序会提示你输入 sudo 密码。在阅读并同意相当冗长的许可协议后,安装过程只需要几秒钟。

实验室结束

最近,VeraCrypt 的开发人员也开始为特定的 Linux 发行版提供.deb.rpm安装包。对于 Debian/Ubuntu 类的系统,可以使用sudo dpkg -i来安装.deb文件。对于 RHEL/CentOS/AlmaLinux/SUSE 系统,可以使用sudo rpm -Uvh来安装.rpm文件。请注意,您可能会收到错误信息,提示您需要安装其他软件包作为依赖项。另外,RHEL/AlmaLinux 9 发行版没有.rpm安装包。不过不用担心,因为我已经验证过 CentOS 8 的安装包在 AlmaLinux 9 上完全可以正常使用。

实操实验 - 在控制台模式下创建并挂载 VeraCrypt 卷

我一直没能找到关于 VeraCrypt 控制台模式变种的文档,但你可以通过输入veracrypt来查看可用命令的列表。在这个演示中,你将创建一个 2 GB 的加密目录。但你也可以在其他地方做,比如在 USB 闪存盘上。

  1. 要创建一个新的加密卷,请输入以下命令:
veracrypt -c
  1. 这将带你进入一个易于使用的交互式工具。大多数情况下,只需接受默认选项即可:
donnie@ubuntu:~$ veracrypt -c
Volume type:
 1) Normal
 2) Hidden
Select [1]:
Enter volume path: /home/donnie/good_stuff
Enter volume size (sizeK/size[M]/sizeG): 2G
Encryption Algorithm:
 1) AES
 2) Serpent
. . .
. . .
Select [1]:
. . .
. . .
  1. 对于文件系统,默认的FAT选项为 Linux、macOS 和 Windows 之间提供最佳的跨平台兼容性:
Filesystem:
 1) None
 2) FAT
 3) Linux Ext2
 4) Linux Ext3
 5) Linux Ext4
 6) NTFS
 7) exFAT
Select [2]:
  1. 选择你的密码和PIM个人迭代乘数的缩写)。我的 PIM 值为8891。(较高的 PIM 值提供更好的安全性,但也会导致卷挂载的时间变长。)然后,输入至少 320 个随机字符以生成加密密钥。(如果我的猫在键盘上走来走去,那就太方便了):
Enter password:
Re-enter password:
Enter PIM: 8891
Enter keyfile path [none]:
Please type at least 320 randomly chosen characters and then press Enter:
  1. 按下Enter键后,请耐心等待,因为加密卷的最终生成需要一些时间。在这里,你可以看到我的 2 GB good_stuff容器已成功创建:
donnie@ubuntu:~$ ls -l good_stuff
-rw------- 1 donnie donnie 2147483648 Nov  1 17:02 good_stuff
donnie@ubuntu:~$
  1. 挂载此容器以使用它。首先创建一个挂载点目录:
donnie@ubuntu:~$ mkdir good_stuff_dir
donnie@ubuntu:~$
  1. 使用veracrypt工具将你的容器挂载到该挂载点:
donnie@ubuntu:~$ veracrypt good_stuff good_stuff_dir
Enter password for /home/donnie/good_stuff:
Enter PIM for /home/donnie/good_stuff: 8891
Enter keyfile [none]:
Protect hidden volume (if any)? (y=Yes/n=No) [No]:
Enter your user password or administrator password:
donnie@ubuntu:~$
  1. 要查看已挂载的 VeraCrypt 卷,请使用veracrypt -l
donnie@ubuntu:~$ veracrypt -l
1: /home/donnie/secret_stuff /dev/mapper/veracrypt1 /home/donnie/secret_stuff_dir
2: /home/donnie/good_stuff /dev/mapper/veracrypt2 /home/donnie/good_stuff_dir
donnie@ubuntu:~$
  1. 实验结束。就这些。

在图形界面模式下使用 VeraCrypt

任何支持的操作系统的桌面用户都可以安装 VeraCrypt 的图形界面变种。但要注意,你不能在同一台机器上同时安装控制台模式变种和图形界面变种,因为一个会覆盖另一个。以下是安装后的情况:

19501_06_06.png

19501_06_06.png

由于本书的主要重点是服务器安全,因此我不会在这里详细介绍图形界面版本。但它相当直观,你可以在他们的官方网站查看完整的 VeraCrypt 文档。

你可以从这里下载 VeraCrypt:www.veracrypt.fr/en/Home.html

OpenSSL 和公钥基础设施

使用 OpenSSL 时,我们可以在信息传输过程中即时加密数据。我们无需在发送数据之前手动加密,因为 OpenSSL 加密会自动进行。这一点非常重要,因为没有它,在线商业和银行业务无法存在。

安全套接字层SSL)是最初的传输中加密协议。具有讽刺意味的是,尽管我们现在使用的是 OpenSSL 套件的程序和库,但我们不再希望使用 SSL。相反,我们现在希望使用传输层安全TLS)协议。SSL 充满了遗留代码,并伴随有许多与这些遗留代码相关的漏洞。TLS 较新,并且更加安全。但是,即使在使用 TLS 时,我们仍然可以使用 OpenSSL 套件。

旧版 SSL 协议如此糟糕的一个原因是过去的政府监管,特别是在美国,禁止使用强加密。在公共互联网的最初几年,美国的网站运营商不能合法地实施超过 40 位的加密密钥。即使在当时,40 位密钥也不能提供足够的安全性。但美国政府将强加密视为一种军火,并试图控制它,以防其他国家的政府能够使用它。与此同时,一个名为 Fortify 的澳大利亚公司开始生产一个强加密插件,用户可以将其安装在 Netscape 浏览器中。这个插件允许使用 128 位加密,我和我的极客朋友们都迫不及待地在自己的计算机上安装了它。回想起来,我不确定它是否真的起了多大作用,因为美国的网站运营商仍然被禁止在他们的 Web 服务器上使用强加密密钥。

惊人的是,Fortify 公司仍然保留着他们的网站。即使现在这个插件已经完全没用了,您仍然可以下载 Fortify 插件。这是 Fortify 网站的截图:

19501_06_07.png

19501_06_07.png

加密的 SSL/TLS 会话同时使用对称和非对称机制。为了获得可接受的性能,它使用对称加密来加密传输中的数据。但对称加密要求在两个通信方之间交换私钥。为此,SSL/TLS 首先使用我们在 GPG 部分中看到的相同公钥交换机制来协商一个非对称会话。

一旦建立了非对称会话,两个通信方就可以安全地交换他们将用于对称会话的私钥。

商业证书颁发机构

为了使这一过程生效,您需要在您的 Web 服务器上安装安全证书。该证书有两个作用:

  • 它包含了设置非对称密钥交换会话所需的公钥。

  • 可选地,它可以验证您的网站的身份或进行身份认证。因此,举例来说,用户理论上可以确保他们连接的是自己的真实银行,而不是乔·黑客伪装成自己银行的“骗子和罪犯银行”。

当您购买证书时,您会发现有相当多的供应商,它们都被称为证书颁发机构,或称为CA。大多数 CA,包括 Thawte、Symantec、GoDaddy 和 Let's Encrypt 等供应商,提供几种不同级别的证书。为了帮助解释不同级别证书之间的差异,以下是来自 GoDaddy 网站的截图:

在列表的左侧,最便宜的价格是标准 域名验证DV)证书。供应商将这种类型的证书宣传为仅在您关心的是加密的情况下使用。身份验证仅限于域名验证,这意味着,您的网站的记录已经在公共可访问的 DNS 服务器上找到。

在右侧,我们看到了高级 扩展验证EV)证书。这是证书供应商提供的顶级、最高等级证书。拥有这种扩展验证级别的证书,您需要经历一些繁琐的步骤来证明您确实是您所声称的人,并且您的网站和您的业务都是合法的。曾经,Firefox 和 Chrome 会在任何具有 EV 证书的网站的 URL 中显示绿色的高保障栏,但现在它们不再这么做,原因我稍后会解释。

那么,这种经过严格身份测试的高级 SSL EV证书究竟有多好呢?嗯,没我想的那么好。在我为本书前一版写下关于不同类型证书的解释后两天,我收到了来自 Feisty Duck Publishing 的最新一期《Bulletproof TLS Newsletter》。其中的重大新闻是,Google 和 Mozilla 决定从 Chrome 和 Firefox 的未来版本中移除绿色的高保障栏。他们的理由如下:

  • 绿色高保障栏旨在帮助用户避免钓鱼攻击。但为了让这一点发挥作用,用户必须注意到高保障栏的存在。研究表明,大多数人甚至没有注意到它。

  • 安全研究员 Ian Carrol 质疑扩展验证证书的价值。作为实验,他成功注册了一个虚假的 Stripe, Inc. 证书(Stripe 是一家合法公司)。证书供应商最终注意到他们的错误并吊销了该证书,但这本不应该发生。

  • 除了其他所有内容之外,注册扩展验证证书时也可能使用不正确的信息。这表明验证过程并不像证书供应商所宣传的那样彻底。

尽管偶尔会遇到这些问题,我仍然相信扩展验证证书是有用的。当我访问我的银行账户时,我喜欢相信额外的身份验证永远不会是坏事。

另一个比较奇怪的事情是,证书供应商仍然将他们的证书宣传为 SSL 证书。不过,不要被欺骗。只要网站所有者正确配置他们的服务器,他们将使用更安全的 TLS 协议,而不是 SSL。

Let's Encrypt 是一个相对较新的组织,目标是确保全球所有网站都启用加密。这个目标很值得追求,但也带来了一个新问题。下面是 Let's Encrypt 网站的样子:

19501_06_09.png

19501_06_09.png

要从传统证书供应商处获取证书,你必须使用 OpenSSL 工具来创建密钥和证书请求。然后,你将提交证书请求、必要时的身份验证以及付款给证书颁发机构。根据你购买的证书等级,你可能需要等待一到几天才能拿到证书。

Let's Encrypt 完全免费,而且你不需要费劲去获取证书。相反,你只需配置你的 Web 服务器,在每次设置新网站时自动获取新的 Let's Encrypt 证书。如果 Let's Encrypt 看到你的网站在一个公开可访问的 DNS 服务器上有有效记录,它将自动创建并安装证书到你的服务器上。除了需要配置 Web 服务器以使用 Let's Encrypt 外,完全没有麻烦。

Let's Encrypt 的问题在于,它比扩展验证证书更容易被滥用。Let's Encrypt 开始运作后不久,犯罪分子就开始设置看似是合法商业网站子域名的域名。因此,人们看到网站已经加密,并且域名看起来合法,就会愉快地输入自己的凭证,而不会多想。Let's Encrypt 对于合法用途非常方便和有用,但也要注意它的缺点。

提示:

在选择证书供应商之前,做些调查。有时,即使是大牌供应商也会遇到问题。几年前,Google 将 Symantec 从 Chrome 信任的证书颁发机构列表中移除,因为 Symantec 被指控多次违反行业最佳实践。这真是有点讽刺,因为 Symantec 长期以来一直是安全产品的可信供应商。

现在我们已经了解了 SSL/TLS 加密的基础知识,接下来看看如何使用 OpenSSL 套件实现它。

创建密钥、证书签名请求和证书

好消息是,无论我们使用哪个较新的Linux 发行版,操作过程都是一样的。(我说较新,因为最新版本的 Ubuntu 和 RHEL/AlmaLinux 使用 OpenSSL 3 版本。某些版本 3 的命令与较老版本的有所不同。)不太好的消息是,OpenSSL 可能有点难学,因为它有很多子命令,每个子命令都有一套自己的选项和参数。请耐心等待,我会尽力将其拆解清楚。

使用 RSA 密钥创建自签名证书

自签名证书在你只需要加密或进行测试时非常有用。自签名证书不涉及身份验证,所以绝对不应该在用户需要信任的服务器上使用它们。假设我需要在将新网站投入生产前进行测试,并且我不想使用真实的密钥和证书进行测试。我将通过一个命令创建密钥和自签名证书:

openssl req -newkey rsa:2048 -nodes -keyout donnie-domain.key-x509 -days 365 -out donnie-domain.crt

下面是具体内容:

  • openssl:我正在使用 OpenSSL,并且只有普通用户权限。目前,我在自己的主目录下进行所有操作,所以不需要 root 或 sudo 权限。

  • req:这是用于管理证书签名请求(CSR)的子命令。在创建自签名证书时,OpenSSL 会创建一个临时的 CSR。

  • -newkey rsa:2048:我正在创建一个长度为 2,048 位的 RSA 密钥对。我实际上想使用稍微长一点的密钥,但这可能会影响服务器在设置 TLS 握手时的性能。(同样,这个选项前面只有一个短横线。)

  • -nodes:这意味着我将不会加密即将创建的私钥。如果我要加密私钥,每次重启 Web 服务器时都需要输入私钥密码。

  • -keyout donnie-domain.key-x509:我正在创建名为donnie-domain.key-x509的私钥。x509部分表示该密钥将用于自签名证书。

  • -days 365:证书将在一年后过期。

  • -out donnie-domain.crt:最后,我正在创建donnie-domain.crt证书。

当你运行这个命令时,系统会提示你输入有关你的公司和服务器的信息。(稍后我们会详细讨论。)创建完密钥和证书后,我需要将它们移动到正确的位置,并配置 Web 服务器以找到它们。(稍后我们也会涉及这个话题。)

加密私钥是一个可选步骤,我没有进行加密。如果我加密了私钥,每次重启 Web 服务器时,我都必须输入密码。如果有任何没有密码的 Web 服务器管理员,这可能会带来问题。而且,尽管这听起来有些反直觉,但加密 Web 服务器上的私钥并不会显著提高安全性。任何能够获得 Web 服务器物理访问权限的恶意人士,都会使用内存取证工具从系统内存中获取私钥,即使密钥已加密。不过,如果你计划将密钥的备份存储在其他地方,绝对需要加密那个备份。现在,让我们创建一个加密的私钥备份,以便可以安全地存放在 Web 服务器以外的地方:

[donnie@localhost ~]$ openssl rsa -aes256 -in donnie-domain.key-x509 -out donnie-domain-encrypted.key-x509 
writing RSA key
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
[donnie@localhost ~]$

这里有两件事需要注意:

  • rsa -aes256 表示我正在使用 AES256 加密算法来加密 RSA 密钥。

  • 为了确保我创建的是一个副本,而不是覆盖原始的未加密密钥,我指定了 donnie-domain-encrypted.key-x509 作为副本的名称。

使用椭圆曲线密钥创建自签名证书

RSA keys were okay in their day, but they do have their disadvantages. (I'll cover this more in just a bit.) Elliptic Curve (EC) keys are superior in pretty much every way. So, let's now create a self-signed certificate with an EC key, instead of with an RSA key, like so:
openssl req -new -x509 -nodes -newkey ec:<(openssl ecparam -name secp384r1) -keyout cert.key.x509 -out cert.crt -days 3650

唯一不同的部分是ec:<(openssl ecparam -name secp384r1)。它看起来很奇怪,但其实很有逻辑。在创建 EC 密钥时,必须使用 ecparam 命令指定一个参数。通常你会看到这是两个分开的 openssl 命令,但将这两个命令组合在一起以一个命令的形式嵌套执行会更方便。内层的 openssl 命令通过输入重定向符号(<)将其输出反馈给外层的 openssl 命令。-name secp384r1 部分表示我们正在创建一个使用 secp384 曲线算法的 384 位 EC 密钥。

创建 RSA 密钥和证书签名请求

通常,我们不会使用自签名证书来处理面向公众的接口。相反,我们希望从商业 CA 获取证书,因为我们希望用户知道他们连接的是一个其所有者身份已经过验证的服务器。为了从受信任的 CA 获取证书,首先需要创建一个密钥和证书签名请求CSR)。现在我们来做这个:

openssl req --out CSR.csr -new -newkey rsa:2048 -nodes -keyout server-privatekey.key

这里是详细解析:

  • openssl:我正在使用 OpenSSL,并且只用我的普通用户权限。现在,我将所有操作都放在我的主目录中,因此无需 root 或 sudo 权限。

  • req:这是用于管理 CSR 的子命令。

  • --out CSR.csr--out意味着我正在创建某些东西。在这种情况下,我正在创建一个名为CSR.csr的 CSR。所有的 CSR 都将具有 .csr 文件扩展名。

  • -new:这是一个新请求。(是的,这个选项前面只有一个破折号,与上一行中的out(前面有两个破折号)不同。)

  • -newkey rsa:2048:我正在创建一个长度为 2,048 位的 RSA 密钥对。实际上,我想使用更长一点的密钥,但那样会影响设置 TLS 握手时的服务器性能。(再次提醒,这前面只加了一个短横线。)

  • -nodes:这意味着我不会加密我即将创建的私钥。如果我要加密私钥,则每次重启 Web 服务器时都必须输入私钥密码。

  • -keyout server-privatekey.key:最后,我正在创建一个名为server-privatekey.key的私钥。由于这个密钥不是用于自签名证书,我没有在密钥文件名末尾加上-x509

现在我们来看一下命令输出的一个片段:

[donnie@localhost ~]$ openssl req --out CSR.csr -new -newkey rsa:2048 -nodes -keyout server-privatekey.key
Generating a RSA private key
. . .
. . .
Country Name (2 letter code) [XX]:US
State or Province Name (full name) []:GA
Locality Name (eg, city) [Default City]:Saint Marys
Organization Name (eg, company) [Default Company Ltd]:Tevault Enterprises
Organizational Unit Name (eg, section) []:Education
Common Name (eg, your name or your server's hostname) []:www.tevaultenterprises.com
Email Address []:any@any.net
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:TurkeyLips
An optional company name []:

所以,我已经输入了关于公司位置、名称和网站名称的信息。注意底部,它询问我输入一个挑战密码。这个密码并不加密私钥或证书。它只是证书授权机构和我之间的共享密钥,嵌入到证书中。我需要将它保存在安全的地方,以备将来需要重新安装证书时使用。(而且,天哪,当你真正做这件事时,选一个比TurkeyLips更好的密码吧。)

如之前所述,我没有加密私钥。但如果你需要备份,只需按照前面章节中看到的步骤进行操作。

要从商业 CA 获得证书,访问他们的网站并按照指示操作。当你收到证书时,将其安装到 Web 服务器的正确位置,并配置 Web 服务器以找到它。

创建 EC 密钥和 CSR

直到几年前,你会想在 Web 服务器上使用 RSA 密钥。它们没有某些其他密钥类型的安全弱点,而且几乎所有的 Web 浏览器都广泛支持它们。但 RSA 密钥确实有两个弱点:

  • 即使是标准的 2,048 位长度,它们也比其他密钥类型需要更多的计算能力。增加密钥长度以提高安全性可能会降低 Web 服务器的性能。

  • RSA 不提供完美前向保密PFS)。换句话说,如果有人捕获了 RSA 算法生成的会话密钥,他们将能够解密过去的内容。如果同一个人捕获了一个由 PFS 算法生成的会话密钥,他们将只能解密当前的通信流。

使用新型 EC 算法代替陈旧的 RSA 算法可以解决这两个问题。但是,如果你翻开几年前的书籍,你会看到它们推荐使用 RSA 密钥以兼容旧版的网页浏览器。这部分是因为某些操作系统及其相关的专有浏览器,比预期的寿命要长得多。(我看着你,Windows XP。)不过,现在,2022 年 10 月,当我坐在这里写这些时,我认为是时候忽略那些拒绝从这些过时平台上迁移的人们的需求了。我的意思是,Windows XP 和 Windows 7 都早已进入了生命周期的终结。那么,大家该与时俱进了。

与我们刚刚看到的 RSA 密钥不同,我们不能通过一个简单的命令同时创建 EC 私钥和 CSR。对于 EC,我们需要将其分为两个独立的步骤来完成。

首先,我将创建私钥:

openssl genpkey -algorithm EC -out eckey.pem -pkeyopt ec_paramgen_curve:P-384 -pkeyopt ec_param_enc:named_curve

以下是详细说明:

  • genpkey -algorithm ECgenpkey 命令是 OpenSSL 的一个较新的添加,现在是创建私钥的推荐方法。在这里,我告诉它使用 EC 算法来创建密钥。

  • -out eckey.pem:我正在创建 eckey.pem 密钥,它采用 隐私增强邮件PEM)格式。我在上一节中创建的 RSA 密钥也是 PEM 格式的,但我使用了 .key 文件扩展名。你可以使用 .key.pem 文件扩展名,两者都可以工作。但是,如果你使用 .pem 扩展名,任何查看这些文件的人都能一眼看出它们是 PEM 密钥。

  • -pkeyopt ec_paramgen_curve:P-384:这告诉 OpenSSL 创建一个 384 位长度的 EC 密钥。EC 的一个优点是,它较短的密钥长度能提供与较长的 RSA 密钥相同的加密强度。在这种情况下,我们有一个 384 位的密钥,它实际上比一个 2048 位的 RSA 密钥更强大。而且,它需要更少的计算资源。(我称之为彻底的胜利!)

  • -pkeyopt ec_param_enc:named_curve:这是我用于 EC 参数的编码方式。必须设置为 named_curveexplicit

现在,我将创建一个 CSR,并使用我的新私钥对其进行签名,如下所示:

[donnie@localhost ~]$ openssl req -new -key eckey.pem -out eckey.csr
. . .
. . .
[donnie@localhost ~]$

我没有包括的输出与你在 RSA 密钥部分看到的相同。

最后的步骤与之前相同。选择一个 CA,让他们告诉你如何提交 CSR。当他们颁发证书时,将其安装到你的 web 服务器上。

创建本地 CA

从商业 CA 购买证书对于处理公众访问的需要信任的网站来说是很好的选择。但对于组织的内部使用来说,购买商业证书并不总是必要的,也未必可行。假设你的组织有一组开发人员,他们需要自己的客户端证书来访问开发服务器。为每个开发人员购买商业证书将非常昂贵,而且还需要开发服务器拥有一个公开可访问的域名,以便商业 CA 进行域名验证。即使选择免费的 Let's Encrypt 证书也不是一个好选择,因为那同样要求开发服务器拥有一个公开可访问的域名。第二个选项是使用自签名证书,但这行不通,因为自签名证书无法进行客户端认证。那就只剩下第三个选项,搭建一个私有的、内部部署的 CA。

如果你在网上搜索,你会找到许多关于设置私有 CA 的指南。但几乎所有的都是过时的,而且大多数都是关于如何使用 OpenSSL 设置 CA。使用 OpenSSL 作为 CA 没有问题,只是设置过程相当复杂且分阶段进行。而且,当你最终成功设置好之后,你还需要通过命令行使用复杂的命令来执行任何操作。我们需要的是一种更加用户友好的方式,适合你和你的用户。

实操实验室 – 设置 Dogtag CA

Dogtag PKI 设置起来要简单得多,它拥有 OpenSSL 所没有的漂亮 Web 界面。它可以在 Debian/Ubuntu 和 RHEL/AlmaLinux 的正常仓库中找到,但软件包名称不同。在 Debian/Ubuntu 仓库中,软件包名称是 dogtag-pki。在 RHEL/AlmaLinux 仓库中,软件包名称是 pki-ca。(由于某种我不理解的原因,红帽的用户永远不会使用“Dogtag”这个名字。)

在安装 Dogtag 软件包之前,我们需要做一些简单的工作:

Set a Fully Qualified Domain Name (FQDN) on the server

要么在本地 DNS 服务器中为 Dogtag 服务器创建一条记录,要么在其自己的 /etc/hosts 文件中创建一条条目。

你可以在 AlmaLinux 9 或 Ubuntu 22.04 虚拟机上进行此操作,我将分别给出两者的操作指南。为了访问 Dogtag 仪表板,我们将使用第二台安装了桌面环境的 Linux 虚拟机。解决了这些问题之后,我们开始吧:

  1. 在你的服务器虚拟机上,设置一个完全限定域名(FQDN),用你自己的替代我正在使用的域名:
sudo hostnamectl set-hostname donnie-ca.local
  1. 编辑 /etc/hosts 文件,添加如下的一行:
192.168.0.53 donnie-ca.local

使用你虚拟机的 IP 地址和完全限定域名(FQDN)。

  1. 接下来,增加系统可以同时打开的文件描述符数量。(否则,当你运行目录服务器安装程序时,会收到警告消息。)可以通过编辑 /etc/security/limits.conf 文件来完成此操作。在文件的末尾,添加以下两行:
root            hard    nofile          4096
root            soft    nofile          4096
  1. 重启机器,以使新的主机名和文件描述符限制生效。

  2. Dogtag 将其证书和用户信息存储在 LDAP 数据库中。在这一步中,我们将安装 LDAP 服务器包以及 Dogtag 包。对于 AlmaLinux 9,请执行以下操作:

sudo dnf install 389-ds-base pki-ca

对于 Ubuntu 22.04,请执行以下操作:

 sudo apt install 389-ds-base dogtag-pki

接下来,通过首先在根用户的主目录中创建 instance.inf 文件来创建 LDAP Directory Server (DS) 实例:

sudo vim /root/instance.inf
Make its contents look something like this, using your own suffix and root_password:
# /root/instance.inf
[general]
config_version = 2
[slapd]
root_password = TurkeyLips
[backend-userroot]
sample_entries = yes
suffix = dc=donnie-ca,dc=local
  1. (是的,我知道将密码放入明文配置文件是不良做法。但没关系,我们马上就会处理。)

  2. 现在,我们可以使用 instance.inf 文件和 dscreate 实用程序来创建目录服务器实例:

sudo dscreate from-file /root/instance.inf
  1. 最后,是时候创建 CA 了:
sudo pkispawn

在最后,接受所有默认设置。当询问 开始安装? 时,键入 Yes。当到达目录服务器部分时,输入用于在上一步创建 DS 实例时使用的密码。请注意,您将被提供通过安全端口访问 LDAP DS 实例的选择。但由于我们在同一台机器上设置 LDAP 和 Dogtag,这并不必要。

  1. 确保 Dogtag 服务会通过启用 pki-tomcatd.target 自动启动。执行以下操作:
sudo systemctl enable pki-tomcatd.target
sudo shutdown -r now
  1. 一切设置完成后,您将不再需要保存密码明文的 instance.inf 文件。执行以下操作来删除它:
sudo shred -u -z /root/instance.inf
  1. 您将通过端口 8443/tcp 访问 Dogtag Web 界面。在 AlmaLinux 机器上,请按如下方式打开此端口:
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload

在 Ubuntu 机器上,假设您正在使用简化防火墙,请按如下方式打开端口:

sudo ufw allow 8443/tcp
  1. 在另一台带有桌面界面的 Linux 虚拟机上,编辑 /etc/hosts 文件,添加与服务器 hosts 文件中 第 2 步 中添加的相同行。然后,在该机器上打开 Firefox Web 浏览器并导航到 Dogtag 仪表板。按照这个场景的示例,URL 如下:
https://donnie-ca.local:8443

由于证书是自签名的,所以您会收到关于证书无效的警告。这是正常的,因为每个 CA 都必须从自签名证书开始,并且您还没有将此证书导入到您的信任存储中。暂时添加异常并继续操作。(换句话说,清除 永久添加 复选框中的勾选。您将在下一个实验中了解原因。)点击链接,直到您看到此屏幕:

19501_06_10.png

19501_06_10.png

  1. 点击 SSL 终端用户服务 链接。这是最终用户可以请求各种类型证书的地方。点击返回按钮返回到上一个屏幕。这次,点击 Agent 服务 链接。您将无法进入,因为需要在您的 Web 浏览器中安装证书进行身份验证。

  2. 您需要安装的证书位于您的 Dogtag VM 的 /root/.dogtag/pki-tomcat/ 目录中。将此文件复制到您正在使用 Firefox 访问 Dogtag 仪表板的 VM 上。执行以下操作:

sudo su -
cd /root/.dogtag/pki-tomcat
scp ca_admin_cert.p12 donnie@192.168.0.14:
exit

当然,替换成你自己的用户名和 IP 地址。请注意,文件会自动保存在你的主目录,并且其所有权会从 root 更改为你自己的用户名。

  1. 在带有 Firefox 的虚拟机上,将证书导入 Firefox。通过 Firefox 菜单,选择设置,然后选择隐私与安全。在屏幕的最底部,点击查看证书。点击顶部的您的证书标签,然后点击底部的导入按钮。导航到你的主目录,选择你刚刚从 Dogtag 服务器虚拟机传输过来的证书。一旦导入操作完成,你应该能在导入的证书列表中看到PKI 管理员证书:

    19501_06_11.png

    19501_06_11.png

  2. 现在尝试访问代理服务页面。确认你希望使用刚导入的证书后,你将被允许访问该页面。

  3. 实验结束。

当用户需要请求自己的证书时,他们会使用openssl来创建密钥和证书签名请求(CSR),正如我在本章前面所展示的那样。然后他们将进入 SSL 终端用户服务页面,将 CSR 的内容粘贴到他们请求证书的框中。管理员接着会进入代理服务页面批准请求并签发证书。(为了帮助你熟悉 Dogtag,我鼓励你在 Web 界面中四处点击,探索所有选项。)

将 CA 添加到操作系统中

大多数主要的网页浏览器,如 Firefox、Chrome 和 Chromium,都自带有预定义的可信 CA 数据库和相关证书。当你创建了一个私人 CA 时,你需要将 CA 证书导入到浏览器的信任库中。否则,用户会不断收到提示,告知他们正在浏览的网站使用了不受信任的证书。事实上,我们的 Dogtag 服务器就是这种情况。任何访问它并请求证书的用户都会收到一个警告,说明该 CA 使用了非可信证书。我们将通过从 Dogtag 服务器导出 CA 证书并将其导入所有用户的浏览器来解决这个问题。我们开始吧,如何?

实操实验——导出和导入 Dogtag CA 证书

Dogtag Web 门户没有这个选项,所以我们需要使用命令行:

  1. 在 Dogtag 服务器的主目录中,创建 password.txt 文件。在文件的第一行,插入服务器证书的密码。(即你在运行 pkispawn 命令时设置的密码。)

  2. 如下所示提取服务器密钥和证书:

sudo pki-server ca-cert-chain-export --pkcs12-file pki-server.p12 --pkcs12-password-file password.txt

运行 ls -l 命令,验证 pki-server.p12 文件是否已创建。

  1. p12 文件的问题在于它同时包含了服务器的私钥和证书。但要将证书添加到浏览器的 CA 信任库中,你只需要证书本身而没有私钥。按如下方式提取证书:
openssl pkcs12 -info -in pki-server.p12 -out pki-server.crt -nokeys
  1. 将这个新的 pki-server.crt 文件传输到一个带有图形桌面的机器上。在 Firefox 中,打开 Settings/Privacy & Security。点击底部的 View Certificates 按钮。点击 Authorities 选项卡并导入新的证书。选择 Trust this CA to identify websitesTrust this CA to identify email users

    19501_06_12.png

    19501_06_12.png

  2. 关闭 Firefox 然后重新打开以确保证书生效。导航到 Dogtag 门户。这一次,您不应该收到任何关于使用不受信任证书的警告消息。

  3. 实验结束。

将 CA 导入 Windows

无论您使用 Firefox 还是 Chrome,您都可以直接将 CA 证书导入到浏览器的信任存储中,而不受操作系统的限制。但是,如果您被困在微软自己的专有浏览器上,并且运行的是被称为 Windows 的副牌操作系统,则需要将证书导入到 Windows 的信任存储中,而不是浏览器中。幸运的是,这非常容易做到。在将证书复制到 Windows 机器后,只需打开 Windows 文件资源管理器并双击证书文件。然后,在弹出的对话框中点击 Install Certificate 按钮。如果您的组织正在运行 Active Directory 域,请向其中一个 AD 管理员请求帮助将其导入到 Active Directory 中。

OpenSSL 和 Apache Web 服务器

任何 Web 服务器的默认安装都不够安全,因此您需要加强安全性。其中一种方法是禁用较弱的 SSL/TLS 加密算法。一般的原则适用于所有 Web 服务器,但在我们的示例中,我们将仅讨论 Apache。 (Web 服务器加固的主题非常广泛。目前,我将限制讨论到 SSL/TLS 配置的加固。)您可以在此部分使用 Ubuntu 22.04 或 AlmaLinux 9,但两个发行版之间的包名称和配置文件有所不同。CentOS 7 和 AlmaLinux 9 之间的配置也有所不同,因此我们也会进行讨论。但是,在我解释配置选项之前,我需要简要介绍一下 SSL/TLS 协议的历史。

在 1990 年代,Netscape 的工程师发明了 SSL 协议。版本 1 从未发布,因此第一个发布的版本是 SSL 版本 2(SSLv2)。SSLv2 存在一些弱点,其中许多已在 SSLv3 中得到解决。由于微软的坚持,下一版本被重命名为传输层安全协议(TLS)版本 1(TLSv1)。(我不知道为什么微软反对使用 SSL 这个名字。)当前版本是 TLSv1.3,现在大多数 Linux 发行版都支持它。默认情况下,Apache 仍然支持一些较旧的协议。我们的目标是禁用这些较旧的协议。直到几年前,这意味着禁用 SSLv2 和 SSLv3,并保留 TLSv1 至 TLSv1.2,因为对于更新的协议,浏览器支持存在问题。不过,现在我认为禁用 TLSv1.3 之前的协议已经是安全的了。在 2019 年我编写这本书第二版时,只有苹果 Safari 浏览器不支持 TLSv1.3。幸运的是,即使是苹果现在也已经支持最新的 TLS。

加固 Ubuntu 上的 Apache SSL/TLS

本演示中,我们将使用两台 Ubuntu 22.04 虚拟机。在第一台上安装 Apache,在第二台上安装sslscan。(这个sslscan软件包在 AlmaLinux 仓库中不可用。):

  1. 在 Ubuntu 机器上安装 Apache,只需执行以下步骤:
sudo apt install apache2

这还会安装mod_ssl软件包,其中包含用于 SSL/TLS 实现的库和配置文件。

当然,如果启用了防火墙,请确保443/tcp端口已打开。

  1. Apache 服务已启用并正在运行,因此无需对此进行修改。但你确实需要使用以下三条命令启用默认 SSL 站点和 SSL 模块:
sudo a2ensite default-ssl.conf
sudo a2enmod ssl
sudo systemctl restart apache2
  1. 在查看 SSL/TLS 配置之前,先设置一个扫描机器来外部测试我们的配置。在第二台 Ubuntu 虚拟机上安装sslscan软件包:
sudo apt install sslscan

在扫描机器上,扫描你安装了 Apache 的 Ubuntu 机器,替换为你自己机器的 IP 地址:

sslscan 192.168.0.3

注意支持的算法和协议版本。你应该能看到 SSLv2、SSLv3、TLSv1.0 和 TLSv1.1 都已被禁用。只有 TLSv1.2 和 TLSv1.3 是启用的。

  1. 在安装了 Apache 的 Ubuntu 虚拟机上,编辑/etc/apache2/mods-enabled/ssl.conf文件。查找以下这一行:
SSLProtocol all -SSLv3

将其更改为以下内容:

SSLProtocol all -SSLv3 -TLSv1.2
  1. 重启 Apache 守护进程,使这个更改生效:
sudo systemctl restart apache2
  1. 再次扫描此机器,并注意输出。你应该会看到较旧的 TLSv1.2 协议也已被禁用。所以,恭喜你!你刚刚为你的 Web 服务器进行了一次快速且简单的安全升级。

  2. 实验结束。

现在,让我们来看看 RHEL 9/AlmaLinux 9。

加固 RHEL 9/AlmaLinux 9 上的 Apache SSL/TLS

在本演示中,你将在 AlmaLinux 9 虚拟机上安装 Apache 和 mod_ssl。(与 Ubuntu 不同,你需要将这两个包作为独立包安装。)使用之前实验中用过的扫描器虚拟机。RHEL 8/9 发行版的新功能是,你现在可以为大多数需要加密的服务和应用程序设置系统范围的加密策略。我们在这里快速了解一下,在 第七章SSH 强化 中再次介绍:

  1. 在做任何操作之前,关闭你的 AlmaLinux 9 虚拟机,并从 VirtualBox 控制台创建一个快照。因为稍后你将需要恢复到一个干净的快照,以测试加密策略功能。

  2. 在你的 AlmaLinux 9 虚拟机上,安装 Apache 和 mod_ssl,并启动该服务:

sudo dnf install httpd mod_ssl
sudo systemctl enable --now httpd
  1. 在防火墙上打开端口443
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
  1. 从扫描器虚拟机扫描 Apache 虚拟机,替换为你自己的 IP 地址:
sslscan 192.168.0.160

正如你在 Ubuntu 服务器上看到的,TLSv1.2 之前的协议不再受支持。

  1. 接下来,在 Apache 虚拟机上查看系统范围加密配置的状态:
sudo update-crypto-policies --show

你应该看到DEFAULT作为输出。使用DEFAULT,你可以将 TLSv1.2 作为最低协议版本,并享受 TLSv1.3 的优势。但你也会看到一些可以不使用的 TLSv1.2 算法。

  1. 关闭 Apache 虚拟机。进入 VirtualBox 控制台,恢复步骤 1 中创建的快照,以删除 Apache 安装。然后,重新启动虚拟机并将加密策略设置为 FUTURE,如下所示:
sudo update-crypto-policies --set FUTURE

我让你在设置 FUTURE 模式之前创建并恢复快照是有充分理由的。因为如果你在设置 FUTURE 模式之前安装 Apache,之后就无法启动 Apache。因此,如果你想在 FUTURE 模式下运行 Apache Web 服务器,必须先设置 FUTURE 模式,再安装 Apache。

  1. 重启 Apache 虚拟机,以使 FUTURE 模式生效。通过以下方式验证 FUTURE 模式是否已生效:
sudo update-crypto-policies --show
  1. 安装 mod_ssl 和 Apache 包,并像第 2 步那样启动 Apache。

  2. 按照第 4 步的方式扫描 Web 服务器虚拟机。你会看到 TLSv1.2 仍然启用,但启用的算法列表变得更小了。

  3. 实验结束。

除了我在这里展示的两种加密策略模式外,还有另外两种模式。LEGACY模式启用一些非常旧的算法,除非必须支持较老的客户端,否则我们不希望使用这些算法。但正如我一直强调的,任何使用如此陈旧客户端的人都应该进行升级。还有一个FIPS模式,如果你与美国政府有业务往来,可能需要使用这个模式。尽管 update-crypto-policies 工具似乎可以与 FIPS 模式一起工作,但 Red Hat 不建议这样做。相反,他们建议在安装操作系统时就设置 FIPS 模式。我们接下来会看这个。

在 RHEL 9/AlmaLinux 9 上设置 FIPS 模式

FIPS 代表联邦信息处理标准,是一套为那些希望与美国政府做生意的个人和公司设定的网络安全要求。将服务器设置为运行在 FIPS 模式涉及的不仅仅是禁用一些弱加密算法。它还需要安装一组模块,帮助加固操作系统的其他方面。

即使 update-crypto-policies 工具有设置 FIPS 模式的选项,你也永远不会使用它。如果操作系统已经安装完成,你需要使用 sudo fips-mode-setup --enable 命令来设置 FIPS 模式。但是,红帽甚至不建议这样做。相反,他们推荐在安装操作系统时设置 FIPS 模式。他们的担心是,在操作系统安装后设置 FIPS 模式可能会留下使用非 FIPS 算法创建的加密密钥。因此,他们建议在安装操作系统时设置 FIPS 模式。幸运的是,这很容易。你只需在创建新的 AlmaLinux 虚拟机时中断安装程序的启动过程并快速编辑内核配置。

  1. 创建一个新的 AlmaLinux 虚拟机并启动 AlmaLinux 安装程序。按 向上箭头 键来高亮显示 Install AlmaLinux 选项。然后不要按 回车 键继续,而是按 Tab 键以显示内核选项。你应该看到如下内容:

  2. 在屏幕底部,将 fips=1 添加到内核选项行的末尾。现在它应该像这样:

  3. 按下回车键,然后像平常一样继续安装。

  4. 安装完成并且虚拟机重启后,检查 FIPS 模式的状态,方法如下:

[donnie@localhost ~]$ fips-mode-setup --check
FIPS mode is enabled.
[donnie@localhost ~]$
  1. 最后,安装 mod_ssl 和 Apache。打开防火墙端口,并像之前的练习一样使用 sslscan 扫描虚拟机。

  2. 实验结束。

在设置 FIPS 模式的过程中,有一个警告你需要了解,尤其是在任何 RHEL 9 类型的发行版上。当前 FIPS 标准的版本是 140-3。然而,截至本文写作时(2022 年 10 月),RHEL 9 及其衍生版本仍然只符合 FIPS 140-2 标准。红帽文档并未提供何时会更新到 FIPS 140-3 的相关信息。

如果你在想为什么我没有介绍 Ubuntu 上的 FIPS 模式,那是因为在免费的 Ubuntu 版本上无法设置 FIPS 模式。如果你想在 Ubuntu 上运行 FIPS 模式,你必须购买支持合同。

现在,让我们快速看一下传统的 CentOS 7。

在 RHEL 7/CentOS 7 上加固 Apache SSL/TLS

好吧,我确实说过我们会在 CentOS 7 机器上进行这个操作。但我会简要说明。

你将在 CentOS 7 上安装 Apache 和 mod_ssl,与在 AlmaLinux 9 上的操作相同,只不过你将使用 yum 命令,而不是 dnf 命令。与 AlmaLinux 一样,你需要使用 systemctl 启用并启动 Apache,但不需要启用 ssl 站点或 ssl 模块。当然,确保防火墙上开放了端口 443

当你对 CentOS 7 机器进行 sslscan 时,你会看到一个非常长的支持算法列表,从 TLSv1 到 TLSv1.2。即使是 TLSv1.2,你也会看到一些非常糟糕的东西,比如这样:

Accepted  TLSv1.2  112 bits  ECDHE-RSA-DES-CBC3-SHA        Curve P-256 DHE 256
Accepted  TLSv1.2  112 bits  EDH-RSA-DES-CBC3-SHA          DHE 2048 bits
Accepted  TLSv1.2  112 bits  DES-CBC3-SHA 

这些行中的 DESSHA 表明我们正在支持使用过时的数据加密标准 (DES) 和 安全哈希算法SHA)的第 1 版。这不好。通过编辑 /etc/httpd/conf.d/ssl.conf 文件来移除它们。查找这两行:

SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:3DES:!aNULL:!MD5:!SEED:!IDEA

将它们更改为以下内容:

SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite HIGH:!3DES:!aNULL:!MD5:!SEED:!IDEA:!SHA

使用以下命令重新加载 Apache:

sudo systemctl reload httpd

再次扫描机器,你会看到支持的算法少了很多。(顺便提一下,新版的 TLSv1.3 的一个优势就是它完全淘汰了这些遗留算法。)

接下来,让我们看看用户如何向服务器证明自己的身份。

设置互认证

当你访问银行的安全网站时,浏览器要求 web 服务器向浏览器证明其身份。换句话说,浏览器要求查看该网站的服务器证书,以验证其是否有效。这样,你可以确保你登录的是银行的真实网站,而不是伪造网站。然后,你还需要向 web 服务器进行身份验证,但通常是通过用户名和密码进行验证。

如果 web 服务器被设置为允许,用户可以使用证书进行身份验证。这样,坏人就没有密码可以窃取或破解。你已经看到,当你将 Dogtag 的 ca_admin_cert.p12 证书导入浏览器时,就是这么做的。这个证书赋予了你访问 Dogtag 管理员页面的强大权限。你的普通终端用户没有这个证书,因此他们只能访问终端用户页面,在哪里他们可以请求证书。

主要的 web 服务器——Apache、Nginx、lighttpd 和一些其他服务器——都支持互认证。由于篇幅限制,我无法详细介绍如何在服务器上设置这一功能,但你使用的服务器文档会覆盖这部分内容。

接下来,让我们回到未来!

引入抗量子计算加密算法

你可能听说过量子计算机,它与电视上的老节目 Quantum Leap 并没有什么关系。这种新型计算机仍处于实验阶段,并可能在未来一段时间内保持这一状态。尽管如此,关于它们最终准备好投入生产使用时会是什么样子,仍然有很多炒作。人们认为,它们将比当前一代计算机强大得多,并且可能轻松破解当前最强的加密算法。确实,这样的预测让人有些害怕。(也许我正在万圣节这天打字,这个节日本身就很吓人。)

尽管对于这种严重预测是否会成真,或者生产级量子计算机是否会真正问世,存在一些怀疑,但美国联邦政府对此是非常重视的。以下是美国国家标准与技术研究院(NIST)目前推荐的量子抗性算法列表:

  • CRYSTALS-Kyber:这是用于一般加密的算法。Cloudflare、Amazon 和 IBM 已经在使用它。

  • CRYSTALS-Dilithium:这是用于加密数字签名的算法。NIST 推荐将其作为主要的签名算法。

  • FALCON:这也是一种签名算法。NIST 推荐在需要比 CRYSTALS-Dilithium 提供的签名更小的情况下使用它。

  • SPHINCS+:这是第三种签名算法,比前两种更慢且更大。它使用了与前两种不同的方法,因此 NIST 将其推荐为备选方案,以防前两种算法被攻破。

那么,如果量子计算机尚不存在,我们如何知道某个加密算法是否能抵抗量子计算机的攻击呢?嗯,我希望我能告诉你答案,但我不能。不管怎样,你现在可能还不需要过多担心,但了解这些内容还是很有价值的。

好的,咱们把这部分内容总结一下,然后进入下一章。

总结

一如既往,本章内容覆盖了很多方面。我们从使用 GPG 加密、签名和共享加密文件开始,接着探讨了加密硬盘、分区、目录和共享容器的各种方法。然后我们研究了如何使用 OpenSSL 创建密钥、证书签名请求(CSR)和证书。但由于我们并不总是希望使用自签名证书,而商业证书也不是每次都需要,我们还探讨了如何使用 Dogtag 设置私有 CA。之后,我们介绍了在 Apache web 服务器上硬化 TLS 配置的简单方法,并触及了相互认证的话题。最后,我们介绍了量子抗性加密算法。

在过程中,我们进行了许多动手实验。这很不错,因为毕竟,闲置的手是魔鬼的工坊,我们可不想发生这种事情。

在下一章,我们将讨论如何加固安全外壳(Secure Shell)。我们在那里见。

问题

  1. 以下哪个不是 GPG 的优点?

    1. 它使用强大且难以破解的算法。

    2. 它适用于与你不认识的人共享秘密。

    3. 它的公钥/私钥方案消除了共享密码的需求。

    4. 你可以用它加密那些你不打算分享的文件,仅供个人使用。

  2. 你需要向 Frank 发送加密消息。在你用 GPG 加密他的消息之前,必须做什么才能避免共享密码?

    1. 无。只需使用你自己的私钥加密消息。

    2. 将 Frank 的私钥导入你的密钥环中,并将你的私钥发送给 Frank。

    3. 将 Frank 的公钥导入你的密钥环中,并将你的公钥发送给 Frank。

    4. 只需将 Frank 的公钥导入你的密钥环中。

    5. 只需将 Frank 的私钥导入你的密钥环中。

  3. 以下哪种选项是 Linux 系统上全盘加密的正确选择?

    1. Bitlocker

    2. VeraCrypt

    3. eCryptfs

    4. LUKS

  4. 如果你使用 eCryptfs 加密用户的主目录,并且没有使用全盘加密,那么为了防止敏感数据泄漏,你还需要采取什么措施?

    1. 无。

    2. 确保用户使用强私钥。

    3. 加密交换分区。

    4. 你必须在全盘模式下使用 eCryptfs。

  5. 在以下哪种情况下你会使用 VeraCrypt?

    1. 每当你想实施全盘加密时。

    2. 每当你只想加密用户的主目录时。

    3. 每当你更倾向于使用专有的、闭源的加密系统时。

    4. 每当你需要创建可以与 Windows、macOS 和 BSD 用户共享的加密容器时。

  6. 你需要确保你的浏览器信任来自 Dogtag CA 的证书。你该怎么做?

    1. 你使用pki-server导出 CA 证书和密钥,然后使用openssl pkcs12提取证书。然后,将证书导入浏览器中。

    2. 你将ca_admin.cert证书导入浏览器中。

    3. 你将ca_admin_cert.p12证书导入浏览器中。

    4. 你将snakeoil.pem证书导入浏览器中。

进一步阅读

关于 TLS 和 OpenSSL 的解释:

EV 证书的问题:

免费 Let's Encrypt 证书的问题:

Dogtag CA:

RHEL 9/AlmaLinux 9:

FIPS

量子抗性加密

答案

  1. B

  2. C

  3. D

  4. C

  5. D

  6. A

第八章:7 SSH 强化

加入我们的书籍社区,和我们一起在 Discord 上讨论

packt.link/SecNet

安全外壳SSH)套件是 Linux 管理员必备的工具之一。它使你能够在自己的办公桌前,甚至在家中,轻松管理 Linux 服务器。无论怎样,总比穿上大衣、绕过安保检查进入冰冷的服务器房间要好得多。安全外壳中的“安全”意味着你输入或传输的所有内容都会被加密,这就消除了通过插入嗅探器到你的网络中获取敏感数据的可能性。

到目前为止,在你的 Linux 生涯中,你应该已经知道如何使用安全外壳(SSH)进行远程登录和远程文件传输。你可能不知道的是,SSH 的默认配置其实是相当不安全的。在本章中,我们将探讨如何通过各种方式强化默认配置。我们将讨论如何使用比默认更强的加密算法,如何设置无密码认证,以及如何为安全文件传输协议(SFTP)的用户设置一个“监狱”。作为额外内容,我们还将探讨如何扫描 SSH 服务器,查找易受攻击的配置,以及如何通过 安全外壳文件系统SSHFS)共享远程目录。

在本章中,我们将覆盖以下主题:

  • 确保禁用 SSH 协议 1

  • 创建和管理无密码登录的密钥

  • 禁用 root 用户登录

  • 禁用用户名/密码登录

  • 启用双因素认证

  • 配置带有强加密算法的安全外壳

  • 在 RHEL 8/9 类型的系统上设置系统范围的加密策略

  • RHEL 8/9 类型系统上的 FIPS 模式

  • 配置更详细的日志记录

  • 使用白名单和 TCP Wrappers 进行访问控制

  • 配置自动注销和安全横幅

  • 其他杂项安全设置

  • 为不同的主机设置不同的配置

  • 为不同的用户和组设置不同的配置

  • 扫描 SSH 服务器

  • 为 SFTP 用户设置 chroot 环境

  • 使用 SSHFS 设置共享目录

  • 从 Windows 桌面远程连接

所以,如果你准备好了,让我们开始吧。

确保禁用 SSH 协议 1

在这本书的前两版中,我曾告诉过你 SSH 协议版本 1 存在严重缺陷,应该始终确保它在 /etc/ssh/sshd_config 文件中没有启用。如今,你不需要再担心这个问题,因为 SSH 协议 1 已经消失,成了过去的事。所以,庆祝一下吧!

创建和管理无密码登录的密钥

Secure Shell 套件是一个非常棒的远程服务器通信工具集。你可以使用ssh组件远程登录到远程机器的命令行界面,也可以使用scpsftp来安全地传输文件。使用这些 SSH 组件的默认方式是使用一个人正常的 Linux 用户帐户的用户名。所以,从我的 OpenSUSE 工作站的终端登录到远程机器会是这样的:

donnie@linux-0ro8:~> ssh donnie@192.168.0.8
donnie@192.168.0.8's password:

虽然确实用户名和密码是以加密格式通过网络传输,使得恶意行为者很难拦截,但这仍然不是最安全的做法。问题在于,攻击者可以使用自动化工具对 SSH 服务器执行暴力破解密码攻击。僵尸网络,如 Hail Mary Cloud,会在互联网上进行持续扫描,寻找启用了 SSH 的互联网-facing 服务器。

如果一个僵尸网络发现服务器允许通过用户名和密码进行 SSH 访问,它将发起暴力破解密码攻击。不幸的是,这种攻击已经成功过很多次,尤其是在服务器管理员允许 root 用户通过 SSH 登录时。

这篇较旧的文章提供了更多关于 Hail Mary Cloud 僵尸网络的细节:futurismic.com/2009/11/16/the-hail-mary-cloud-slow-but-steady-brute-force-password-guessing-botnet/

在接下来的部分,我们将讨论两种方法来帮助防止这些类型的攻击:

  • 通过公钥交换启用 SSH 登录

  • 禁用 root 用户通过 SSH 登录

现在,让我们创建一些密钥。

创建用户的 SSH 密钥集

每个用户都有能力创建自己的私钥和公钥集。无论用户的客户端机器运行的是 Linux、macOS、Windows 上的 Cygwin,还是 Windows 上的 Bash Shell,都不影响。所有情况下,步骤都是完全一样的。

你可以创建几种不同类型的密钥,3072 位 RSA 密钥通常是默认选项。直到最近,2048 位的 RSA 密钥被认为在可预见的未来足够强大。但现在,美国国家标准与技术研究院NIST)的最新指导建议使用至少 3072 位的 RSA 密钥或至少 384 位的 椭圆曲线数字签名算法ECDSA)密钥。(你有时会看到这些 ECDSA 密钥被称为 P-384。)他们的理由是,想要让我们为量子计算做准备,因为量子计算将变得如此强大,以至于会使任何较弱的加密算法变得过时。当然,量子计算目前还不实际可行,而且到目前为止,它似乎总是被预测为“十年后”的事情,不管现在是什么年份。但即使我们忽略量子问题,我们依然必须承认,即使是我们现在的非量子计算机,也在不断变得更强大。因此,采用更强的加密标准还是一个不错的选择。

要查看 NIST 推荐的加密算法和推荐的密钥长度,请访问 cryptome.org/2016/01/CNSA-Suite-and-Quantum-Computing-FAQ.pdf

接下来的几个演示,我们切换到 Ubuntu 22.04 客户端。要创建一个 3072 位的 RSA 密钥对,只需执行以下操作:

donnie@ubuntu2204-packt:~$ ssh-keygen

我们不需要使用任何选项切换,因为命令默认会创建一个 3072 位的 RSA 密钥对。当提示输入密钥的存储位置和名称时,我只需按下 Enter 键接受默认设置。你也可以将私钥设置为空白密码,但这并不是推荐的做法。

请注意,如果你为密钥文件选择了不同的名称,你需要输入完整的路径才能正常工作。例如,在我的情况下,我可以指定 donnie_rsa 密钥的路径为 /home/donnie/.ssh/donnie_rsa

你将在 .ssh 目录中看到你的新密钥:

donnie@ubuntu2204-packt:~$ ls -l .ssh
total 16
-rw------- 1 donnie donnie    0 Oct  6 22:09 authorized_keys
-rw------- 1 donnie donnie 2655 Nov  1 19:49 id_rsa
-rw-r--r-- 1 donnie donnie  577 Nov  1 19:49 id_rsa.pub
-rw------- 1 donnie donnie  978 Oct 26 20:41 known_hosts
-rw-r--r-- 1 donnie donnie  142 Oct 26 20:41 known_hosts.old
donnie@ubuntu2204-packt:~$

id_rsa 密钥是私钥,仅对我具有读写权限。id_rsa.pub 公钥必须是全局可读的。对于 ECDSA 密钥,默认长度为 256 位。如果你选择使用 ECDSA 而非 RSA,可以按照以下步骤创建一个强度为 384 位的密钥:

donnie@ubuntu2204-packt:~$ ssh-keygen -t ecdsa -b 384

无论哪种方式,当你查看 .ssh 目录时,会发现 ECDSA 密钥的命名方式与 RSA 密钥不同:

donnie@ubuntu2204-packt:~$ ls -l .ssh/id*
-rw------- 1 donnie donnie  667 Nov  1 19:55 .ssh/id_ecdsa
-rw-r--r-- 1 donnie donnie  229 Nov  1 19:55 .ssh/id_ecdsa.pub
-rw------- 1 donnie donnie 2655 Nov  1 19:49 .ssh/id_rsa
-rw-r--r-- 1 donnie donnie  577 Nov  1 19:49 .ssh/id_rsa.pub
donnie@ubuntu2204-packt:~$

椭圆曲线算法的美妙之处在于,它们看似较短的密钥长度,与 RSA 长密钥的安全性相当。而且,即使是最大的 ECDSA 密钥,也比 RSA 密钥消耗更少的计算能力。你能使用的 ECDSA 最大密钥长度为 521 位。(是的,你没看错,是 521 位,不是 524 位。)那么,你可能会想,为什么我们不直接使用 521 位密钥呢? 其实,主要原因是 521 位密钥不被 NIST 推荐。有些人担心它们可能会受到填充攻击,这可能会让坏人破解加密并窃取你的数据。

如果你查看 ssh-keygen 的手册页,你会看到你也可以创建一种 Ed25519 类型的密钥,这种密钥有时会被称为 curve25519。这种密钥没有被列入 NIST 推荐的算法列表,也不被 FIPS 法规允许,但仍然有一些人喜欢使用它。

如果操作系统的随机数生成器存在缺陷,RSA 和 DSA 在创建签名时可能会泄露私钥数据。而 Ed25519 在创建签名时不需要随机数生成器,因此不会受到这个问题的影响。此外,Ed25519 的编码方式使其在面对侧信道攻击时更为安全。(侧信道攻击是指攻击者试图利用操作系统中的漏洞,而非加密算法本身的漏洞。)

一些人喜欢 Ed25519 的第二个原因恰恰是因为它在 NIST 的推荐列表上。这些人或多或少不信任政府机构的推荐,可能有他们的理由。

很多年前,在本世纪初,曾经有一起丑闻涉及双椭圆曲线确定性随机比特生成器Dual_EC_DRBG)。这是一个用于椭圆曲线密码学的随机数生成器。问题在于,早期一些独立研究者发现,它有能力被任何了解该能力的人插入后门。恰好,唯一被允许了解这种能力的人是美国国家安全局NSA)的工作人员。在 NSA 的坚持下,NIST 将 Dual_EC_DRBG 列入了推荐算法列表,并且一直保留到 2014 年 4 月才被移除。你可以通过以下链接了解更多细节:

www.pcworld.com/article/2454380/overreliance-on-the-nsa-led-to-weak-crypto-standard-nist-advisers-find.html

www.math.columbia.edu/~woit/wordpress/?p=7045

你可以在这里阅读有关 Ed25519 的详细信息:ed25519.cr.yp.to/

Ed25519只有一种密钥大小,那就是 256 位。所以,要创建一个curve25519密钥,只需这样做:

donnie@ubuntu2204-packt:~$ ssh-keygen -t ed25519

这是我创建的密钥:

donnie@ubuntu2204-packt:~$ ls -l .ssh/*25519*
-rw------- 1 donnie donnie 464 Nov  1 20:35 .ssh/id_ed25519
-rw-r--r-- 1 donnie donnie 105 Nov  1 20:35 .ssh/id_ed25519.pub
donnie@ubuntu2204-packt:~$

然而,ed25519也有一些潜在的缺点:

  • 首先,它不被旧版 SSH 客户端支持。然而,如果你团队中的每个人都在使用当前操作系统并且使用最新的 SSH 客户端,这应该不是问题。

  • 第二点是,它只支持一种特定的密钥长度,这相当于 256 位椭圆曲线算法或 3000 位 RSA 算法。因此,它可能不像我们之前讲解的其他算法那样具有未来适应性。

  • 最后,如果你的组织需要遵循 NIST 的推荐或 FIPS 的要求,你就不能使用它。

好的,还有一种密钥类型我们没有讲过。那就是传统的 DSA 密钥,如果你告诉ssh-keygen,它仍然会创建这种密钥。但不要这么做。DSA 算法已经老旧、笨拙,并且按照现代标准非常不安全。因此,面对 DSA,直接说

将公钥传输到远程服务器

将我的公钥传输到远程服务器,使得服务器可以轻松识别我和我的客户端机器。在我将公钥传输到远程服务器之前,我需要将私钥添加到我的会话密钥环中。这需要两条命令。(一条命令启动ssh-agent,另一条命令则将私钥实际添加到密钥环中):

donnie@ubuntu2204-packt:~$ exec /usr/bin/ssh-agent $SHELL
donnie@ubuntu2204-packt:~$ ssh-add
Enter passphrase for /home/donnie/.ssh/id_rsa: 
Identity added: /home/donnie/.ssh/id_rsa (donnie@ubuntu2204-packt)
Identity added: /home/donnie/.ssh/id_ecdsa (donnie@ubuntu2204-packt)
Identity added: /home/donnie/.ssh/id_ed25519 (donnie@ubuntu2204-packt)
donnie@ubuntu2204-packt:~$

最后,我可以将我的公钥传输到我的 AlmaLinux 9 服务器,其地址为192.168.0.17

donnie@ubuntu2204-packt:~$ ssh-copy-id donnie@192.168.0.17
The authenticity of host '192.168.0.17 (192.168.0.17)' can't be established.
ED25519 key fingerprint is SHA256:GkpFwJdpWRQ5GawgFEz9bgDSny//E1I5aLGkjU9DWWY.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 3 key(s) remain to be installed -- if you are prompted now it is to install the new keys
donnie@192.168.0.17's password: 
Number of key(s) added: 3
Now try logging into the machine, with:   "ssh 'donnie@192.168.0.17'"
and check to make sure that only the key(s) you wanted were added.
donnie@ubuntu2204-packt:~$

通常,你只会为你选择的任何类型创建一对密钥。正如你在这里看到的,我已经创建了三对密钥,每种类型一对。所有三个私钥都被添加到了我的会话密钥环中,所有三个公钥也都被传输了到远程服务器。

下次我登录时,我将使用密钥交换,不需要输入密码:

donnie@ubuntu2204-packt:~$ ssh donnie@192.168.0.17
Last login: Tue Nov  1 16:52:27 2022
[donnie@donnie-ca ~]$ 

如我之前提到的,通常你会为每台机器只创建一对密钥。然而,也有例外。有些管理员更喜欢为他们管理的每个服务器使用不同的密钥对,而不是为所有服务器使用相同的密钥对。一个方便的做法是,创建与各自服务器主机名匹配的密钥文件名。然后,你可以使用-i选项来指定你想使用的密钥对。

在这个示例中,我只有一个服务器,但我为它准备了多个密钥。假设我更喜欢使用Ed25519密钥:

donnie@ubuntu2204-packt:~$ ssh -i ~/.ssh/id_ed25519 donnie@192.168.0.17
Last login: Tue Nov  1 16:56:43 2022 from 192.168.0.14
[donnie@donnie-ca ~]$ 

所以,现在你可能在想,如果我可以不输入密码就登录,那怎么保证安全? 答案是,一旦你关闭了用于登录的客户端机器的终端窗口,私钥就会从你的会话密钥环中移除。当你打开一个新的终端并尝试登录远程服务器时,你会看到这个:

donnie@ubuntu2204-packt:~$ ssh donnie@192.168.0.17
Enter passphrase for key '/home/donnie/.ssh/id_rsa': 
Last login: Tue Nov  1 16:58:22 2022 from 192.168.0.14
[donnie@donnie-ca ~]$

现在,每次我登录到这个服务器时,都需要输入私钥的密码短语,直到我按照上一节中展示的两个命令将其重新添加到会话密钥环中。

实验操作 – 创建和传输 SSH 密钥

在这个实验中,你将使用一台虚拟机(VM)作为客户端,另一台虚拟机作为服务器。或者,如果你使用的是 Windows 主机,你可以使用 Cygwin、PowerShell 或内置的 Windows Bash shell 作为客户端。(不过需要注意,PowerShell 和 Windows Bash shell 会将密钥文件存储在不同的位置。)如果你使用的是 Mac 或 Linux 主机,你可以使用主机自带的命令行终端作为客户端。无论哪种情况,过程都是一样的。

对于服务器虚拟机(VM),使用 Ubuntu 22.04 或 CentOS 7。

这个过程在 AlmaLinux 8 和 9 上同样适用。然而,我们将在接下来的实验中继续使用这台虚拟机,而 AlmaLinux 有一些特殊的考虑事项,我们稍后会进一步了解。

让我们开始吧:

  1. 在客户端机器上,创建一对 384 位的椭圆曲线密钥。接受默认的文件名和位置,并创建一个密码短语:
ssh-keygen -t ecdsa -b 384
  1. 查看密钥,注意权限设置:
ls -l ./ssh
  1. 将你的私钥添加到会话密钥环中。提示时,输入你的密码短语:
exec /usr/bin/ssh-agent $SHELL
ssh-add
  1. 将公钥传输到服务器虚拟机。当提示时,输入你在服务器虚拟机上的用户账户密码。(在以下命令中替换你自己的用户名和 IP 地址。)
ssh-copy-id donnie@192.168.0.7
  1. 按照通常的方式登录到服务器虚拟机:
ssh donnie@192.168.0.7
  1. 查看在服务器虚拟机上创建的 authorized_keys 文件:
ls -l .ssh
cat .ssh/authorized_keys
  1. 从服务器虚拟机登出,并关闭客户端的终端窗口。打开另一个终端窗口,尝试重新登录到服务器。这次,你应该会被提示输入私钥的密码短语。

  2. 重新从服务器虚拟机登出,并将你的私钥重新添加到客户端的会话密钥环中。提示时,输入私钥的密码短语:

exec /usr/bin/ssh-agent $SHELL
ssh-add

只要你保持客户端的这个终端窗口打开,你就能随时登录到服务器虚拟机,而无需输入密码。然而,当你关闭终端窗口时,你的私钥会被从会话密钥环中移除。

  1. 保留你的服务器虚拟机,因为稍后我们还会用到它。

你已完成实验 – 恭喜!

我们到目前为止所做的很好,但还是不完全够。一个问题是,如果你换到另一台客户端机器,你仍然可以使用正常的用户名/密码认证进行登录。没关系,我们稍后会解决这个问题。

禁用 root 用户登录

几年前,有一个相对著名的案例,恶意行为者成功地在东南亚某些地区的多个 Linux 服务器上植入了恶意软件。坏人能这么轻松地做到这一点,主要有三个原因:

  • 涉及的面向互联网的服务器设置为使用用户名/密码认证进行 SSH 登录。

  • 允许 root 用户通过 SSH 登录。

  • 用户密码,包括 root 用户的密码,异常弱。

这一切意味着,Hail Mary 僵尸网络轻松通过暴力破解进入。

不同的发行版对 root 用户登录有不同的默认设置。在你 CentOS 7 或 AlmaLinux 8 机器的/etc/ssh/sshd_config文件中,你会看到这一行:

#PermitRootLogin yes

与大多数配置文件不同,sshd_config中的注释行定义了安全外壳守护进程的默认设置。因此,这一行表示允许 root 用户通过 SSH 登录。要更改这一点,我将去掉注释符号并将设置更改为no

PermitRootLogin no

为了使新的设置生效,我将重新加载 SSH 守护进程,在 CentOS 和 AlmaLinux 上该进程名为sshd,而在 Ubuntu 上是ssh

sudo systemctl reload sshd

在 Ubuntu 机器上,默认设置看起来略有不同:

PermitRootLogin prohibit-password

这意味着 root 用户被允许登录,但仅通过公钥交换。如果你确实需要允许 root 用户登录,这可能足够安全。但是在大多数情况下,你会希望强制管理员用户使用他们的正常用户帐户登录,并使用sudo进行管理员操作。因此,在大多数情况下,你仍然希望将此设置更改为no

在你的 AlmaLinux 9 机器上,你会看到其PermitRootLogin默认设置为prohibit-password

请注意,如果你在云服务(如 Rackspace 或 Vultr)上部署 Linux 实例,服务提供商会让你使用 root 用户帐户登录虚拟机。你需要做的第一件事是创建自己的普通用户帐户,使用该帐户重新登录,禁用 root 用户帐户,并在sshd_config中禁用 root 用户登录。Microsoft Azure 是这个规则的一个例外,因为它会为你自动创建一个非特权用户帐户。

你将在接下来的几分钟内练习这个操作。

禁用用户名/密码登录

这是你只有在与客户端设置了密钥交换之后才应该做的事情。否则,客户端将无法进行远程登录。

实践实验 - 禁用 root 登录和密码验证

对于这个实验,使用上一个实验中用过的相同服务器虚拟机。我们开始吧:

  1. 在 Ubuntu、CentOS 或 AlmaLinux 8 服务器虚拟机上,查找sshd_config文件中的这一行:
#PasswordAuthentication yes
  1. 移除注释符号,将参数值更改为no,并重新加载 SSH 守护进程。现在该行应该像这样:
PasswordAuthentication no

现在,当僵尸网络扫描你的系统时,它们会发现暴力破解密码攻击毫无用处。然后它们就会离开,不再打扰你。

  1. 查找以下两行中的一行,具体取决于服务器是 Ubuntu 还是 CentOS 7/AlmaLinux 8 虚拟机:
#PermitRootLogin yes
#PermitRootLogin prohibit-password
  1. 取消注释这一行,并将其更改为以下内容:
PermitRootLogin no
  1. 重新加载 SSH 守护进程,使其读取新的更改。在 Ubuntu 上,操作如下:
sudo systemctl reload ssh

在 CentOS/AlmaLinux 上,操作如下:

sudo systemctl reload sshd
  1. 尝试从你在上一个实验中使用的客户端登录服务器虚拟机。

  2. 尝试从你没有创建密钥对的另一个客户端登录服务器虚拟机。(你应该无法登录。)

  3. 和之前一样,保留服务器虚拟机,因为稍后我们会继续使用它。

你已完成实验——恭喜!

现在我们已经讲解了如何在客户端创建公私钥对以及如何将公钥传输到服务器,接下来讲讲如何设置双重身份验证。

启用双重身份验证

双重身份验证可以提供额外的保护层。如果你拥有智能手机,可以通过Google Authenticator来设置,它会为你提供一个一次性密码,用于在本地终端登录、执行sudo命令或通过 SSH 远程登录。在开始之前,然而,有几点注意事项你需要理解:

  • 要在 Linux 机器上使其工作,你需要安装一个 Google 未提供的 PAM 模块。它在一些 Linux 发行版的仓库中可以找到,但并非所有发行版都包含。(当然,你也可以从 GitHub 仓库下载源代码并自行编译,但这超出了本书的范围。)

  • 这个 PAM 模块的创建者写了一些文档,但并不太有用。如果你搜索文档,你会发现一些博客文章,其中的步骤比没有文档更糟糕,因为如果你照做,它们会破坏你的系统。

  • 你可以将机器设置为要求 Google 身份验证用于全局使用,或者仅仅用于通过 SSH 登录。(全局使用意味着在本地终端登录、使用sudo以及通过 SSH 远程登录时都需要输入 Authenticator 代码。)

  • 如果你正在处理多个用户,每个用户都需要为自己的账户设置 Google Authenticator,并使用自己的智能手机。

现在,我们已经解决了这个问题,接下来让我们为 Ubuntu 22.04 服务器设置 Google Authenticator,以用于本地登录和sudo命令:

请注意,这个 PAM 模块在普通的 Ubuntu 仓库中有,而在 RHEL 8 类型的发行版的 EPEL 仓库中也有。它在 RHEL 9 类型的发行版中完全不可用。

实验操作——在 Ubuntu 22.04 上设置双重身份验证

对于本实验,从一个全新的 Ubuntu 22.04 虚拟机开始,该虚拟机未设置公钥认证。(这样可以避免在执行此过程时出现很多困惑。)

  1. 在你的智能手机上安装 Google Authenticator。(它在 Android 和 iPhone 的常规应用商店中都有提供。)

  2. 在你的 Ubuntu 虚拟机上,安装libpam-google-authenticator包,如下所示:

sudo apt install libpam-google-authenticator
  1. 对于此步骤,如果你尚未操作,请使用 SSH 通过主机机器的 GUI 类型终端远程登录 Ubuntu 虚拟机。(这是因为你可能需要调整窗口大小以使下一步操作生效。)现在,从这个 GUI 类型终端,运行google-authenticator应用,操作如下:
google-authenticator
  1. 屏幕上会显示一个大的二维码。如果整个二维码图形没有显示出来,使用图形界面终端的控制按钮进行缩小,直到整个图形可见为止。

  2. 打开你智能手机上的 Google Authenticator 应用,并点击屏幕右下角的 + 符号。选择 扫描二维码 选项,然后拍摄你的二维码图片。

  3. 在你的智能手机上,注意到 Ubuntu 虚拟机的新条目已经添加到列表中。在 Ubuntu 虚拟机上,输入与该条目一起显示的验证码。

  4. 接下来,你会在 Ubuntu 终端上看到紧急的备用验证码。把它们抄下来并保存在安全的地方。(如果你丢失了手机,你将使用这些备用验证码来登录。)

  5. 接下来,你会被问到一系列问题。对于所有问题,输入 y

  6. 在此步骤中,你将为本地终端登录和 sudo 操作设置双重身份验证。用你喜欢的文本编辑器打开 /etc/pam.d/common-auth 文件。将 auth required pam_google_authenticator.so 行作为第一个参数添加进去。文件的顶部部分现在应该是这样的:

#
# /etc/pam.d/common-auth - authentication settings common to all services
#
# This file is included from other service-specific PAM config files,
. . .
. . .
auth required pam_google_authenticator.so
# here are the per-package modules (the "Primary" block)
. . .
. . .

某些博客文章告诉你将这一行添加到文件的末尾。请注意,如果你这么做,你会被锁定在机器外面,届时需要执行紧急程序才能重新进入并修复它。每次编辑 PAM 文件时,确保按照正确的顺序放置指令是至关重要的。(如果你想知道,我将在第十六章忙碌小蜜蜂的安全技巧与窍门 中展示这个紧急程序。)

  1. 在 Ubuntu 虚拟机的本地终端上,登出然后重新登录。当提示时,输入来自智能手机应用的验证码。

  2. 执行一个需要 sudo 权限的命令。你应该看到类似这样的提示:

donnie@ubuntu2204-packt:~$ sudo nft list ruleset
Verification code: 
[sudo] password for donnie:
. . .
. . .
Enter the verification code at the prompt.

请注意,直到 sudo 定时器超时之前,你不会再需要输入验证码。

  1. 从你的主机或其他虚拟机,通过 SSH 远程登录到 Ubuntu 虚拟机。你仍然能够这样做,因为我们还没有配置 /etc/ssh/sshd_config 文件。用文本编辑器打开 sshd_config 文件,将 KbdInteractiveAuthentication no 行改为 KbdInteractiveAuthentication yes

  2. 重新加载安全外壳配置:

sudo systemctl reload ssh
  1. 再次尝试从你的主机或其他虚拟机登录。这时,你应该会被提示输入你的验证码。

  2. 现在,假设你的组织需要为远程 SSH 登录设置双重身份验证,但不需要为本地登录或 sudo 操作设置双重身份验证。让我们修改配置,使得只有远程用户需要输入验证码。用文本编辑器打开 /etc/pam.d/common-auth 文件,移除在第 9 步中插入的那一行。

  3. 用文本编辑器打开/etc/pam.d/sshd文件,并在文件顶部的@include common-auth行下方添加该行。文件的顶部部分现在应该如下所示:

PAM configuration for the Secure Shell service
      # Standard Un*x authentication.
     @include common-auth
     auth required pam_google_authenticator.so

现在,你应该可以登录到本地终端并执行sudo操作,而无需输入验证码。相反,你只需要在远程登录时输入验证码。

  1. 实验结束。

接下来,让我们看一下如何在我们的 Ubuntu 机器上使用 Google Authenticator 配合密钥交换。

实操实验--在 Ubuntu 上使用 Google Authenticator 配合密钥交换

对于本实验,请使用与上一个实验相同的 Ubuntu 虚拟机。

  1. 在主机或另一台虚拟机上,创建一对密钥并将其传输到 Ubuntu 虚拟机,就像在创建和传输 SSH 密钥实验中那样。这次,在执行ssh-copy-id命令时,系统应该会提示你输入验证码。

  2. 在 Ubuntu 虚拟机上,用文本编辑器打开/etc/ssh/sshd_config文件。这次,不是修改#PasswordAuthentication yes这一行,而是在KbdInteractiveAuthentication yes这一行下方添加以下这一行:

AuthenticationMethods publickey keyboard-interactive:pam

重新加载 SSH 配置后,你会发现,如果你从已设置密钥交换的机器登录,就可以通过密钥交换进行远程登录。如果从没有设置密钥交换的机器登录,你仍然可以使用密码和验证码进行登录。所以,目前我们还没有实现真正的双因素认证。

  1. 要要求同时使用密钥认证和 Google Authenticator 验证,请将上述行更改为如下所示:
AuthenticationMethods publickey,keyboard-interactive:pam
  1. 重新加载 SSH 配置后,你只允许从已设置密钥交换的机器登录。现在你实际上拥有了三因素认证,因为你仍然会被要求输入常规登录密码。

  2. 要禁用密码登录,以便只使用密钥交换和验证码,请用文本编辑器打开/etc/pam.d/sshd文件。在文件的最顶部,找到@include common-auth行并将其更改为#@include common-auth

  3. 通过尝试从没有进行密钥交换设置的虚拟机登录,来验证密钥交换是否有效。(你应该无法登录。)

  4. 就这些。实验结束。

现在,让我们看看我们能在 AlmaLinux 8 上做些什么。

实操实验--在 AlmaLinux 8 上设置双因素认证

对于本实验,我假设你已经在智能手机上安装了 Google Authenticator 应用。

Authenticator PAM 模块不在 RHEL 9 发行版的任何软件库中,但它在 RHEL 8 发行版的 EPEL 软件库中。(在你阅读本文时,这可能会发生变化,因此,如果你想在 AlmaLinux 9 上尝试此操作,检查一下也无妨。)所以,启动一个新的 AlmaLinux 8 虚拟机,让我们开始吧。

  1. 以这种方式安装 PAM 模块:
sudo dnf install epel-release
sudo dnf install google-authenticator qrencode-libs

请注意,您需要 qrencode-libs 包才能生成 QR 码。

  1. 从主机机器的 GUI 终端,使用 SSH 远程登录到 AlmaLinux 8 VM。这将允许您调整 QR 码图像的大小,以便您可以用智能手机拍照。然后,像这样运行 google-authenticator 应用:
google-authenticator -s ~/.ssh/google_authenticator
  1. 这次,我们在 .ssh 目录内创建 google_authenticator 文件,因为 AlmaLinux 已设置为使用 SELinux。当您启用 Authenticator 尝试远程登录时,SSH 守护程序将尝试写入 google_authenticator 文件。SELinux 阻止 SSH 写入 .ssh 目录之外的文件。(我们将在《第十章》《使用 SELinux 和 AppArmor 实施强制访问控制》中详细讨论 SELinux。)

  2. 跟随 Authenticator 设置,与 在 Ubuntu 22.04 上设置双因素身份验证 实验中的第 4 至 8 步骤相同。

  3. 在您的文本编辑器中打开 /etc/pam.d/sshd 文件。在文件的末尾添加此行:

auth required pam_google_authenticator.so secret=/home/${USER}/.ssh/google_authenticator

(请注意,打印页面上的行会换行。)

  1. 在您的文本编辑器中打开 /etc/ssh/sshd_config 文件。找到 #ChallengeResponseAuthentication no 行,并将其更改为 ChallengeResponseAuthentication yes

  2. 重新加载或重启 sshd 服务:

sudo systemctl reload sshd
  1. 设置您创建的 google_authenticator 文件的适当 SELinux 安全上下文:
cd
sudo restorecon .ssh/google_authenticator
  1. 退出远程会话,然后尝试重新登录。这次,系统会提示您输入验证码。

接下来,让我们为 AlmaLinux 设置密钥交换。

实验室实践--在 AlmaLinux 8 上使用密钥交换与 Google Authenticator

这与 Ubuntu 的设置基本相同,只有少许差异。

  1. 将公钥从主机机器传输到 AlmaLinux 8 机器,方法与 创建和传输 SSH 密钥 实验中的相同。

  2. /etc/ssh/sshd_config 文件中,将 #PasswordAuthentication yes 行更改为 PasswordAuthentication no,然后重新加载 SSH 配置。现在,您将只使用密钥交换登录,完全绕过 Authenticator。让我们修复这些问题,以便您同时使用两者。

  3. /etc/ssh/sshd_config 文件中,在 PasswordAuthentication no 行的下面添加以下行:

AuthenticationMethods publickey,password publickey,keyboard-interactive
  1. 重新加载 SSH 配置后,您将具有三因素身份验证,因为您将需要输入密码和验证代码以及密钥交换。

  2. 如果需要,您可以轻松禁用密码提示,仅使用密钥交换和验证代码。在 /etc/pam.d/sshd 文件中,找到文件顶部的 auth substack password-auth 行,并将其更改为 #auth substack password-auth

  3. 这就是 Google Authenticator 的全部内容。

在下一节中,让我们确保仅使用最强的加密算法。

配置使用强加密算法的安全 Shell

正如我之前提到的,当前的 NIST 推荐标准,商业国家安全算法套件CNSA 套件),涉及使用比以前需要使用的更强的算法和更长的密钥。我将在这个表格中总结新的推荐内容:

算法 用途
RSA,3,072 位或更大 密钥协商和数字签名
Diffie-Hellman(DH),3,072 位或更大 密钥协商
ECDH 与 NIST P-384 密钥协商
ECDSA 与 NIST P-384 数字签名
SHA-384 完整性
AES-256 保密性

在其他出版物中,您可能会看到 NIST Suite B 是推荐的加密算法标准。Suite B 是一个较旧的标准,已被 CNSA Suite 取代。

另一个您可能需要处理的加密标准是美国政府发布的联邦信息处理标准FIPS)。当前版本是 FIPS 140-3,于 2019 年 9 月 22 日获得最终批准。

理解 SSH 加密算法

SSH 与对称和非对称加密的组合工作方式类似于传输层安全性工作方式。SSH 客户端通过使用公钥方法开始此过程,以与 SSH 服务器建立非对称会话。一旦建立了此会话,两台机器可以同意并交换一个秘密代码,用于建立对称会话。(与 TLS 之前看到的情况类似,出于性能原因,我们希望使用对称加密,但我们需要使用非对称会话执行秘密密钥交换。)为了执行这种魔术,我们需要四类加密算法,这些算法将在服务器端进行配置。它们是:

  • Ciphers:这些是对称算法,用于加密客户端和服务器彼此交换的数据。

  • HostKeyAlgorithms:这是服务器可以使用的主机密钥类型列表。

  • KexAlgorithms:这些是服务器可以使用的算法,用于执行对称密钥交换。

  • MAC:消息认证码是加密数据传输中的哈希算法,用于加密数据的签名。这确保了数据的完整性,并告诉您是否有人篡改了您的数据。

最佳的了解方法是查看sshd_config手册页面,像这样:

man sshd_conf

我可以使用任何虚拟机来演示这一点。不过,现在我选择 CentOS 7,除非另有说明。(不同的 Linux 发行版和版本的默认和可用算法列表将不同。)另外,请注意,为了演示这一点,我们要查看sshd_config手册页面以查看可用已启用算法的列表。CentOS 7 和 AlmaLinux 8 的手册页面中包含已启用列表,但 AlmaLinux 9 的手册页面中没有。

首先,让我们看一下支持的密码列表。滚动到手册页面的底部,直到找到它们:

3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
arcfour
arcfour128
arcfour256
blowfish-cbc
cast128-cbc
chacha20-poly1305@openssh.com

然而,并非所有这些支持的密码算法都已启用。在该列表下方,我们可以看到默认启用的密码算法列表:

chacha20-poly1305@openssh.com,
aes128-ctr,aes192-ctr,aes256-ctr,
aes128-gcm@openssh.com,aes256-gcm@openssh.com,
aes128-cbc,aes192-cbc,aes256-cbc,
blowfish-cbc,cast128-cbc,3des-cbc

接下来,按字母顺序排列的是 HostKeyAlgorithms。在 CentOS 7 上的列表如下所示:

ecdsa-sha2-nistp256-cert-v01@openssh.com,
ecdsa-sha2-nistp384-cert-v01@openssh.com,
ecdsa-sha2-nistp521-cert-v01@openssh.com,
ssh-ed25519-cert-v01@openssh.com,
ssh-rsa-cert-v01@openssh.com,
ssh-dss-cert-v01@openssh.com,
ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,
ssh-ed25519,ssh-rsa,ssh-dss

接下来,向下滚动到 KexAlgorithms(即密钥交换算法)部分。你将看到一个支持的算法列表,类似于以下内容:

curve25519-sha256
curve25519-sha256@libssh.org
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521

请注意,这个列表可能会在不同的发行版之间有所不同。例如,RHEL 8/AlmaLinux 8 支持三种额外的更新且更强的算法。它的列表如下所示:

curve25519-sha256
curve25519-sha256@libssh.org
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521

在 AlmaLinux 9 机器上,你将看到相同的列表,只不过 sntrup761x25519-sha512@openssh.com 算法已被添加。

接下来,你将看到默认启用的算法列表:

curve25519-sha256,curve25519-sha256@libssh.org,
ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,
diffie-hellman-group-exchange-sha256,
diffie-hellman-group14-sha1,
diffie-hellman-group1-sha1

这个列表在不同的 Linux 发行版中也可能有所不同。(不过,在这种情况下,CentOS 7 和 AlmaLinux 8 之间没有差异。)

最后,我们有 MAC 算法。CentOS 7 上默认启用的算法列表如下所示:

umac-64-etm@openssh.com,umac-128-etm@openssh.com,
hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,
hmac-sha1-etm@openssh.com,
umac-64@openssh.com,umac-128@openssh.com,
hmac-sha2-256,hmac-sha2-512,hmac-sha1,
hmac-sha1-etm@openssh.com

要查看你的系统支持的算法列表,可以查看该机器的 sshd_config 手册页面,或执行以下 ssh -Q 命令:

ssh -Q cipher
ssh -Q key
ssh -Q kex
ssh -Q mac

当你查看 /etc/ssh/sshd_config 文件时,无法看到任何配置这些算法的行。这是因为默认的算法列表是硬编码到 SSH 守护进程中的。你唯一需要配置这些算法的情况是,如果你想启用一个未启用的算法或禁用一个已启用的算法。在此之前,让我们扫描一下系统,查看哪些算法已启用,并检查扫描工具是否能给出任何建议。

扫描已启用的 SSH 算法

我们有两种不错的方式来扫描 SSH 服务器。如果你的服务器可以通过互联网访问,你可以访问 SSHCheck 网站 sshcheck.com/

然后,只需输入服务器的 IP 地址或主机名。如果你已将端口更改为默认端口22以外的端口,也需要输入端口号。当扫描完成后,你将看到启用的算法列表,以及哪些算法需要启用或禁用的建议。

如果你想扫描的机器无法从互联网访问,或者即便可以访问,你也可以使用本地扫描工具。在本书的前一版中,我们使用了ssh_scan工具。遗憾的是,这个工具已经不再维护,并且在使用 OpenSSL 版本 3 的新版本 Linux 发行版中无法使用。因此,我们可以尝试使用 Nmap 脚本引擎来进行扫描。

动手实验 – 使用 Nmap 扫描

对于本实验,你可以使用任何一台虚拟机。让我们开始吧:

  1. 首先,从你的常规发行版仓库安装 nmap 包。在 Ubuntu 中,执行以下命令:
sudo apt update
sudo apt install nmap

在 CentOS 7 中,按照以下步骤操作:

sudo yum install nmap

在 AlmaLinux 8 或 9 中,按照以下步骤操作:

sudo dnf install nmap
  1. 使用nmapssh2-enum-algos.nse脚本扫描你在之前实验中创建和配置的服务器虚拟机。将这里使用的 IP 地址替换为你自己的 IP 地址。请注意,即使你没有在扫描机上创建密钥对,扫描仍然可以在禁用了用户名/密码身份验证的机器上工作。(但当然,你将无法从扫描机上登录):
nmap --script=ssh2-enum-algos.nse 192.168.0.14

请注意,如果你正在扫描启用了防火墙的机器,可能会收到扫描被阻止的错误消息。如果发生这种情况,尝试添加-Pn选项,这样命令将变成这样:

nmap -Pn --script=ssh2-enum-algos.nse 192.168.0.14
  1. 重复扫描,但这次将输出保存到一个普通文本文件中,像这样:
nmap --script=ssh2-enum-algos.nse 192.168.0.14 -oN ubuntuscan.txt
  1. 在普通文本编辑器或分页器中打开文本文件。你将看到一个启用的算法的完整列表。将你的结果与适用于你的情况的标准进行比较,例如 NIST 的 CNSA 标准,以确保你启用或禁用了正确的算法。

  2. 在你的主机或带有桌面界面的虚拟机上,访问 Shodan 网站:www.shodan.io。在搜索框中输入ssh并查看出现的面向互联网的 SSH 服务器列表。点击不同的 IP 地址,直到找到一个没有在默认端口22上运行的 SSH 服务器。观察该设备启用的算法列表。

  3. 扫描设备时,使用-p选项扫描不同的端口,像这样:

nmap -p 2222 --script=ssh2-enum-algos.nse 192.168.0.14
  1. 请注意,除了在 Shodan 上看到的启用的算法列表之外,你现在还得到了一个弱算法列表,这些算法是该设备的所有者需要禁用的。

  2. 保持这个扫描器和服务器虚拟机的状态,因为在禁用某些算法之后我们还会再次使用它们。

你已经完成了实验,恭喜!

好的,现在让我们禁用一些陈旧、脆弱的东西吧。

禁用弱 SSH 加密算法

如我之前所说,我们希望将我们的扫描结果与 NIST 推荐进行比较,并相应地进行配置。但要理解的是,可用的算法列表在不同的 Linux 发行版之间有所不同。为了减少混淆,我将在本节中介绍两个操作流程,一个适用于 Ubuntu 22.04,另一个适用于 CentOS 7。AlmaLinux 8 和 9 有其独特的操作方式,所以我将在下一节中介绍它们。

动手实验 – 禁用弱 SSH 加密算法 – Ubuntu 22.04

对于本次实验,你需要使用作为扫描器的虚拟机,以及另一台用于扫描和配置的 Ubuntu 22.04 虚拟机。让我们开始吧:

  1. 如果你还没有这样做,请扫描 Ubuntu 22.04 虚拟机并将输出保存到文件中:
nmap --script=ssh2-enum-algos.nse 192.168.0.14 -oN ubuntuscan.txt
  1. 通过执行以下命令计算文件中的行数:
wc -l ubuntuscan.txt
  1. 在目标 Ubuntu 22.04 虚拟机上,用你喜欢的文本编辑器打开/etc/ssh/sshd_config文件。在文件的顶部找到这两行:
# Ciphers and keying
#RekeyLimit default none
  1. 在这两行下面,插入以下三行:
Ciphers -aes128-ctr,aes192-ctr,aes128-gcm@openssh.com
KexAlgorithms ecdh-sha2-nistp384
MACs -hmac-sha1-etm@openssh.com,hmac-sha1,umac-64-etm@openssh.com,umac-64@openssh.com,umac-128-etm@openssh.com,umac-128@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-256
  1. CiphersMACs 行中,您会看到一个以逗号分隔的禁用算法列表,前面有 - 符号(只需要一个 - 就可以禁用列表中的所有算法)。在 KexAlgorithms 行中,没有 - 符号。这意味着该行中列出的算法是唯一被启用的算法。

  2. 保存文件并重新启动 SSH 守护进程。验证它是否正确启动:

sudo systemctl restart ssh
sudo systemctl status ssh
  1. 再次扫描 Ubuntu 22.04 虚拟机,并将输出保存到另一个文件:
nmap --script=ssh2-enum-algos.nse 192.168.0.14 -oN ubuntuscan_modified.txt
  1. 计算新文件中的行数:
wc -l ubuntuscan_modified.txt
  1. 在扫描虚拟机上,使用 diff 比较这两个文件。您应该看到比以前少的算法:
diff -y ubuntuscan.txt ubuntuscan_modified.txt

眼尖的朋友会注意到,我们留下了一个不在 NIST CNSA 列表中的密码算法。chacha20-poly1305@openssh.com 是一个轻量级算法,适用于低功耗的手持设备。它是一个优秀且强大的算法,可以替代传统的高级加密标准AES)算法,并且具有更高的性能。然而,如果您需要完全符合 NIST CNSA 标准,那么您可能需要禁用它。

您已经完成实验的最后一步——恭喜!

接下来,我们来使用 CentOS 7。

实验操作 – 禁用弱 SSH 加密算法 – CentOS 7

在开始使用 CentOS 7 时,您会注意到两件事:

  • 启用更多算法:CentOS 7 的默认 SSH 配置启用了比 Ubuntu 22.04 更多的算法。这包括一些非常古老的算法,您真的不希望再看到它们。我说的是像 Blowfish 和 3DES 这样的算法,它们早就应该被淘汰了。

  • 另一种配置技术:在 CentOS 中,将 - 符号放在您想禁用的算法列表前面不起作用。相反,您需要列出所有您希望启用的算法。

对于本实验,您需要一台 CentOS 7 虚拟机和一直在使用的扫描虚拟机。有了这些准备,咱们开始吧:

  1. 扫描 CentOS 7 虚拟机并将输出保存到文件中。请注意,由于 CentOS 7 防火墙的原因,您需要添加 -Pn 选项:
nmap -Pn --script=ssh2-enum-algos.nse 192.168.0.12 -oN centos7scan.txt
  1. 计算输出文件中的行数:
wc -l centos7scan.txt
  1. 在目标 CentOS 7 虚拟机上,使用您喜欢的文本编辑器打开 /etc/ssh/sshd_config 文件。在文件顶部,找到这两行:
# Ciphers and keying
#RekeyLimit default none
  1. 在这两行下面,插入以下三行:
Ciphers aes256-gcm@openssh.com,aes256-ctr,chacha20-poly1305@openssh.com
KexAlgorithms ecdh-sha2-nistp384
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-256
  1. 如我之前提到的,使用 CentOS 时,使用 - 禁用算法不起作用。相反,我们需要列出所有我们希望启用的算法。

  2. 保存文件并重新加载 SSH 守护进程。验证它是否正确启动:

sudo systemctl reload sshd
sudo systemctl status sshd
  1. 再次扫描 CentOS 7 虚拟机,并将输出保存到另一个文件:
nmap -Pn --script=ssh2-enum-algos.nse 192.168.0.12 -oN centos7scan_modified.txt
  1. 计算新输出文件中的行数:
wc -l centos7scan_modified.txt
  1. 在扫描虚拟机上,使用 diff 比较这两个文件。您应该看到比以前少的算法:
diff -y centos7scan.txt centos7scan_modified.txt

与之前一样,我启用了 chacha20-poly1305@openssh.com 算法。如果您需要完全符合 NIST CNSA 标准,那么您可能需要禁用它。

恭喜你,实验已经结束!

接下来,让我们来看一下 RHEL 8 和 9 系列中一个方便的新功能。

在 RHEL 8/9 和 AlmaLinux 8/9 上设置系统范围的加密策略

第六章加密技术中,我们简要介绍了如何在 AlmaLinux 8 和 9 上设置系统范围的加密策略。通过这个酷炫的功能,你不再需要为每个单独的守护进程配置加密策略。相反,你只需运行几个简单的命令,加密策略就会立即为多个守护进程改变。要查看哪些守护进程涵盖在内,请查看/etc/crypto-policies/back-ends/目录。这里是部分内容:

[donnie@localhost back-ends]$ ls -l
total 0
. . .
. . .
lrwxrwxrwx. 1 root root 46 Sep 24 18:17 openssh.config -> /usr/share/crypto-policies/DEFAULT/openssh.txt
lrwxrwxrwx. 1 root root 52 Sep 24 18:17 opensshserver.config -> /usr/share/crypto-policies/DEFAULT/opensshserver.txt
lrwxrwxrwx. 1 root root 49 Sep 24 18:17 opensslcnf.config -> /usr/share/crypto-policies/DEFAULT/opensslcnf.txt
lrwxrwxrwx. 1 root root 46 Sep 24 18:17 openssl.config -> /usr/share/crypto-policies/DEFAULT/openssl.txt
[donnie@localhost back-ends]$

如你所见,这个目录包含指向文本文件的符号链接,这些文件包含有关为DEFAULT配置启用或禁用哪些算法的指令。在上一级的/etc/crypto-policies/目录中,有一个config文件。打开它,你会看到这里设置了系统范围的配置:

DEFAULT

使用DEFAULT配置扫描此虚拟机会发现,仍然启用了不少旧算法。为了去除它们,我们可以切换到FUTURE模式或FIPS模式。

提示

在撰写本文时,EPEL 仓库使用的安全证书与FUTURE模式不兼容。这将阻止你从 EPEL 仓库更新或安装任何软件包。如果你需要在启用FUTURE模式的同时使用 EPEL 仓库,请注意,在更新系统或安装 EPEL 中的软件包之前,你需要将机器设置回DEFAULT模式。(当然,到你阅读本文时,这个问题可能已经解决。)

为了展示这个如何工作,让我们动手做另一个实验。

动手实验 – 在 AlmaLinux 9 上设置加密策略

从一个全新的 AlmaLinux 9 虚拟机和你一直在使用的扫描器虚拟机开始。现在,按照以下步骤操作:

  1. 在 AlmaLinux 9 虚拟机上,使用update-crypto-policies工具验证其是否运行在DEFAULT模式:
sudo update-crypto-policies --show
  1. 使用DEFAULT配置扫描 AlmaLinux 9 虚拟机并将输出保存到文件:
nmap -Pn --script=ssh2-enum-algos.nse 192.168.0.17 -oN alma9_default.txt
  1. 在 AlmaLinux 9 虚拟机上,将系统范围的加密策略设置为FUTURE
sudo update-crypto-policies --set FUTURE
  1. /etc/ssh/目录中,删除当前主机的密钥:
sudo rm /etc/ssh/*key*
  1. (不用担心,重启机器时会自动创建新密钥。)

  2. 重启虚拟机:

sudo shutdown -r now
  1. 在扫描器虚拟机上,使用文本编辑器打开~/.ssh/known_hosts文件。删除之前为 AlmaLinux 虚拟机创建的条目,并保存文件。(我们这样做是因为由于新策略,AlmaLinux 虚拟机的公钥指纹会发生变化。)

  2. 再次扫描 AlmaLinux 虚拟机,并将输出保存到另一个文件:

nmap -Pn --script=ssh2-enum-algos.nse 192.168.0.17 -oN alma9_future.txt
  1. 比较这两个输出文件。你现在应该看到启用的算法比之前少了。
diff -y alma9_default.txt alma9_future.txt
  1. 查看/etc/crypto-policies/back-ends/目录中的文件:
ls -l /etc/crypto-policies/back-ends/
  1. 现在你会看到符号链接指向FUTURE目录中的文件。

  2. 查看/etc/ssh/目录中的主机密钥,看看它们是否与之前的不同:

ls -l /etc/ssh/*key*
  1. 扫描你在第六章加密技术中设置为FIPS模式的 AlmaLinux 9 虚拟机,并将扫描结果与DEFAULTFUTURE模式的扫描结果进行比较。

  2. 实验结束

提示

如果FUTURE模式没有禁用足够的算法,你可以创建自己的自定义策略。详细信息见此处:

access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/security_hardening/using-the-system-wide-cryptographic-policies_security-hardening#customizing-system-wide-cryptographic-policies-with-subpolicies_using-the-system-wide-cryptographic-policies

现在你知道如何配置 SSH,仅使用最现代、最安全的算法。接下来,让我们来看看日志记录。

配置更详细的日志记录

在默认配置下,SSH 在有人通过 SSH、SCP 或 SFTP 登录时已经会生成日志条目。在 Debian/Ubuntu 系统中,条目会记录在/var/log/auth.log文件中。在 Red Hat/CentOS/AlmaLinux 系统中,条目会记录在/var/log/secure文件中。无论哪种情况,日志条目的格式大致如下:

Oct  1 15:03:23 donnie-ca sshd[1141]: Accepted password for donnie from 192.168.0.225 port 54422 ssh2
Oct  1 15:03:24 donnie-ca sshd[1141]: pam_unix(sshd:session): session opened for user donnie by (uid=0)

打开sshd_config的手册页,滚动到LogLevel项。在那里,你会看到提供不同级别 SSH 日志详细信息的各种设置。各个级别如下:

  • 安静模式

  • 致命错误

  • 错误

  • 信息

  • 详细模式

  • DEBUGDEBUG1

  • DEBUG2

  • DEBUG3

通常,我们关心的只有其中的两个级别:INFOVERBOSEINFO是默认设置,而VERBOSE是我们在正常情况下会使用的另一个级别。各种DEBUG级别可能有助于故障排除,但手册页警告我们,在生产环境中使用DEBUG会侵犯用户隐私。

让我们动手操作一下,亲自感受一下不同日志级别会记录什么。

实验操作 – 配置更详细的 SSH 日志

对于这个实验,使用你之前实验中使用的相同虚拟机(VM)。这样,你可以更好地了解一个完整的sshd_config文件在完全锁定时应该是什么样子。通过 SSH 远程登录到目标虚拟机,并按照以下步骤操作:

  1. 打开主日志文件,滚动到由于你的登录而生成的日志条目所在的位置,观察它的内容。对于 Ubuntu,执行:
sudo less /var/log/auth.log

对于 CentOS 或 AlmaLinux,执行:

sudo less /var/log/secure
  1. 正如我之前提到的,你绝对不想在生产机器上将 SSH 日志级别设置为任何DEBUG级别。但为了让你看到它究竟记录了什么,现在将机器设置为DEBUG。在你最喜欢的文本编辑器中打开/etc/ssh/sshd_config文件。找到以下内容:
#LogLevel INFO

将其更改为以下内容:

LogLevel DEBUG3
  1. 保存文件后,重新加载 SSH。在 Ubuntu 上,执行以下命令:
sudo systemctl reload ssh

在 CentOS 或 AlmaLinux 上,执行以下操作:

sudo systemctl reload sshd
  1. 退出 SSH 会话,然后重新登录。查看系统日志文件,查看此次新登录的条目。

  2. 打开/etc/ssh/sshd_config文件进行编辑。将LogLevel DEBUG3行更改为以下内容:

LogLevel VERBOSE
  1. 保存文件后,重新加载或重启 SSH 守护进程。退出 SSH 会话,重新登录,并查看系统日志文件中的条目。

提示

VERBOSE模式的主要好处是它会记录任何用于登录的密钥的指纹。这对密钥管理非常有帮助。

恭喜你,已经完成了实验的最后一步!

所以,你刚刚看到如何在系统日志中获取有关 SSH 登录的更多信息。接下来,我们来聊一下访问控制。

使用白名单和 TCP Wrappers 配置访问控制

通过要求客户端通过密钥交换进行身份验证,而不是使用用户名和密码,我们已经把安全性提高到了一个相当高的水平。当我们禁止密码身份验证时,坏人可以对我们进行暴力破解密码攻击,直到天荒地老,但对他们毫无帮助。(虽然,实际上,他们一发现密码身份验证已经被禁用,就会放弃。)为了增加额外的安全性,我们还可以设置几个访问控制机制,只允许特定的用户、组或客户端机器登录 SSH 服务器。这两种机制是:

  • sshd_config文件中的白名单

  • TCP Wrappers,通过/etc/hosts.allow/etc/hosts.deny文件

好的,你现在可能会说,但防火墙呢?这不是我们可以使用的第三种机制吗? 你说得对。但我们已经在第四章《用防火墙保护你的服务器 - 第一部分》和第五章《用防火墙保护你的服务器 - 第二部分》中讨论过防火墙的内容,所以这里就不再重复了。你还可以在系统的 systemd 单元文件中为 SSH 添加访问控制指令。不过,针对我们目前的讨论,我更愿意避免解释如何编辑 systemd 单元文件的复杂性。不管怎样,这些就是控制 SSH 服务器访问的方式。你可以根据需要同时使用这些方式,也可以选择其中一种。(这完全取决于你有多谨慎。)

关于如何进行访问控制,有两种竞争的哲学思想。使用黑名单时,你明确禁止某些人或机器的访问。这很难做到,因为列表可能会变得非常长,而且你依然无法阻止所有你需要阻止的人。首选且更简单的方法是使用白名单,专门允许某些人或机器的访问。

首先,让我们通过实践实验来看看如何在sshd_config中创建白名单。

在 sshd_config 中配置白名单

你可以在sshd_config中设置的四个访问控制指令如下:

  • DenyUsers

  • AllowUsers

  • DenyGroups

  • AllowGroups

对于每个指令,你可以指定多个用户名或组名,用空格隔开。此外,这四个指令是按我列出的顺序处理的。换句话说,如果一个用户同时列在DenyUsersAllowUsers指令中,DenyUsers具有优先权。如果一个用户在DenyUsers中被列出,并且是某个列在AllowGroups中的组的成员,DenyUsers依然具有优先权。为了演示这一点,我们来做个实验。

实践实验 – 在 sshd_config 中配置白名单

这个实验适用于你所有的虚拟机。请按照以下步骤操作:

  1. 在你希望配置的虚拟机上,为 Frank、Charlie 和 Maggie 创建用户账户。在 Ubuntu 上,按如下方式操作:
sudo adduser frank

在 CentOS 或 AlmaLinux 上,按如下方式操作:

sudo useradd frank
sudo passwd frank
  1. 创建webadmins组并将 Frank 添加到其中:
sudo groupadd webadmins
sudo usermod -a -G webadmins frank
  1. 从你的主机或者另一台虚拟机上,让这三位用户登录。然后,将他们登出。

  2. 用你喜欢的文本编辑器打开/etc/ssh/sshd_config文件。在文件底部,添加一行AllowUsers并写上你的用户名,像这样:

AllowUsers donnie
  1. 然后,重启或重新加载 SSH 服务并验证它是否正确启动:
For Ubuntu:
sudo systemctl restart ssh
sudo systemctl status ssh
For CentOS:
sudo systemctl restart sshd
sudo systemctl status sshd
  1. 重复步骤 3。这一次,这三只小猫应该无法登录。用文本编辑器打开/etc/ssh/sshd_config文件。这一次,在文件底部为webadmins组添加一行AllowGroups,像这样:
AllowGroups webadmins
  1. 重启 SSH 服务并验证它是否正常启动。

从你的主机或另一台虚拟机上,让 Frank 尝试登录。你会看到,尽管他是webadmins组的成员,他仍然无法登录。这是因为带有你自己用户名的AllowUsers行具有优先权。

  1. 用文本编辑器打开sshd_config,删除在步骤 4中插入的AllowUsers行。重启 SSH 服务并验证它是否正常启动。

  2. 尝试登录你自己的账户,然后尝试登录所有其他用户的账户。你现在应该看到,只有 Frank 被允许登录。现在,任何其他用户只有通过虚拟机的本地控制台才能登录。

  3. 在虚拟机的本地控制台登录你自己的账户。从sshd_config中删除AllowGroups行,并重启 SSH 服务。

你已完成实验——恭喜!

你刚刚看到的是如何在守护进程级别配置白名单,使用 SSH 守护进程的配置文件。接下来,我们将讨论如何在网络级别配置白名单。

使用 TCP Wrappers 配置白名单

这个名字听起来很奇怪,但概念很简单。TCP Wrappers – 单数形式,不是复数形式 – 监听传入的网络连接,并根据情况允许或拒绝连接请求。白名单和黑名单配置在 /etc/hosts.allow 文件和 /etc/hosts.deny 文件中。这两个文件一起工作。如果你在 hosts.allow 中创建了一个白名单,而没有在 hosts.deny 中添加任何内容,什么也不会被阻止。因为 TCP Wrappers 会先检查 hosts.allow,如果在那里找到白名单项,它就会跳过检查 hosts.deny。如果一个连接请求的来源没有在白名单中,TCP Wrappers 会先检查 hosts.allow,发现那里没有这个来源的条目后,再检查 hosts.deny。如果 hosts.deny 中没有任何内容,连接请求仍然会通过。所以,在配置好 hosts.allow 后,你还必须配置 hosts.deny 才能阻止任何连接。

你需要注意的是,Red Hat 已经从 RHEL 8/9 及其后代版本中移除了 TCP Wrappers。因此,如果你决定使用我在这里展示的技术进行实践,你可以在 Ubuntu 或 CentOS 7 虚拟机上进行,但不能在 AlmaLinux 8/9 虚拟机上进行。(Red Hat 现在推荐通过 firewalld 而不是 TCP Wrappers 来进行访问控制。)

你可以在这里阅读更多内容:access.redhat.com/solutions/3906701

(你需要一个 Red Hat 账户才能阅读全文。如果你不需要为 Red Hat 支持付费,可以开设一个免费的开发者账户。)

现在,有一点非常重要。始终,始终,在配置 hosts.deny 之前先配置 hosts.allow。因为一旦你保存这两个文件中的任何一个,新配置就会立即生效。所以,如果你在远程登录时配置了 hosts.deny 中的阻止规则,那么一旦保存该文件,你的 SSH 连接就会中断。唯一能够重新登录的方法是进入服务器机房,从本地控制台重新配置。最好的做法是习惯总是先配置 hosts.allow,即使你是从本地控制台工作。这样,你永远可以确保无误。(令人惊讶的是,其他一些 TCP Wrappers 教程却告诉你先配置 hosts.deny。他们到底在想什么?)

你可以使用 TCP Wrappers 做一些相当复杂的操作,但现在我只想保持简单。所以,让我们来看一些最常用的配置。

要将单个 IP 地址列入白名单,在 /etc/hosts.allow 文件中加入如下行:

SSHD: 192.168.0.225

然后,将此行添加到 /etc/hosts.deny 文件中:

SSHD: ALL

现在,如果你尝试从除 hosts.allow 中列出的 IP 地址之外的任何地方登录,将会被拒绝访问。

你也可以在 hosts.allow 中列出多个 IP 地址或网络地址。有关如何操作的详细信息,请参阅 hosts.allow 的手册页。

正如我之前提到的,你可以使用 TCP Wrappers 做一些高级操作。然而,现在 Red Hat 社区已经弃用了它,你最好习惯通过设置防火墙规则或配置 sshd_config 文件来进行配置。另一方面,TCP Wrappers 仍然可以在你需要快速配置访问控制规则时派上用场,前提是你使用的机器支持它。

配置自动注销和安全横幅

最佳安全实践要求人们在离开桌面之前注销计算机。这在管理员使用个人办公电脑远程登录敏感服务器时尤其重要。默认情况下,SSH 允许用户一直保持登录状态而不发出任何警告。然而,你可以设置它自动注销空闲用户。我们将介绍两种快速的方法来实现这一点。

配置本地和远程用户的自动注销

第一种方法会自动注销本地控制台或通过 SSH 远程登录的空闲用户。进入 /etc/profile.d/ 目录并创建 autologout.sh 文件,内容如下:

TMOUT=100
readonly TMOUT
export TMOUT

这设置了一个超时时间值为 100 秒。(TMOUT 是一个 Linux 环境变量,用于设置超时时间值。)

为所有人设置可执行权限:

sudo chmod +x autologout.sh

注销并重新登录。然后,让虚拟机保持空闲。100 秒后,你应该看到虚拟机回到登录提示符。需要注意的是,如果在你创建此文件时有用户已登录,则新配置不会对他们生效,直到他们注销并重新登录。

在 sshd_config 中配置自动注销

第二种方法仅会注销通过 SSH 远程登录的用户。你不需要创建 /etc/profile.d/autologout.sh 文件,只需在 /etc/ssh/sshd_config 文件中找到这两行:

#ClientAliveInterval 0
#ClientAliveCountMax 3

将它们更改为如下所示:

ClientAliveInterval 100
ClientAliveCountMax 0

然后,重启或重新加载 SSH 服务以使更改生效。

在这两个示例中,我一直使用 100 秒作为超时时间值。然而,你可以根据自己的需求设置超时时间值。

现在你已经知道如何自动注销用户。接下来,我们来看一下如何设置安全横幅。

创建登录前的安全横幅

第三章保护普通用户账户中,我向你展示了如何创建一个在用户登录后显示的安全消息。你可以通过将消息插入 /etc/motd 文件来实现。但仔细想想,是否更好让人们在登录之前就看到安全横幅呢?你可以通过 sshd_config 来实现这一点。

首先,让我们创建 /etc/ssh/sshd-banner 文件,内容如下:

Warning!!  Authorized users only.  All others will be prosecuted.

/etc/ssh/sshd_config文件中,查找这一行:

#Banner none

将其更改为:

Banner /etc/ssh/sshd-banner

一如既往,重启或重新加载 SSH 服务。现在,任何远程登录的人都会看到类似这样的内容:

[donnie@fedora-teaching ~]$ ssh donnie@192.168.0.3
Warning!!  Authorized users only.  All others will be prosecuted.
donnie@192.168.0.3's password: 
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-64-generic x86_64)
. . .
. . .

那么,这个横幅能确保你的系统免受坏人的攻击吗?不能,但如果你需要提起诉讼,它可能会有用。有时候,向法官和陪审团证明入侵者知道自己正在进入不该去的地方是很重要的。

现在你知道了如何设置安全横幅和自动登出,接下来我们来看看一些不容易归类的杂项设置。

配置其他杂项安全设置

我们的 SSH 配置比以前更加安全,但我们仍然可以做得更好。以下是一些你可能在其他地方没有见过的小技巧。

禁用 X11 转发

当你以常规方式 SSH 连接到服务器时,就像我们一直做的那样,你只能运行文本模式的程序。如果你尝试远程运行任何基于图形界面的程序,比如 Firefox,你会收到一个错误消息。但,当你打开几乎所有 Linux 发行版的sshd_config文件时,你会看到这一行:

X11Forwarding yes

这意味着,只要选项开关正确,你就可以远程运行基于图形界面的程序。假设你登录的机器安装了图形桌面环境,你可以在登录时使用-Y-X选项,如下所示:

ssh -X donnie@192.168.0.12

ssh -Y donnie@192.168.0.12

这里的问题是,X11 协议(它在大多数 Linux 和 Unix 系统上驱动图形桌面环境)存在一些安全漏洞,使得远程使用它有点危险。坏人有办法利用它来破坏整个系统。你最好的选择是通过将X11Forwarding行更改为以下内容来禁用它:

X11Forwarding no

如常,重启或重新加载 SSH 服务以使新配置生效。

现在你知道了 X11 转发,接下来我们来挖掘一些隧道吧。

禁用 SSH 隧道

SSH 隧道,或者有时称为 SSH 端口转发,是保护非安全协议的一个便捷方式。例如,通过 SSH 隧道转发普通的 HTTP,你可以以安全的方式访问一个非安全的网站。以下是它的工作方式:

sudo ssh -L 80:localhost:80 donnie@192.168.0.12

我在这里必须使用sudo,因为所有低于1024端口的端口都是特权端口。如果我将 Web 服务器配置更改为监听一个非特权的高端口,就不需要使用sudo了。

现在,为了以安全的方式连接到这个网站,我可以直接在本地机器上打开网页浏览器,并输入以下 URL:

http://localhost

是的,似乎通过输入localhost来访问远程机器有点奇怪,但那是我在用 SSH 登录时使用的标识符。我本可以使用其他名字,但localhost是你在 SSH 教程中传统上看到的名字,所以我这里跟随这个做法。现在,一旦我退出 SSH 会话,连接到 Web 服务器的连接将会断开。

虽然这听起来是个好主意,但实际上它会带来安全问题。假设你的公司防火墙已经设置好,防止员工回家并远程登录公司工作站。这是件好事,对吧?现在,假设公司防火墙必须允许外发 SSH 连接。用户可能会从公司工作站创建一个 SSH 隧道,连接到其他位置的计算机,然后去该位置并创建一个反向隧道回到公司工作站。所以,如果无法在防火墙上阻止外发的 SSH 流量,那么你最好的做法就是禁用 SSH 隧道。在你的sshd_config文件中,确保有类似如下的配置:

AllowTcpForwarding no
AllowStreamLocalForwarding no
GatewayPorts no
PermitTunnel no

重启或重新加载 SSH 服务,和往常一样。现在,端口隧道将被禁用。

既然你已经知道如何禁用 SSH 隧道,现在我们来谈谈如何更改默认端口。

更改默认 SSH 端口

默认情况下,SSH 监听在 22/TCP 端口。如果你做过一段时间的运维工作,你肯定见过很多文档强调使用其他端口,以便让坏人更难找到你的 SSH 服务器。但我得说,这种观点是有争议的。

首先,如果你启用了密钥认证并禁用了密码认证,那么更改端口的价值是有限的。当扫描器机器人发现你的服务器并看到密码认证被禁用时,它会直接离开,不再打扰你。其次,即使你更改了端口,坏人的扫描工具仍然能找到它。如果你不相信我,只需去 Shodan.io 搜索 ssh。例如,有人以为他们通过更改端口为 2211 智能地隐藏了自己的 SSH 服务:

是的,聪明吧。结果并没有把事情隐藏得那么好,对吧?

另一方面,安全专家 Daniel Miessler 表示,更改端口仍然是有用的,以防有人试图利用零日漏洞攻击 SSH。他最近发布了一个非正式实验的结果,他在该实验中设置了一个公共服务器,监听端口 22 和端口 24 的 SSH 连接,并观察每个端口的连接尝试次数。他说,在一个周末的时间里,端口 22 收到了 18,000 次连接,而端口 24 只有 5 次。但尽管他没有明确说明,看起来他还是启用了密码认证。为了得到真正科学准确的结果,他需要在禁用密码认证的情况下进行同样的实验。他还需要在分别启用 SSH 的不同服务器上进行研究,而不是在单台机器上同时启用两个端口。我的直觉是,当扫描器机器人发现端口 22 开放时,它们就不再扫描其他 SSH 端口了。

你可以在这里阅读他的实验:danielmiessler.com/study/security-by-obscurity/

无论如何,如果你确实想更改端口,只需取消sshd_config文件中#Port 22行的注释,并将端口号改为你想要的任何值。

接下来,我们来谈谈密钥管理。

管理 SSH 密钥

之前,我向你展示了如何在本地工作站上创建一对密钥,并将公钥传输到远程服务器。这样可以在服务器上禁用用户名/密码验证,从而使坏人更难突破。然而,这个方法有个问题我们没有解决,那就是公钥被放入一个位于用户自己家目录中的authorized_keys文件。因此,用户可以手动将额外的密钥添加到该文件中,这样用户就能从除授权地点之外的其他位置登录。此外,authorized_keys文件散布在每个用户的家目录中,这也使得很难追踪每个人的密钥。

处理这个问题的一种方法是将每个人的authorized_keys文件移动到一个中央位置。我们以 Vicky 为例,她是我 15 岁的纯灰色小猫。管理员在服务器上为她创建了一个需要访问的账户,并允许她在禁用密码验证之前创建并传输她的密钥到该账户。所以,Vicky 现在把她的authorized_keys文件放在了该服务器的家目录中,就像我们在这里看到的那样:

vicky@ubuntu-nftables:~$ cd .ssh
vicky@ubuntu-nftables:~/.ssh$ ls -l
total 4
-rw------- 1 vicky vicky 233 Oct  3 18:24 authorized_keys
vicky@ubuntu-nftables:~/.ssh$

Vicky 拥有该文件,并且对其具有读写权限。因此,尽管管理员禁用了密码验证后,她不能通过正常方式将其他密钥传输到该文件,但她仍然可以手动传输密钥文件,并手动编辑authorized_keys文件以包含它们。为了阻止她的努力,我们那位无畏的管理员将在/etc/ssh/目录下创建一个目录,用来存放每个人的authorized_keys文件,就像这样:

sudo mkdir /etc/ssh/authorized-keys

我们那位无畏的管理员拥有完全的管理员权限,这使他能够登录到 root 用户的 Shell 中,这样他就能进入所有其他用户的目录:

donnie@ubuntu-nftables:~$ sudo su -
[sudo] password for donnie: 
root@ubuntu-nftables:~# cd /home/vicky/.ssh
root@ubuntu-nftables:/home/vicky/.ssh# ls -l
total 4
-rw------- 1 vicky vicky 233 Oct 3 18:24 authorized_keys
root@ubuntu-nftables:/home/vicky/.ssh#

下一步是将 Vicky 的authorized_keys文件移到新的位置,并将其名称改为vicky,就像这样:

root@ubuntu-nftables:/home/vicky/.ssh# mv authorized_keys /etc/ssh/authorized-keys/vicky
root@ubuntu-nftables:/home/vicky/.ssh# exit
donnie@ubuntu-nftables:~$

现在,我们遇到了一些难题。正如你在这里看到的,文件仍然属于 Vicky,她具有读写权限。所以,即使没有管理员权限,她仍然可以编辑该文件。去除写权限也行不通,因为文件属于她,她可以将写权限恢复。将所有权更改为 root 用户是其中的一个解决方法,但那样会导致 Vicky 无法读取文件,从而无法登录。为了看到完整的解决方案,我们来看一下我自己已经对authorized_keys文件做了什么:

donnie@ubuntu-nftables:~$ cd /etc/ssh/authorized-keys/
donnie@ubuntu-nftables:/etc/ssh/authorized-keys$ ls -l
total 8
-rw------- 1 vicky vicky 233 Oct 3 18:24 vicky
-rw-r-----+ 1 root root 406 Oct 3 16:24 donnie
donnie@ubuntu-nftables:/etc/ssh/authorized-keys$

眼尖的你们一定注意到了donnie文件的变化。你们看到我把所有权改为 root 用户,并且添加了一个访问控制列表,正如+符号所示。我们也可以对 Vicky 做同样的事情:

donnie@ubuntu-nftables:/etc/ssh/authorized-keys$ sudo chown root: vicky
donnie@ubuntu-nftables:/etc/ssh/authorized-keys$ sudo setfacl -m u:vicky:r vicky 
donnie@ubuntu-nftables:/etc/ssh/authorized-keys$

查看权限设置时,我们看到 Vicky 对vicky文件有读取权限:

donnie@ubuntu-nftables:/etc/ssh/authorized-keys$ ls -l 
total 8
-rw-r-----+ 1 root root 406 Oct 3 16:24 donnie
-rw-r-----+ 1 root root 233 Oct 3 18:53 vicky
donnie@ubuntu-nftables:/etc/ssh/authorized-keys$

顺便来看一下她的访问控制列表:

donnie@ubuntu-nftables:/etc/ssh/authorized-keys$ getfacl vicky
# file: vicky
# owner: root
# group: root
user::rw-
user:vicky:r--
group::---
mask::r--
other::---
donnie@ubuntu-nftables:/etc/ssh/authorized-keys$

Vicky 现在可以读取文件以便她能够登录,但她不能修改它。

最后一步是重新配置sshd_config文件,然后重启或重新加载 SSH 服务。在文本编辑器中打开该文件并查找此行:

#AuthorizedKeysFile     .ssh/authorized_keys .ssh/authorized_keys2

将其更改为如下:

AuthorizedKeysFile      /etc/ssh/authorized-keys/%u

行尾的%u是一个迷你宏,它指示 SSH 服务查找与登录用户同名的密钥文件。现在,即使用户在自己的家目录下手动创建authorized_keys文件,SSH 服务也会忽略它们。另一个好处是,将所有密钥集中在一个地方,使管理员在需要时撤销某人的访问权限变得更加容易。

请注意,管理 SSH 密钥远比我在这里展示的要复杂得多。一个问题是,虽然有一些免费的开源软件可以用来管理公钥,但没有用于管理私钥的解决方案。一个大型企业可能会有成千上万甚至数百万个私钥和公钥散布在不同的位置。这些密钥永不过期,除非被删除,否则它们会一直存在。如果错误的人拿到了私钥,你的整个系统可能会受到威胁。尽管我不愿意这么说,但你管理 SSH 密钥的最佳选择是使用商业解决方案,比如 SSH.com 和 CyberArk 提供的解决方案。

查看 SSH.com 的密钥管理解决方案:www.ssh.com/academy/iam/ssh-key-management

访问 CyberArk 的密钥管理解决方案:www.cyberark.com/resources/blog/ssh-keys-the-powerful-unprotected-privileged-credentials

完全公开:我与 SSH.com 或 CyberArk 没有任何关系,也没有因为告诉你这些信息而获得任何报酬。

你在这里学到了几个提升服务器安全性的酷技巧。现在,让我们看看如何为不同的用户和组创建不同的配置。

为不同的用户和组设置不同的配置

在服务器端,你可以使用Match UserMatch Group指令为特定的用户或组设置自定义配置。要查看如何操作,查看/etc/ssh/sshd_config文件底部的示例。在那里,你会看到以下内容:

# Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server

当然,由于这一行被注释掉了,它不会产生任何效果,但这没关系。以下是anoncvs用户的配置:

  • 他不能进行X11 转发

  • 他不能进行TCP 转发

  • 他不能使用命令终端。

一旦他登录,就会启动并行版本服务CVS)服务器。由于没有终端的使用权限,anoncvs可以启动 CVS 服务器,但不能做其他事情。

你可以为需要的多个用户设置不同的配置。你在自定义配置中设置的任何内容都会覆盖全局设置。要为某个组设置自定义配置,只需将Match User替换为Match Group,并提供组名而非用户名。

为不同的主机创建不同的配置

换个角度来看,接下来我们来看客户端的配置。这一次,我们将讨论一个实用的小技巧,帮助简化登录到需要不同密钥或 SSH 选项的不同服务器的过程。你只需进入自己主目录下的.ssh目录,并创建一个config文件。为了演示这一点,假设我们已经为服务器创建了 DNS 记录或/etc/hosts文件条目,这样就不用记住那么多 IP 地址。假设我们还为每个需要访问的服务器创建了一对单独的密钥。在~/.ssh/config文件中,我们可以添加一个条目,像这样:

Host ubuntu-nftables
 IdentityFile ~/.ssh/unft_id_rsa
 IdentitiesOnly yes
 ForwardX11 yes
 Cipher aes256-gcm@openssh.com

这里是详细分解:

  • IdentityFile:指定与该服务器配对的密钥。

  • IdentitiesOnly yes:如果你的会话密钥环中加载了多个密钥,这将强制客户端仅使用此处指定的密钥。

  • ForwardX11 yes:我们希望这个客户端使用X11转发。(当然,前提是服务器已被配置为允许该功能。)

  • 密码算法 aes256-gcm@openssh.com:我们希望使用这个算法,使用这个算法来执行加密操作。

要为其他主机创建自定义配置,只需为每个主机在此文件中添加一个条目。

保存文件后,你必须将其权限设置更改为600。如果不这么做,当你尝试登录配置文件中的任何服务器时,会出现错误。

既然你已经了解了自定义配置,接下来我们来谈谈 SFTP,在这里我们将充分利用刚才看到的Match Group指令。

为 SFTP 用户设置 chroot 环境

安全文件传输协议SFTP)是一个执行安全文件传输的好工具。它有一个命令行客户端,但用户更可能使用图形化客户端,如 FileZilla。在默认的 SSH 设置下,任何在 Linux 机器上有用户账户的人都可以通过 SSH 或 SFTP 登录,并可以浏览整个服务器的文件系统。我们真正想要的 SFTP 用户限制是阻止他们通过 SSH 登录命令行提示符,并将他们限制在自己的指定目录中。

提示

这个小技巧的一个好用例子是为网站创建者设置 SFTP 配置。与其允许这些用户仅将文件传输到和从他们自己的主目录, 不如只允许他们将文件传输到和从网站内容目录。

创建组并配置 sshd_config 文件

除了用户创建命令稍有不同外,这个过程在你所有的虚拟机上都是相同的。

我们将首先创建一个sftpusers组:

sudo groupadd sftpusers

创建用户账户并将其添加到sftpusers组。我们将一步完成这两个操作。在你的 CentOS 或 AlmaLinux 机器上,创建 Max 账户的命令如下所示:

sudo useradd -G sftpusers max
sudo passwd max

在你的 Ubuntu 机器上,它将显示如下:

sudo useradd -m -d /home/max -s /bin/bash -G sftpusers max

打开/etc/ssh/sshd_config文件,使用你喜欢的文本编辑器。找到以下这一行:

Subsystem sftp /usr/lib/openssh/sftp-server

将其更改为如下所示:

Subsystem sftp internal-sftp

此设置允许你禁用某些用户的普通 SSH 登录。

sshd_config文件的底部,添加一个Match Group段落:

Match Group sftpusers
        ChrootDirectory /home
        AllowTCPForwarding no
        AllowAgentForwarding no
        X11Forwarding no
        ForceCommand internal-sftp

这里需要注意的一个重要事项是,ChrootDirectory必须由 root 用户拥有,并且不能对除 root 用户之外的任何人可写。当 Max 登录时,他将处于/home/目录,然后必须cd进入自己的目录。这也意味着你希望所有用户的主目录都设置为限制性的700权限,以防止其他人访问彼此的文件。

保存文件并重启 SSH 守护进程。然后,尝试以 Max 身份通过普通 SSH 登录,看看会发生什么:

donnie@linux-0ro8:~> ssh max@192.168.0.8
max@192.168.0.8's password:
This service allows sftp connections only.
Connection to 192.168.0.8 closed.
donnie@linux-0ro8:~>

好的,他不能这样做。那么,接下来让我们让 Max 通过 SFTP 登录,并验证他是否已经进入了/home/目录:

donnie@linux-0ro8:~> sftp max@192.168.0.8
max@192.168.0.8's password:
Connected to 192.168.0.8.
drwx------    7 1000     1000         4096 Nov  4 22:53 donnie
drwx------    5 1001     1001         4096 Oct 27 23:34 frank
drwx------    3 1003     1004         4096 Nov  4 22:43 katelyn
drwx------    2 1002     1003         4096 Nov  4 22:37 max
sftp>

现在,让我们看看他尝试cd/home/目录时会发生什么:

sftp> cd /etc
Couldn't stat remote file: No such file or directory
sftp>

所以,我们的 chroot 监狱确实起作用了。

动手实验——为sftpusers组设置 chroot 目录

对于本次实验,你可以使用 CentOS 虚拟机或 Ubuntu 虚拟机。你将添加一个组,然后配置sshd_config文件,使该组的成员只能通过 SFTP 登录,并且限制他们只能进入自己的目录。对于模拟客户端机器,你可以使用 macOS 或 Linux 桌面机器的终端,或者 Windows 机器上任何可用的 Bash Shell。我们开始吧:

  1. 创建sftpusers组:
sudo groupadd sftpusers
  1. 创建 Max 的用户账户并将其添加到sftpusers组。在 CentOS 或 AlmaLinux 上,执行以下操作:
sudo useradd -G sftpusers max
sudo passwd max

在 Ubuntu 上,执行以下操作:

sudo useradd -m -d /home/max -s /bin/bash -G sftpusers max
  1. 对于 Ubuntu,确保用户的主目录仅对该目录的用户设置读取、写入和执行权限。如果不是这种情况,执行以下操作:
sudo chmod 700 /home/*
  1. 使用你喜欢的文本编辑器打开/etc/ssh/sshd_config文件。找到以下这一行:
Subsystem sftp /usr/lib/openssh/sftp-server

将其更改为如下所示:

Subsystem sftp internal-sftp
  1. sshd_config文件的末尾,添加以下段落:
Match Group sftpusers
     ChrootDirectory /home
     AllowTCPForwarding no
     AllowAgentForwarding no
     X11Forwarding no
     ForceCommand internal-sftp
  1. 重新加载 SSH 配置。在 CentOS 或 AlmaLinux 上,执行以下操作:
sudo systemctl reload sshd

在 Ubuntu 上,执行以下操作:

sudo systemctl reload ssh
  1. 让 Max 尝试通过普通 SSH 登录,看看会发生什么:
ssh max@IP_Address_of_your_vm
  1. 现在,让 Max 通过 SFTP 登录。登录后,让他尝试cd/home/目录:
sftp max@IP_Address_of_your_vm

你已经完成了实验——恭喜!

现在你已经知道如何安全地配置 SFTP,接下来让我们看看如何安全地共享一个目录。

使用 SSHFS 共享目录

共享目录有几种方式。在企业环境中,你会遇到 网络文件系统NFS)、Samba 和各种分布式文件系统。SSHFS 在企业中使用得不如 NFS 或 Samba 多,但它仍然非常有用。它的优点是所有的网络流量默认都是加密的,这不同于 NFS 或 Samba。而且,除了安装 SSHFS 客户端程序并创建本地挂载点目录之外,它不需要其他配置。它特别适用于访问基于云的 虚拟私人服务器VPS)上的目录,因为它允许你直接在共享目录中创建文件,而不是使用 scpsftp 命令来传输文件。所以,如果你准备好了,咱们就开始吧。

实践实验 – 使用 SSHFS 共享目录

对于这个实验,我们将使用两台虚拟机。对于服务器,你可以使用任意一台虚拟机。客户端也是一样,唯一不同的是每个发行版的 SSHFS 客户端在不同的仓库中。这里是我指的:

  • 客户端已包含在标准的 Ubuntu 仓库中,因此你不需要做特别的操作来获取它。

  • 对于 CentOS 7 和 AlmaLinux 9,你需要使用常规的 yum installdnf install 命令安装 epel-release 包。

  • AlmaLinux 8 的 SSHFS 客户端位于其自己的 PowerTools 仓库中,默认情况下未启用。要启用它,打开 /etc/yum.repos.d/almalinux-powertools.repo 文件,使用你喜欢的文本编辑器。在 [powertools] 部分,找到 enabled=0 这一行,并将其改为 enabled=1

现在我们已经理清了一切,开始吧:

  1. 启动一台虚拟机作为服务器。(服务器端只需要这么做。)

  2. 在你用作客户端的另一台虚拟机上,在自己的主目录中创建一个挂载点目录,如下所示:

mkdir remote
  1. 在客户端虚拟机上,安装 SSHFS 客户端。在 Ubuntu 上,执行以下操作:
sudo apt install sshfs

在 CentOS 7 上,执行以下操作:

sudo yum install fuse-sshfs

在 AlmaLinux 8 或 9 上,执行以下操作:

sudo dnf install fuse-sshfs
  1. 从客户端机器上,挂载你在服务器上的主目录:
sshfs donnie@192.168.0.10: /home/donnie/remote

注意,如果没有指定共享目录,默认共享的是用于登录的用户账户的主目录。

  1. 使用 mount 命令验证目录是否已正确挂载。你应该在输出的底部看到新的共享挂载:
donnie@ubuntu-nftables:~$ mount
. . .
. . .
donnie@192.168.0.10: on /home/donnie/remote type fuse.sshfs (rw,nosuid,nodev,relatime,user_id=1000,group_id=1004)
  1. 进入 remote 目录并创建一些文件。验证它们是否实际出现在服务器上。

  2. 在服务器虚拟机的本地控制台中,在自己的主目录中创建一些文件。验证它们是否出现在客户端虚拟机的 remote/ 目录中。

你已经完成实验了,恭喜!

在这个实验中,我刚刚展示了如何从远程服务器挂载你自己的家目录。你还可以通过在sshfs命令中指定其他目录来挂载其他服务器目录。例如,假设我想挂载/maggie_files/目录,并将~/remote3/目录作为本地挂载点。(我之所以选这个名字,是因为 Maggie 猫正坐在我面前的地方,本该是键盘所在的位置。)只需这样做:

sshfs donnie@192.168.0.53:/maggie_files /home/donnie/remote3

你也可以通过向/etc/fstab文件添加条目,让远程目录每次启动客户端机器时自动挂载。但这通常不是一个好主意。如果服务器在启动客户端机器时不可用,可能会导致启动过程挂起。

好的,现在你已经看到如何使用 SSHFS 创建一个加密连接并挂载共享的远程目录。接下来,让我们从 Windows 桌面机器登录到服务器。

从 Windows 桌面远程连接

我知道,我们这些企鹅粉都希望只用 Linux,甚至只用 Linux。但在企业环境中,事情并不总是那么简单。你很可能需要从坐在隔间桌前的 Windows 10/11 桌面机器上管理 Linux 服务器。在第一章在虚拟环境中运行 Linux中,我展示了如何使用 Cygwin 或新的 Windows 10/11 Shell 远程连接到 Linux 虚拟机。你也可以使用这些技术连接到实际的 Linux 服务器。

但是,有些商店要求管理员使用终端程序,而不是像 Cygwin 这样的完整 Bash Shell。通常,这些商店会要求你在 Windows 机器上使用PuTTY

PuTTY 是一个免费的程序,你可以从这里下载:www.chiark.greenend.org.uk/~sgtatham/putty/latest.html

安装很简单。只需双击安装文件并按照安装向导的提示完成安装:

你可以从 Windows 10/11 的开始菜单中打开 PuTTY 用户手册:

连接到远程 Linux 机器很简单。只需输入机器的 IP 地址并点击Open

请注意,这也给你提供了保存会话的选项。所以,如果你需要管理多个服务器,你可以打开 PuTTY,点击你想连接的服务器名称,然后点击Open

如你所见,这比每次需要登录到服务器时手动输入ssh命令方便得多,并且可以避免你记住多个服务器的 IP 地址。(当然,你也可以通过 Cygwin 或 Windows 10 的 shell,通过为每台需要管理的 Linux 机器创建一个登录 Shell 脚本来实现同样的功能。)

无论哪种方式,你最终都会进入远程机器的 Bash Shell:

要设置密钥交换认证,使用 PuTTYgen 创建密钥对。唯一的小窍门是,你必须通过手动复制并粘贴密钥到服务器的authorized_keys文件中来传输公钥:

我已经给你介绍了 PuTTY 的基础知识,你可以阅读 PuTTY 手册,获取更多详细信息。

好的,我认为关于安全外壳套件的讨论就到这里。

总结

在这一章中,我们已经看到,安全外壳的默认配置并不像我们希望的那样安全,并且我们也了解了如何解决这个问题。我们看了如何设置基于密钥的认证和双因素认证,并探讨了可以加强 SSH 服务器安全的不同选项。我们还看了如何禁用弱加密算法,以及 RHEL 8/CentOS 8 和 RHEL 9/AlmaLinux 9 中新的系统级加密策略如何使这一过程变得更加简便。过程中,我们了解了如何设置访问控制,以及如何为不同用户、组和主机创建不同配置。在演示了如何将 SFTP 用户限制在其自己的家目录后,我们使用 SSHFS 共享了一个远程目录。最后,我们通过展示一种便捷的方法,从 Windows 桌面计算机登录到 Linux 服务器来总结本章内容。

显而易见的是,有些技术在这里没有提到,你可能在其他地方看到过它们的推荐。端口敲门和 Fail2Ban 是两种流行的技术,它们可以帮助控制对 SSH 服务器的访问。然而,只有在你允许基于密码的认证时才需要它们。如果你按照我在这里展示的方式设置了基于密钥的认证,那么你就不需要那些其他复杂的解决方案了。

在下一章,我们将深入探讨自由访问控制(discretionary access control)的主题。我们下次见。

问题

  1. 以下哪一项陈述是正确的?

A. 安全外壳(Secure Shell)在其默认配置下是完全安全的。

B. 允许 root 用户通过安全外壳跨互联网登录是安全的。

C. 安全外壳在其默认配置下是不安全的。

D. 使用安全外壳最安全的方式是通过用户名和密码登录。

  1. 为了符合最佳的安全实践,以下三项你会做哪些?

A. 确保所有用户都使用强密码通过安全外壳登录。

B. 让所有用户创建公钥/私钥对,并将公钥传输到他们想要登录的服务器。

C. 禁用通过用户名/密码登录的功能。

D. 确保 root 用户使用强密码。

E. 禁用 root 用户的登录权限。

  1. 以下哪一行在sshd_config文件中会导致僵尸网络不再扫描你的系统以寻找登录漏洞?

A. PasswordAuthentication no

B. PasswordAuthentication yes

C. PermitRootLogin yes

D. PermitRootLogin no

  1. 如何将 SFTP 用户限制在他或她指定的目录中?

A. 确保该用户目录的正确所有权和权限已设置。

B. 在sshd_config文件中,禁用该用户通过正常 SSH 登录的权限,并为该用户定义一个chroot目录。

C. 使用 TCP Wrappers 定义用户的限制。

D. 在服务器上使用全盘加密,以便 SFTP 用户只能访问他们自己的目录。

  1. 以下哪两个命令可以将你的私钥添加到会话密钥环中?

A. ssh-copy-id

B. exec /usr/bin/ssh-agent

C. exec /usr/bin/ssh-agent $SHELL

D. ssh-agent

E. ssh-agent $SHELL

F. ssh-add

  1. 以下哪项在 NIST 推荐算法的列表中?

A. RSA

B. ECDSA

C. Ed25519

  1. 以下哪项是为 Katelyn 创建自定义配置的正确指令?

A. User Match katelyn

B. Match katelyn

C. Match Account katelyn

D. Match User katelyn

  1. 创建~/.ssh/config文件时,该文件的权限值应为多少?

A. 600

B. 640

C. 644

D. 700

  1. 以下哪项加密策略提供了 RHEL 8/9 类发行版上最强的加密?

A. LEGACY

B. FIPS

C. DEFAULT

D. FUTURE

  1. 以下哪项标准定义了 NIST 当前推荐的加密算法?

A. FIPS 140-2

B. FIPS 140-3

C. CNSA

D. Suite B

进一步阅读

答案

  1. C

  2. B, C, E

  3. A

  4. B

  5. C, F

  6. C

  7. D

  8. A

  9. D

  10. C

第二部分:掌握文件和目录的访问控制(DAC)

本节将讨论通过设置适当的权限和所有权、以及使用扩展属性xattr)来保护敏感文件和目录。避免与设置用户 IDSUID)和设置组 IDSGID)相关的安全问题。

本节包含以下章节:

  • 第八章掌握自主访问控制

  • 第九章访问控制列表和共享目录管理

8 掌握自主访问控制

加入我们的书籍社区,加入 Discord

packt.link/SecNet

自主访问控制DAC)实际上意味着每个用户都有能力控制谁可以访问他们的文件。如果我想让系统中的其他所有用户都能访问我的主目录,我可以这么做。完成后,我可以控制谁可以访问每个具体的文件。在下一章中,我们将使用我们的 DAC 技能来管理共享目录,其中组成员可能需要不同级别的文件访问权限。

在你 Linux 职业生涯的这个阶段,你可能已经了解了通过设置文件和目录权限来控制访问的基础知识。在本章中,我们将回顾这些基础知识,然后探讨一些更高级的概念。

在本章中,我们将讨论以下主题:

  • 使用chown更改文件和目录的所有权

  • 使用chmod设置文件和目录的权限

  • SUID 和 SGID 设置在常规文件上的作用

  • 对不需要 SUID 和 SGID 权限的文件设置这些权限的安全影响

  • 如何使用扩展文件属性保护敏感文件

  • 保护系统配置文件

使用chown更改文件和目录的所有权

控制文件和目录的访问实际上就是确保合适的用户能够访问他们自己的文件和目录,并且每个文件和目录的权限设置得当,只有授权的用户能够访问它们。chown工具涵盖了这个方程式的第一部分。

chown的一个独特之处在于,即使你正在处理自己目录中的文件,你也必须拥有sudo权限才能使用它。你可以使用它更改文件或目录的用户、与文件或目录关联的组,或者同时更改两者。

首先,假设你拥有perm_demo.txt文件,并且你想将该文件的用户和组关联更改为另一个用户。在这种情况下,我将把文件的所有权从我自己更改为maggie

[donnie@localhost ~]$ ls -l perm_demo.txt
-rw-rw-r--. 1 donnie donnie 0 Nov  5 20:02 perm_demo.txt
[donnie@localhost ~]$ sudo chown maggie:maggie perm_demo.txt
[donnie@localhost ~]$ ls -l perm_demo.txt
-rw-rw-r--. 1 maggie maggie 0 Nov  5 20:02 perm_demo.txt
[donnie@localhost ~]$

maggie:maggie中的第一个maggie是你想要授予所有权的用户。冒号后的第二个maggie表示你希望文件与之关联的组。因为我正在将用户和组都更改为maggie,所以我可以省略第二个maggie,只写第一个maggie后加冒号,这样也能达到相同的结果:

sudo chown maggie: perm_demo.txt

只改变组关联而不改变用户时,只需列出组名,前面加冒号:

[donnie@localhost ~]$ sudo chown :accounting perm_demo.txt
[donnie@localhost ~]$ ls -l perm_demo.txt
-rw-rw-r--. 1 maggie accounting 0 Nov  5 20:02 perm_demo.txt
[donnie@localhost ~]$

最后,只改变用户而不改变组时,列出用户名时不需要冒号:

[donnie@localhost ~]$ sudo chown donnie perm_demo.txt
[donnie@localhost ~]$ ls -l perm_demo.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:02 perm_demo.txt
[donnie@localhost ~]$

这些命令在目录上与在文件上的作用是一样的。然而,如果你还想改变目录内容的所有权和/或组关联,并且同时改变目录本身的权限,可以使用-R选项,表示递归。在这种情况下,我只想将perm_demo_dir目录的组更改为accounting。让我们看看我们最初的情况:

[donnie@localhost ~]$ ls -ld perm_demo_dir
drwxrwxr-x. 2 donnie donnie 74 Nov  5 20:17 perm_demo_dir
[donnie@localhost ~]$ ls -l perm_demo_dir
total 0
-rw-rw-r--. 1 donnie donnie 0 Nov  5 20:17 file1.txt
-rw-rw-r--. 1 donnie donnie 0 Nov  5 20:17 file2.txt
-rw-rw-r--. 1 donnie donnie 0 Nov  5 20:17 file3.txt
-rw-rw-r--. 1 donnie donnie 0 Nov  5 20:17 file4.txt

现在,让我们运行命令并查看结果:

[donnie@localhost ~]$ sudo chown -R :accounting perm_demo_dir
[donnie@localhost ~]$ ls -ld perm_demo_dir
drwxrwxr-x. 2 donnie accounting 74 Nov  5 20:17 perm_demo_dir
[donnie@localhost ~]$ ls -l perm_demo_dir
total 0
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file1.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file2.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file3.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file4.txt
[donnie@localhost ~]$

这就是chown的全部内容。接下来,我们来改变一些权限。

使用chmod设置文件和目录的权限

在 Unix 和 Linux 系统上,你可以使用chmod工具来设置文件和目录的权限值。你可以为文件或目录的用户、与文件或目录关联的组等设置权限。三种基本权限如下:

  • r:表示读权限。

  • w:表示写权限。

  • x:表示可执行权限。你可以将其应用于任何类型的程序文件或目录。如果你将可执行权限应用于目录,授权用户将能够cd进入该目录。

如果你对一个文件执行ls -l,你会看到类似这样的内容:

-rw-rw-r--. 1 donnie donnie     804692 Oct 28 18:44 yum_list.txt

这一行的第一个字符表示文件的类型。在这种情况下,我们可以看到一个破折号,表示这是一个普通文件。(普通文件几乎是普通用户在日常使用中能够访问的所有文件类型。)接下来的三个字符rw-表示文件对用户(即拥有文件的用户)具有读写权限。接着,我们可以看到组的rw-权限和其他用户的r--权限。程序文件也会有可执行权限:

-rwxr-xr-x. 1 root root     62288 Nov 20  2015 xargs

在这里,我们可以看到xargs程序文件为所有人设置了可执行权限。

你可以使用chmod有两种方式来更改权限设置:

  • 符号方法

  • 数字方法

接下来我们将介绍这些方法。

使用符号方法设置权限

每当你以普通用户身份创建文件时,默认情况下,它会为用户和组设置读写权限,并为其他用户设置读权限。

chmod u+x donnie_script.sh
chmod g+x donnie_script.sh
chmod o+x donnie_script.sh
chmod u+x,g+x donnie_script.sh
chmod a+x donnie_script.sh

前三个命令为用户、组和其他用户添加了可执行权限。第四个命令为用户和组添加了可执行权限,而最后一个命令则为所有人添加了可执行权限(a表示所有人)。你还可以通过将+替换为-来移除可执行权限。最后,你还可以根据需要添加或移除读写权限。

尽管这种方法有时很方便,但也有一些缺陷;即它只能添加已经存在的权限,或者从已经存在的权限中移除权限。如果你需要确保某个特定文件的所有权限都被设置为某个特定值,那么符号方法可能会变得有些笨重。对于 Shell 脚本来说,这几乎是不可能的。在 Shell 脚本中,你需要添加很多额外的代码来判断哪些权限已经设置好了。而数字方法则能极大地简化我们的工作。

使用数字方法设置权限

使用数字方法时,你会使用一个八进制值来表示文件或目录的权限设置。对于rwx权限,分别赋予数字值421。你需要为用户、组和其他用户设置这些权限值,然后将它们相加,得到文件或目录的权限值:

用户 其他用户
rwx rwx rwx
421 421 421
7 7 7

所以,如果为所有人设置了所有权限,那么文件或目录的权限值将是777。如果我创建一个 Shell 脚本文件,默认情况下,它将具有标准的664权限,这意味着用户和组有读写权限,其他用户只有只读权限:

-rw-rw-r--. 1 donnie donnie 0 Nov  6 19:18 donnie_script.sh

提示

如果你使用 root 权限创建文件,无论是通过sudo还是通过 root 用户命令提示符,你会看到默认的权限设置是更为严格的644

假设我想让这个脚本可以执行,但我希望自己是全世界唯一可以对它做任何事情的人。为了实现这一点,我可以执行以下操作:

[donnie@localhost ~]$ chmod 700 donnie_script.sh
[donnie@localhost ~]$ ls -l donnie_script.sh
-rwx------. 1 donnie donnie 0 Nov  6 19:18 donnie_script.sh
[donnie@localhost ~]$

通过这一条简单的命令,我已移除组和其他用户的所有权限,并为自己设置了可执行权限。这就是数字方法在编写 Shell 脚本时如此方便的原因。

一旦你在使用数字方法一段时间后,看一个文件并计算它的数字权限值将变得像第二天性一样。与此同时,你可以使用stat命令并配合-c %a选项来查看文件的权限值。可以这样操作:

[donnie@localhost ~]$ stat -c %a yum_list.txt
664
[donnie@localhost ~]$
[donnie@localhost ~]$ stat -c %a donnie_script.sh
700
[donnie@localhost ~]$
[donnie@localhost ~]$ stat -c %a /etc/fstab
644
[donnie@localhost ~]$

如果你想一次性查看所有文件的数字权限,可以这样做:

[donnie@donnie-ca ~]$ stat -c '%n %a ' *
dropbear 755 
internal.txt 664 
password.txt 664 
pki-server.crt 664 
pki-server.p12 644 
yum_list.txt 664 
[donnie@donnie-ca ~]$

在这里,你可以看到命令末尾的通配符(*),这表示你想查看所有文件的设置。%n 表示你想查看文件名及其权限设置。由于我们使用了两个 -c 选项,所以必须将这两个选项括在一对单引号中。唯一需要注意的地方是,这个输出并没有显示这些项目中哪些是文件,哪些是目录。然而,由于目录需要可执行权限才能让人们 cd 进入,我们可以猜测 dropbear 可能是一个目录。不过,为了确保,可以使用 ls -l,如下所示:

[donnie@donnie-ca ~]$ ls -l
total 2180
-rwxr-xr-x. 1 donnie donnie  277144 Apr 22  2018 dropbear
-rw-rw-r--. 1 donnie donnie      13 Sep 19 13:32 internal.txt
-rw-rw-r--. 1 donnie donnie      11 Sep 19 13:42 password.txt
-rw-rw-r--. 1 donnie donnie    1708 Sep 19 14:41 pki-server.crt
-rw-r--r--. 1 root   root      1320 Sep 20 21:08 pki-server.p12
-rw-rw-r--. 1 donnie donnie 1933891 Sep 19 18:04 yum_list.txt
[donnie@donnie-ca ~]$

现在,让我们继续讲解一些非常特殊的权限设置。

在常规文件上使用 SUID 和 SGID

当常规文件设置了 SUID 权限时,任何访问该文件的人将拥有与文件所有者相同的权限。

为了演示这一点,假设 Maggie 是一个普通的、没有特权的用户,她想更改自己的密码。由于这是她自己的密码,她只需要使用单个命令 passwd,而不需要使用 sudo

[maggie@localhost ~]$ passwd
Changing password for user maggie.
Changing password for maggie.
(current) UNIX password:
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
[maggie@localhost ~]$

要更改密码,用户必须修改 /etc/shadow 文件。在我的 CentOS 和 AlmaLinux 机器上,shadow 文件的权限如下所示:

[donnie@localhost etc]$ ls -l shadow
----------. 1 root root 840 Nov  6 19:37 shadow
[donnie@localhost etc]$

在一台 Ubuntu 机器上,它们看起来是这样的:

donnie@ubuntu:/etc$ ls -l shadow
-rw-r----- 1 root shadow 1316 Nov  4 18:38 shadow
donnie@ubuntu:/etc$

无论如何,这些权限设置都不允许 Maggie 直接修改 shadow 文件。然而,通过更改她的密码,她能够修改 shadow 文件。那么,这是怎么回事呢?为了解答这个问题,让我们进入 /usr/bin/ 目录,查看 passwd 可执行文件的权限设置:

[donnie@localhost etc]$ cd /usr/bin
[donnie@localhost bin]$ ls -l passwd
-rwsr-xr-x. 1 root root 27832 Jun 10 2014 passwd
[donnie@localhost bin]$

对于用户权限,你会看到 rws 而不是 rwxs 表示这个文件具有 SUID 权限。由于该文件属于 root 用户,因此任何访问该文件的人都具有与 root 用户相同的权限。我们看到小写字母 s,意味着该文件对 root 用户也设置了可执行权限。由于 root 用户被允许修改 shadow 文件,因此任何使用 passwd 工具更改自己密码的人也可以修改 shadow 文件。

设置了 SGID 权限的文件,其组的可执行位置上会有一个 s

[donnie@localhost bin]$ ls -l write
-rwxr-sr-x. 1 root tty 19536 Aug  4 07:18 write
[donnie@localhost bin]$

tty 组相关的 write 工具允许用户通过命令行控制台向其他用户发送消息。拥有 tty 组权限的用户可以执行此操作。

SUID 和 SGID 权限的安全影响

尽管在可执行文件上设置 SUID 或 SGID 权限可能很有用,但我们应将其视为一种必要的恶习。虽然某些操作系统文件上设置 SUID 或 SGID 对 Linux 系统的运行至关重要,但当用户在其他文件上设置 SUID 或 SGID 时,它会成为一种安全风险。问题在于,如果入侵者找到一个属于 root 用户并且设置了 SUID 位的可执行文件,他们可以利用这个文件来攻击系统。在他们离开之前,他们可能会留下自己拥有 root 权限并且设置了 SUID 位的文件,这样下次遇到这个文件时,他们就能轻松重新进入系统。如果没有找到入侵者的 SUID 文件,入侵者仍然可以访问系统,即使原始问题已经被修复。

SUID 的数值是 4000,而 SGID 的数值是 2000。要在文件上设置 SUID,只需将 4000 添加到你本来会设置的权限值中。例如,如果你有一个权限值为 755 的文件,可以通过将权限值更改为 4755 来设置 SUID。(这会为用户提供读/写/执行权限,为组提供读/执行权限,为其他人提供读/执行权限,并且添加了 SUID 位。)

查找多余的 SUID 或 SGID 文件

一个快速的安全技巧是运行 find 命令来检查系统中的 SUID 和 SGID 文件。你还可以将输出保存到文本文件中,以便验证自运行命令以来是否添加了任何文件。你的命令看起来应该像这样:

sudo find / -type f \( -perm -4000 -o -perm -2000 \) > suid_sgid_files.txt

下面是详细说明:

  • /: 我们正在搜索整个文件系统。由于某些目录只有 root 用户才能访问,因此我们需要使用 sudo

  • -type f: 这意味着我们正在搜索常规文件,包括可执行程序文件和 shell 脚本。

  • -perm 4000: 我们正在寻找设置了 4000(即 SUID)权限位的文件。

  • -o: 或操作符。

  • -perm 2000: 我们正在寻找设置了 2000(即 SGID)权限位的文件。

  • >: 这里,我们将输出重定向到 suid_sgid_files.txt 文本文件中,使用 > 操作符。

请注意,两个 -perm 项需要合并为一个用括号括起来的术语。为了防止 Bash shell 错误解释括号字符,我们需要使用反斜杠转义每个括号字符。同时,我们需要在第一个括号字符和第一个 -perm 之间,以及在 2000 和最后一个反斜杠之间加上一个空格。另外,-type f-perm 之间的 and 操作符,即使没有插入 -a,也可以理解为存在。你将创建的文本文件应该如下所示:

/usr/bin/chfn
/usr/bin/chsh
/usr/bin/chage
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/su
/usr/bin/umount
/usr/bin/sudo
. . .
. . .
/usr/lib64/dbus-1/dbus-daemon-launch-helper

如果你想查看哪些文件是 SUID 或 SGID,可以选择添加 -ls 选项:

sudo find / -type f \( -perm -4000 -o -perm -2000 \) -ls > suid_sgid_files.txt

好的,你现在可能会说,嘿,Donnie,这个实在是太多要输入的内容了。我听懂了。幸运的是,有一个简写方式可以替代。由于4000 + 2000 = 6000,我们可以创建一个单一的表达式,来匹配 SUID(4000)或 SGID(2000)值,像这样:

sudo find / -type f -perm /6000 -ls > suid_sgid_files.txt

此命令中的/6000表示我们正在寻找40002000值。就我们的目的而言,这两者是唯一可以组合成6000的加数。

提示

在一些旧的参考资料中,你可能会看到+6000而不是/6000。使用+符号已经被弃用,并且不再有效。

现在,假设 Maggie 因为某种原因决定在她家目录下的 Shell 脚本文件上设置 SUID 位:

[maggie@localhost ~]$ chmod 4755 bad_script.sh
[maggie@localhost ~]$ ls -l
total 0
-rwsr-xr-x. 1 maggie maggie 0 Nov  7 13:06 bad_script.sh
[maggie@localhost ~]$

再次运行find命令,并将输出保存到另一个文本文件中。然后,对这两个文件执行diff操作,查看有哪些变化:

[donnie@localhost ~]$ diff suid_sgid_files.txt suid_sgid_files2.txt
17a18
> /home/maggie/bad_script.sh
[donnie@localhost ~]$

唯一的区别是添加了 Maggie 的 Shell 脚本文件。

动手实验 – 查找 SUID 和 SGID 文件

你可以在任意一台虚拟机上执行这个实验。你需要将find命令的输出保存到一个文本文件中。让我们开始吧:

  1. 在整个文件系统中搜索所有设置了 SUID 或 SGID 的文件,然后将输出保存到一个文本文件中:
 sudo find / -type f -perm /6000 -ls > suid_sgid_files.txt
  1. 登录到你系统上的任何其他用户账户,创建一个虚拟的 Shell 脚本文件。然后,设置该文件的 SUID 权限,退出后再重新登录到你自己的用户账户:
su - desired_user_account
touch some_shell_script.sh
chmod 4755 some_shell_script.sh
ls -l some_shell_script.sh
exit
  1. 再次运行find命令,并将输出保存到另一个文本文件中:
sudo find / -type f -perm /6000 -ls > suid_sgid_files_2.txt
  1. 查看两个文件之间的差异:
diff suid_sgid_files.txt suid_sgid_files_2.txt

实验到此结束 – 恭喜你!

防止在分区上使用 SUID 和 SGID

如前所述,你不希望用户为他们创建的文件分配 SUID 和 SGID 权限,因为这会带来安全风险。你可以通过使用nosuid选项挂载分区来防止 SUID 和 SGID 的使用。因此,我在前一章节创建的luks分区在/etc/fstab文件中的条目应该如下所示:

/dev/mapper/luks-6cbdce17-48d4-41a1-8f8e-793c0fa7c389 /secrets   xfs  nosuid  0 0

不同的 Linux 发行版在操作系统安装期间有不同的默认分区方案设置方式。通常,默认的做法是除了/boot/目录外,所有目录都位于/分区下。如果你设置了自定义的分区方案,你可以将/home/目录放在单独的分区中,并在其中设置nosuid选项。请记住,你不希望为/分区设置nosuid,否则你将得到一个无法正常工作的操作系统。

使用扩展文件属性来保护敏感文件

扩展文件属性是另一个帮助你保护敏感文件的工具。它们不能防止入侵者访问你的文件,但可以帮助你防止敏感文件被修改或删除。虽然有很多扩展属性,但我们只需要关注那些与文件安全相关的属性。

首先,让我们使用lsattr命令查看我们已经设置了哪些扩展属性。在 CentOS 或 AlmaLinux 机器上,你的输出应该是这样的:

[donnie@localhost ~]$ lsattr
---------------- ./yum_list.txt
---------------- ./perm_demo.txt
---------------- ./perm_demo_dir
---------------- ./donnie_script.sh
---------------- ./suid_sgid_files.txt
---------------- ./suid_sgid_files2.txt
[donnie@localhost ~]$

到目前为止,我的文件上还没有设置任何扩展属性。

在 Ubuntu 机器上,输出看起来会更像这样:

donnie@ubuntu:~$ lsattr
-------------e-- ./file2.txt
-------------e-- ./secret_stuff_dir
-------------e-- ./secret_stuff_for_frank.txt.gpg
-------------e-- ./good_stuff
-------------e-- ./secret_stuff
-------------e-- ./not_secret_for_frank.txt.gpg
-------------e-- ./file4.txt
-------------e-- ./good_stuff_dir
donnie@ubuntu:~$

我们不需要担心e属性,因为它仅表示该分区采用ext4文件系统格式。CentOS 和 AlmaLinux 没有设置这个属性,因为它们的分区是使用XFS文件系统格式化的。

本节我们将查看的两个属性如下:

  • a:你可以将文本追加到具有此属性的文件的末尾,但不能覆盖它。只有拥有适当sudo权限的人才能设置或删除此属性。

  • i:这会使文件变为不可变,只有拥有适当sudo权限的人才能设置或删除它。具有此属性的文件无法以任何方式删除或更改。也无法为具有此属性的文件创建硬链接。

要设置或删除属性,你需要使用chattr命令。你可以在文件上设置多个属性,但只有在合理的情况下才能这样做。例如,你不会在同一个文件上同时设置ai属性,因为i会覆盖a

让我们首先创建一个名为perm_demo.txt的文件,其中包含以下文本:

This is Donnie's sensitive file that he doesn't want to have overwritten.

现在,我们继续设置属性。

设置 a 属性

现在,我将设置a属性:

[donnie@localhost ~]$ sudo chattr +a perm_demo.txt
[sudo] password for donnie:
[donnie@localhost ~]$

你使用+来添加一个属性,使用-来删除它。并且,文件属于我且在我的主目录下并不重要。即便如此,我仍然需要sudo权限来添加或删除这个属性。

现在,让我们看看当我尝试覆盖这个文件时会发生什么:

[donnie@localhost ~]$ echo "I want to overwrite this file." > perm_demo.txt
-bash: perm_demo.txt: Operation not permitted
[donnie@localhost ~]$ sudo echo "I want to overwrite this file." > perm_demo.txt
-bash: perm_demo.txt: Operation not permitted
[donnie@localhost ~]$

无论是否拥有sudo权限,我都无法覆盖它。那么,如果我尝试向其中追加一些内容呢?

[donnie@localhost ~]$ echo "I want to append this to the end of the file." >> perm_demo.txt
[donnie@localhost ~]$

这次没有错误信息。我们来看看文件中的内容:

This is Donnie's sensitive file that he doesn't want to have overwritten.
I want to append this to the end of the file.

除了无法覆盖文件外,我还无法删除它:

[donnie@localhost ~]$ rm perm_demo.txt
rm: cannot remove ‘perm_demo.txt’: Operation not permitted
[donnie@localhost ~]$ sudo rm perm_demo.txt
[sudo] password for donnie:
rm: cannot remove ‘perm_demo.txt’: Operation not permitted
[donnie@localhost ~]$

所以,a属性生效了。然而,我决定不再需要这个属性了,因此我将把它移除:

[donnie@localhost ~]$ sudo chattr -a perm_demo.txt
[donnie@localhost ~]$ lsattr perm_demo.txt
---------------- perm_demo.txt
[donnie@localhost ~]$

设置 i 属性

当文件设置了i属性时,你唯一能做的事情就是查看它的内容。你不能更改它、移动它、删除它、重命名它,或者为它创建硬链接。我们来测试一下perm_demo.txt文件:

[donnie@localhost ~]$ sudo chattr +i perm_demo.txt
[donnie@localhost ~]$ lsattr perm_demo.txt
----i----------- perm_demo.txt
[donnie@localhost ~]$

现在是最有趣的部分:

[donnie@localhost ~]$ sudo echo "I want to overwrite this file." > perm_demo.txt
-bash: perm_demo.txt: Permission denied
[donnie@localhost ~]$ echo "I want to append this to the end of the file." >> perm_demo.txt
-bash: perm_demo.txt: Permission denied
[donnie@localhost ~]$ sudo echo "I want to append this to the end of the file." >> perm_demo.txt
-bash: perm_demo.txt: Permission denied
[donnie@localhost ~]$ rm -f perm_demo.txt
rm: cannot remove ‘perm_demo.txt’: Operation not permitted
[donnie@localhost ~]$ sudo rm -f perm_demo.txt
rm: cannot remove ‘perm_demo.txt’: Operation not permitted
[donnie@localhost ~]$ sudo rm -f perm_demo.txt

我还可以尝试几个其他命令,但你已经明白了思路。要移除i属性,执行如下操作:

[donnie@localhost ~]$ sudo chattr -i perm_demo.txt
[donnie@localhost ~]$ lsattr perm_demo.txt
---------------- perm_demo.txt
[donnie@localhost ~]$
实验室操作——设置与安全相关的扩展文件属性

对于这个实验,你需要创建一个perm_demo.txt文件,并填入你选择的文本。然后你将设置ia属性,并查看结果。我们开始吧:

  1. 使用你喜欢的文本编辑器,创建perm_demo.txt文件并输入一行文本。

  2. 查看文件的扩展属性:

lsattr perm_demo.txt
  1. 添加a属性:
sudo chattr +a perm_demo.txt
lsattr perm_demo.txt
  1. 尝试覆盖并删除文件:
echo "I want to overwrite this file." > perm_demo.txt
sudo echo "I want to overwrite this file." > perm_demo.txt
rm perm_demo.txt
sudo rm perm_demo.txt
  1. 现在,向文件追加一些内容:
echo "I want to append this line to the end of the file." >> perm_demo.txt
  1. 删除a属性并添加i属性:
sudo chattr -a perm_demo.txt
lsattr perm_demo.txt
sudo chattr +i perm_demo.txt
lsattr perm_demo.txt
  1. 重复步骤 4

  2. 另外,尝试更改文件名并创建一个硬链接到文件:

mv perm_demo.txt some_file.txt
sudo mv perm_demo.txt some_file.txt
ln ~/perm_demo.txt ~/some_file.txt
sudo ln ~/perm_demo.txt ~/some_file.txt
  1. 现在,尝试为文件创建一个符号链接:
ln -s ~/perm_demo.txt ~/some_file.txt

请注意,i属性不能让你创建硬链接到文件,但它允许你创建符号链接。

实验结束了 – 恭喜!

保护系统配置文件

如果你查看任何给定 Linux 发行版的配置文件,你会发现大多数文件属于 root 用户或某个指定的系统用户。你还会看到,这些文件大多数拥有所属用户的读写权限,并且其他人仅有读取权限。这意味着每个人及其兄弟都可以读取大多数 Linux 系统配置文件。例如,看看这个 Apache web 服务器配置文件:

[donnie@donnie-ca ~]$ cd /etc/httpd/conf
[donnie@donnie-ca conf]$ pwd
/etc/httpd/conf
[donnie@donnie-ca conf]$ ls -l httpd.conf 
-rw-r--r--. 1 root root 11753 Aug  6 09:44 httpd.conf
[donnie@donnie-ca conf]$

在“其他人”位置上有r权限时,所有登录的人,无论其权限级别如何,都可以查看 Apache 配置。

那么,这重要吗?这实际上取决于你的具体情况。某些配置文件,特别是一些基于 PHP 的内容管理系统CMS)的配置文件,可能包含明文密码,而 CMS 必须能够访问这些密码。在这种情况下,显然你需要限制对这些配置文件的访问。但是,其他那些不包含敏感密码的配置文件呢?

对于只有少数管理员能够访问的服务器,这并不是一个大问题。但如果是那些普通的、非管理员用户可以通过安全外壳(Secure Shell)远程访问的服务器呢?如果他们没有sudo权限,他们无法编辑任何配置文件,但他们可以查看配置文件,了解你的服务器是如何配置的。如果他们知道了配置情况,这会帮助他们在试图入侵系统时吗,如果他们决定这么做的话?

我必须承认,直到最近,我才开始认真思考这个问题。当时,我成为了一家专注于物联网IoT)设备安全的公司的 Linux 顾问。对于物联网设备,你需要担心的事情比普通服务器要多一些。普通服务器拥有高度的物理安全保护,而物联网设备通常几乎没有任何物理安全保护。你可能会在整个 IT 职业生涯中都没有亲眼见过一台服务器,除非你是那些被授权进入服务器机房内部的人之一。相反,物联网设备通常是暴露在外的。

我与一家物联网安全公司合作,该公司有一套指南,用于帮助加强物联网设备的防护,防止被攻击和泄露。其中一项要求是确保设备上的所有配置文件都设置为600权限。这意味着只有文件的所有者——通常是 root 用户或系统账户——可以读取这些文件。然而,配置文件非常多,你需要一个简便的方法来更改这些设置。你可以借助我们可信赖的伙伴——find工具来做到这一点。下面是操作方法:

sudo find / -iname '*.conf' -exec chmod 600 {} \;

下面是具体说明:

  • sudo find / -iname '*.conf':这正是你所期望的操作。它会在整个根文件系统(/)中执行一个不区分大小写的(-iname)搜索,查找所有.conf扩展名的文件。你可能还会查找其他扩展名的文件,如.ini.cfg。另外,由于find本身是递归的,你无需提供任何选项开关,就能让它搜索所有子目录。

  • -exec:这就是执行魔法的部分。它会自动在find找到的每个文件上执行以下命令,无需提示用户。如果你更愿意对find找到的每个文件回答yesno,可以使用-ok代替-exec

  • chmod 600 {} ;chmod 600是我们要执行的命令。当find找到每个文件时,文件名会被放置在一对大括号({})中。每个-exec语句都必须以分号结束。为了防止 Bash shell 错误地解释分号,我们需要用反斜杠对其进行转义。

如果你决定这么做,务必充分测试,确保没有破坏任何东西。大部分情况下,将配置文件设置为600权限是没问题的,但有些文件例外。我刚刚在我的一台虚拟机上执行了这个命令。让我们看看当我尝试 ping 一个互联网网站时会发生什么:

[donnie@donnie-ca ~]$ ping www.civicsandpolitics.com
ping: www.civicsandpolitics.com: Name or service not known
[donnie@donnie-ca ~]$

看起来很糟糕,但解释很简单。原因是为了能够访问互联网,机器必须能够找到 DNS 服务器。DNS 服务器信息可以在/etc/resolv.conf文件中找到,而我刚刚已移除其他用户的读取权限。如果没有其他用户的读取权限,只有具有 root 用户权限的人才能访问互联网。所以,除非你想限制只有 root 或sudo权限的用户才能访问互联网,否则你需要将resolv.conf的权限设置恢复为644

[donnie@donnie-ca etc]$ ls -l resolv.conf 
-rw-------. 1 root root 66 Sep 23 14:22 resolv.conf
[donnie@donnie-ca etc]$ sudo chmod 644 resolv.conf 
[donnie@donnie-ca etc]$

好的,我们再试一次:

[donnie@donnie-ca etc]$ ping www.civicsandpolitics.com
PING www.civicsandpolitics.com (64.71.34.94) 56(84) bytes of data.
64 bytes from 64.71.34.94: icmp_seq=1 ttl=51 time=52.1 ms
64 bytes from 64.71.34.94: icmp_seq=2 ttl=51 time=51.8 ms
64 bytes from 64.71.34.94: icmp_seq=3 ttl=51 time=51.2 ms
^C
--- www.civicsandpolitics.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 51.256/51.751/52.176/0.421 ms
[donnie@donnie-ca etc]$

看起来好多了。现在,让我们重启机器。当你重启时,你将看到以下输出:

所以,我还需要将/etc/locale.conf文件的权限设置恢复为644,以确保机器能够正常启动。正如我之前提到的,如果你选择对配置文件设置更严格的权限,务必先进行测试。

正如我之前所说,你可能并不总是觉得有必要更改配置文件的默认权限设置。但如果你确实需要这样做,现在你已经知道如何操作了。

提示

你绝对想和find工具建立友谊。它在命令行和 Shell 脚本中都非常有用,而且极其灵活。它的 man 页面写得很好,你可以从中学到几乎所有关于find的知识。要查看它,只需使用man find命令。

一旦你习惯了find,你就再也不想使用那些花哨的 GUI 类型的搜索工具了。

好的,我想这一章就到这里为止了。

总结

在这一章中,我们回顾了设置文件和目录所有权及权限的基础知识。然后,我们讨论了 SUID 和 SGID 在正确使用时的作用,以及将它们设置在我们自己的可执行文件上所带来的风险。接着,我们了解了两个与文件安全相关的扩展文件属性,最后介绍了一种便捷的省时技巧,用于删除系统配置文件中的全局可读权限。

在下一章,我们将扩展我们在这里学到的内容,探讨更高级的文件和目录访问技术。我们在那里见。

问题

  1. 以下哪个分区挂载选项会阻止设置文件的 SUID 和 SGID 权限?

A. nosgid

B. noexec

C. nosuid

D. nouser

  1. 以下哪个选项表示一个对用户和组有读写权限、对其他人只有只读权限的文件?

A. 775

B. 554

C. 660

D. 664

  1. 你想将somefile.txt文件的所有权和组关联更改为 Maggie。以下哪个命令可以做到这一点?

A. sudo chown maggie somefile.txt

B. sudo chown :maggie somefile.txt

C. sudo chown maggie: somefile.txt

D. sudo chown :maggie: somefile.txt

  1. 以下哪个是 SGID 权限的数字值?

A. 6000

B. 2000

C. 4000

D. 1000

  1. 哪个命令可以用来查看文件的扩展属性?

A. lsattr

B. ls -a

C. ls -l

D. chattr

  1. 以下哪个命令会在整个文件系统中搜索具有 SUID 或 SGID 权限设置的常规文件?

A. sudo find / -type f -perm \6000

B. sudo find / ( -perm -4000 -o -perm -2000 )

C. sudo find / -type f -perm -6000

D. sudo find / -type r -perm \6000

  1. 以下哪种说法是正确的?

A. 使用符号方法设置权限是所有情况中最好的方法。

B. 使用符号方法设置权限是 Shell 脚本中最好的方法。

C. 使用数字方法设置权限是在 Shell 脚本中最好的方法。

D. 使用哪种方法设置权限并不重要。

  1. 以下哪个命令会在一个用户和组拥有读/写/执行权限,其他人拥有读/执行权限的文件上设置 SUID 权限?

A. sudo chmod 2775 somefile

B. sudo chown 2775 somefile

C. sudo chmod 1775 somefile

D. sudo chmod 4775 somefile

  1. 以下哪个功能是通过在可执行文件上设置 SUID 权限来实现的?

A. 它允许任何用户使用该文件。

B. 它防止文件被意外删除。

C. 它允许“其他人”拥有与文件“用户”相同的权限。

D. 它允许“其他人”拥有与与文件关联的组相同的权限。

  1. 为什么用户不应该在自己的常规文件上设置 SUID 或 SGID 权限?

A. 它不必要地占用了更多硬盘空间。

B. 它可以防止在需要时删除文件。

C. 它可能允许某人更改文件。

D. 这可能允许入侵者破坏系统。

  1. 以下哪个find命令选项允许你在find找到的每个文件上自动执行命令,而无需提示?

A. -exec

B. -ok

C. -xargs

D. -do

  1. 判断对错:为了获得最佳安全性,系统上的每个.conf文件都应该始终使用600权限设置。

A. 正确

B. 错误

  1. 以下哪个是正确的陈述?

A. 通过使用nosuid选项挂载/分区,防止用户在文件上设置 SUID。

B. 对某些系统文件必须设置 SUID 权限,操作系统才能正常工作。

C. 可执行文件绝不应设置 SUID 权限。

D. 可执行文件应始终设置 SUID 权限。

  1. 以下哪两项是配置文件的安全问题?

A. 默认配置下,任何具有命令行访问权限的普通用户都可以编辑配置文件。

B. 某些配置文件可能包含敏感信息。

C. 默认配置下,任何具有命令行访问权限的普通用户都可以查看配置文件。

D. 服务器上的配置文件需要比物联网设备上的配置文件更多的保护。

进一步阅读

答案

  1. C

  2. D

  3. C

  4. B

  5. A

  6. A

  7. C

  8. D

  9. C

  10. D

  11. A

  12. B

  13. B

  14. B, C

第九章:9 访问控制列表与共享目录管理

加入我们的书籍社区,在 Discord 上与我们互动

packt.link/SecNet

在上一章中,我们回顾了自主访问控制DAC)的基础知识。正常的 Linux 文件和目录权限设置不够细粒度。使用访问控制列表ACL),我们可以精细调整权限,以获得我们真正需要的权限集。我们还可以利用这一能力来控制对共享目录中文件的访问。

本章的主题包括以下内容:

  • 为用户或组创建 ACL

  • 为目录创建继承的 ACL

  • 使用 ACL 掩码移除特定权限

  • 使用tar --acls选项来防止备份过程中丢失 ACL

  • 创建一个用户组并添加成员

  • 为组创建共享目录,并设置适当的权限

  • 设置共享目录上的 SGID 位和粘滞位

  • 使用 ACL 仅允许小组中的特定成员访问共享目录中的文件

为用户或组创建 ACL

正常的 Linux 文件和目录权限设置是可以的,但它们不够细粒度。使用 ACL,我们可以仅允许某个人访问文件或目录,或者允许多个不同的人访问文件或目录,并为每个人设置不同的权限。如果我们有一个对所有人开放的文件或目录,我们可以使用 ACL 为某个组或个人设置不同级别的访问权限。在本章的最后,我们将把所学的知识结合起来,以管理一个小组共享目录。

你可以使用getfacl查看文件或目录的 ACL。(注意,你不能用它一次性查看目录中的所有文件。)首先,我们使用getfacl查看是否已经在acl_demo.txt文件上设置了 ACL:

[donnie@localhost ~]$ touch acl_demo.txt
[donnie@localhost ~]$ getfacl acl_demo.txt
# file: acl_demo.txt
# owner: donnie
# group: donnie
user::rw-
group::rw-
other::r--
[donnie@localhost ~]$

在这里我们看到的只是正常的权限设置,因此没有 ACL。

设置 ACL 的第一步是将文件所有者以外的所有人权限移除。这是因为默认的权限设置允许组成员具有读/写权限,其他人具有读权限。所以,如果不移除这些权限,直接设置 ACL 是没有意义的:

[donnie@localhost ~]$ chmod 600 acl_demo.txt
[donnie@localhost ~]$ ls -l acl_demo.txt
-rw-------. 1 donnie donnie 0 Nov  9 14:37 acl_demo.txt
[donnie@localhost ~]$

使用setfacl设置 ACL 时,你可以为用户或组设置任意组合的读、写或执行权限。以我们的例子为例,假设我想让 Maggie 读取文件,但不允许她具有写或执行权限:

[donnie@localhost ~]$ setfacl -m u:maggie:r acl_demo.txt
[donnie@localhost ~]$ getfacl acl_demo.txt
# file: acl_demo.txt
# owner: donnie
# group: donnie
user::rw-
user:maggie:r--
group::---
mask::r--
other::---
[donnie@localhost ~]$ ls -l acl_demo.txt
-rw-r-----+ 1 donnie donnie 0 Nov  9 14:37 acl_demo.txt
[donnie@localhost ~]$

setfacl-m选项意味着我们即将修改 ACL。(好吧,在这种情况下是创建一个,但没关系。)u:表示我们正在为用户设置 ACL。然后列出该用户的名字,后跟冒号,以及我们想要授予该用户的权限列表。在此案例中,我们只允许 Maggie 读取权限。通过列出我们要应用此 ACL 的文件来完成该命令。getfacl的输出显示 Maggie 确实具有读取权限。最后,我们在ls -l的输出中看到,尽管我们已为该文件设置了600权限,但组仍然被列为具有读权限。但也有一个+符号,表示该文件有 ACL。当我们设置 ACL 时,ACL 的权限会作为组权限出现在ls -l中。

为了进一步说明,假设我想让 Frank 对这个文件具有读/写访问权限:

[donnie@localhost ~]$ setfacl -m u:frank:rw acl_demo.txt
[donnie@localhost ~]$ getfacl acl_demo.txt
# file: acl_demo.txt
# owner: donnie
# group: donnie
user::rw-
user:maggie:r--
user:frank:rw-
group::---
mask::rw-
other::---
[donnie@localhost ~]$ ls -l acl_demo.txt
-rw-rw----+ 1 donnie donnie 0 Nov  9 14:37 acl_demo.txt
[donnie@localhost ~]$

所以,我们可以将两个或更多不同的 ACL 分配给同一个文件。在ls -l输出中,我们看到为组设置了rw权限,这实际上只是我们在两个 ACL 中设置的权限的汇总。

我们可以通过将u:替换为g:来为组访问设置 ACL:

[donnie@localhost ~]$ getfacl new_file.txt
# file: new_file.txt
# owner: donnie
# group: donnie
user::rw-
group::rw-
other::r--
[donnie@localhost ~]$ chmod 600 new_file.txt
[donnie@localhost ~]$ setfacl -m g:accounting:r new_file.txt
[donnie@localhost ~]$ getfacl new_file.txt
# file: new_file.txt
# owner: donnie
# group: donnie
user::rw-
group::---
group:accounting:r--
mask::r--
other::---
[donnie@localhost ~]$ ls -l new_file.txt
-rw-r-----+ 1 donnie donnie 0 Nov  9 15:06 new_file.txt
[donnie@localhost ~]$

accounting组的成员现在可以读取这个文件。

为目录创建继承的 ACL

有时,你可能希望所有在共享目录中创建的文件都拥有相同的 ACL。我们可以通过对目录应用继承的 ACL 来实现这一点。不过,需要理解的是,尽管这听起来像个不错的主意,但以正常方式创建的文件将使文件对组设置读/写权限,并为其他人设置读权限。所以,如果你为一个用户通常创建文件的目录进行设置,最好的情况是创建一个 ACL,给某些人添加写或执行权限。或者,确保用户在创建文件时设置600权限,前提是用户确实需要限制对他们文件的访问。

另一方面,如果你正在创建一个在特定目录中创建文件的 shell 脚本,你可以包含chmod命令,以确保文件在创建时拥有必要的限制性权限,从而使你的 ACL 按预期工作。

为了演示,我们来创建new_perm_dir目录,并在其上设置继承的 ACL。我希望我的 shell 脚本在这个目录中创建的文件拥有读/写访问权限,并且 Frank 仅具有读权限。我不希望其他任何人能够读取这些文件:

[donnie@localhost ~]$ setfacl -m d:u:frank:r new_perm_dir
[donnie@localhost ~]$ ls -ld new_perm_dir
drwxrwxr-x+ 2 donnie donnie 26 Nov 12 13:16 new_perm_dir
[donnie@localhost ~]$ getfacl new_perm_dir
# file: new_perm_dir
# owner: donnie
# group: donnie
user::rwx
group::rwx
other::r-x
default:user::rwx
default:user:frank:r--
default:group::rwx
default:mask::rwx
default:other::r-x
[donnie@localhost ~]$

我所需要做的只是通过在u:frank前添加d:,使其成为继承的 ACL。我保留了目录的默认权限设置,这允许每个人读取目录。接下来,我将创建donnie_script.sh shell 脚本,该脚本将在该目录中创建一个文件,并为新文件的用户设置读/写权限:

#!/bin/bash
cd new_perm_dir
touch new_file.txt
chmod 600 new_file.txt
exit

使脚本可执行后,我将运行它并查看结果:

[donnie@localhost ~]$ ./donnie_script.sh
[donnie@localhost ~]$ cd new_perm_dir
[donnie@localhost new_perm_dir]$ ls -l
total 0
-rw-------+ 1 donnie donnie 0 Nov 12 13:16 new_file.txt
[donnie@localhost new_perm_dir]$ getfacl new_file.txt
# file: new_file.txt
# owner: donnie
# group: donnie
user::rw-
user:frank:r-- #effective:---
group::rwx #effective:---
mask::---
other::---
[donnie@localhost new_perm_dir]$

所以,new_file.txt已经创建,且权限设置正确,并且为 Frank 提供了读取权限。(我知道这是一个非常简化的例子,但你明白我的意思。)

通过使用 ACL 掩码删除特定权限

你可以使用-x选项从文件或目录中删除 ACL。让我们回到之前创建的acl_demo.txt文件,并删除 Maggie 的 ACL:

[donnie@localhost ~]$ setfacl -x u:maggie acl_demo.txt
[donnie@localhost ~]$ getfacl acl_demo.txt
# file: acl_demo.txt
# owner: donnie
# group: donnie
user::rw-
user:frank:rw-
group::---
mask::rw-
other::---
[donnie@localhost ~]$

所以,Maggie 的 ACL 消失了。但-x选项会删除整个 ACL,即使这不是你真正想要的。如果你有一个具有多个权限设置的 ACL,你可能只想删除其中一个权限,保留其他权限。在这里,我们看到 Frank 仍然拥有他的 ACL,允许他读写访问。现在,假设我们想要删除写权限,同时仍然允许他保留读权限。为此,我们需要应用一个掩码:

[donnie@localhost ~]$ setfacl -m m::r acl_demo.txt
[donnie@localhost ~]$ ls -l acl_demo.txt
-rw-r-----+ 1 donnie donnie 0 Nov  9 14:37 acl_demo.txt
[donnie@localhost ~]$ getfacl acl_demo.txt
# file: acl_demo.txt
# owner: donnie
# group: donnie
user::rw-
user:frank:rw-            #effective:r--
group::---
mask::r--
other::---
[donnie@localhost ~]$

m::r在 ACL 上设置了只读掩码。运行getfacl显示,Frank 仍然拥有读写 ACL,但旁边的注释显示他的有效权限是只读。因此,Frank 的写权限现在已被删除。而且,如果我们为其他用户设置了 ACL,这个掩码也会以相同的方式影响他们。

使用 tar 的--acls选项来防止在备份过程中丢失 ACL

如果你需要使用tar来备份一个文件或最后两个文件:

[donnie@localhost ~]$ cd perm_demo_dir
[donnie@localhost perm_demo_dir]$ ls -l
total 0
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file1.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file2.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file3.txt
-rw-rw-r--. 1 donnie accounting 0 Nov  5 20:17 file4.txt
-rw-rw----+ 1 donnie donnie     0 Nov  9 15:19 frank_file.txt
-rw-rw----+ 1 donnie donnie     0 Nov 12 12:29 new_file.txt
[donnie@localhost perm_demo_dir]$

现在,我将不使用--acls进行备份:

[donnie@localhost perm_demo_dir]$ cd
[donnie@localhost ~]$ tar cJvf perm_demo_dir_backup.tar.xz perm_demo_dir/
perm_demo_dir/
perm_demo_dir/file1.txt
perm_demo_dir/file2.txt
perm_demo_dir/file3.txt
perm_demo_dir/file4.txt
perm_demo_dir/frank_file.txt
perm_demo_dir/new_file.txt
[donnie@localhost ~]$

看起来不错,对吧?啊,但外表可能会欺骗你。看看当我删除目录,然后从备份中恢复时会发生什么:

[donnie@localhost ~]$ rm -rf perm_demo_dir/
[donnie@localhost ~]$ tar xJvf perm_demo_dir_backup.tar.xz
perm_demo_dir/
. . .
[donnie@localhost ~]$ cd perm_demo_dir/
[donnie@localhost perm_demo_dir]$ ls -l
total 0
-rw-rw-r--. 1 donnie donnie 0 Nov 5 20:17 file1.txt
-rw-rw-r--. 1 donnie donnie 0 Nov 5 20:17 file2.txt
-rw-rw-r--. 1 donnie donnie 0 Nov 5 20:17 file3.txt
-rw-rw-r--. 1 donnie donnie 0 Nov 5 20:17 file4.txt
-rw-rw----. 1 donnie donnie 0 Nov 9 15:19 frank_file.txt
-rw-rw----. 1 donnie donnie 0 Nov 12 12:29 new_file.txt
[donnie@localhost perm_demo_dir]$

我甚至不需要使用getfacl来查看 ACL 已经从perm_demo_dir目录及其所有文件中消失,因为它们的+符号现在都没有了。现在,让我们看看当我使用--acls选项时会发生什么。首先,我将向你展示该目录及其唯一文件的 ACL 已设置:

[donnie@localhost ~]$ ls -ld new_perm_dir
drwxrwxr-x+ 2 donnie donnie 26 Nov 13 14:01 new_perm_dir
[donnie@localhost ~]$ ls -l new_perm_dir
total 0
-rw-------+ 1 donnie donnie 0 Nov 13 14:01 new_file.txt
[donnie@localhost ~]$

现在,我将使用带有--acls选项的tar命令:

[donnie@localhost ~]$ tar cJvf new_perm_dir_backup.tar.xz new_perm_dir/ --acls
new_perm_dir/
new_perm_dir/new_file.txt
[donnie@localhost ~]$

我现在将删除new_perm_dir目录并从备份中恢复它。就像我们之前做的那样,我们将使用--acls选项:

[donnie@localhost ~]$ rm -rf new_perm_dir/
[donnie@localhost ~]$ tar xJvf new_perm_dir_backup.tar.xz --acls
new_perm_dir/
new_perm_dir/new_file.txt
[donnie@localhost ~]$ ls -ld new_perm_dir
drwxrwxr-x+ 2 donnie donnie 26 Nov 13 14:01 new_perm_dir
[donnie@localhost ~]$ ls -l new_perm_dir
total 0
-rw-------+ 1 donnie donnie 0 Nov 13 14:01 new_file.txt
[donnie@localhost ~]$

+符号的存在表明 ACL 在备份和恢复过程中保留了下来。唯一稍微有点棘手的是,你必须在备份和恢复时都使用--acls选项。如果你在其中任何一个过程中省略了此选项,你将丢失 ACL。

创建用户组并向其中添加成员

到目前为止,我一直在自己的家目录中做演示,仅仅是为了展示基本概念。但最终的目标是向你展示如何利用这些知识做一些更实际的事情,比如控制文件

假设我们想为—你猜对了—市场部门的成员创建一个marketing组:

[donnie@localhost ~]$ sudo groupadd marketing
[sudo] password for donnie:
[donnie@localhost ~]$

现在让我们添加一些成员。我们可以通过三种不同的方式来实现:

  • 在创建用户账户时添加成员。

  • 使用usermod来添加已有用户账户的成员。

  • 编辑/etc/group文件。

在创建用户账户时添加成员

首先,我们可以在创建用户帐户时通过 useradd-G 选项将成员添加到组中。在 Red Hat、AlmaLinux 或 CentOS 上,命令应该是这样的:

[donnie@localhost ~]$ sudo useradd -G marketing cleopatra
[sudo] password for donnie:
[donnie@localhost ~]$ groups cleopatra
cleopatra : cleopatra marketing
[donnie@localhost ~]$

在 Debian/Ubuntu 上,命令应该是这样的:

donnie@ubuntu3:~$ sudo useradd -m -d /home/cleopatra -s /bin/bash -G marketing cleopatra
donnie@ubuntu3:~$ groups cleopatra
cleopatra : cleopatra marketing
donnie@ubuntu3:~$

当然,我还需要按正常方式为 Cleopatra 设置密码:

[donnie@localhost ~]$ sudo passwd cleopatra

使用 usermod 将现有用户添加到组中

好消息是,这在 Red Hat/CentOS/AlmaLinux 或 Debian/Ubuntu 上的效果是一样的:

[donnie@localhost ~]$ sudo usermod -a -G marketing maggie
[sudo] password for donnie:
[donnie@localhost ~]$ groups maggie
maggie : maggie marketing
[donnie@localhost ~]$

在这个例子中,-a 选项并不是必需的,因为 Maggie 还不是其他任何二级组的成员。不过,如果她已经属于其他组,-a 选项是必要的,否则会覆盖任何现有的组信息,从而把她从之前的组中移除。

这种方法在 Ubuntu 系统中尤其方便,因为在 Ubuntu 系统中创建加密的主目录时,必须使用 adduser。(正如我们在前一章节看到的,adduser 在创建帐户时并没有给你机会将用户添加到组中。)

通过编辑 /etc/group 文件将用户添加到组中

这个最终的方法是一个很好的“作弊”方式,可以加快将多个现有用户添加到组中的过程。首先,只需用你喜欢的文本编辑器打开 /etc/group 文件,并查找定义你想要添加成员的组的那一行:

. . .
marketing:x:1005:cleopatra,maggie
. . .

所以,我已经将 Cleopatra 和 Maggie 添加到这个组中了。接下来,我们编辑这个文件,添加几个新的成员:

. . .
marketing:x:1005:cleopatra,maggie,vicky,charlie
. . .

完成后,保存文件并退出编辑器。

对每个用户运行 groups 命令将显示我们的“作弊”方式效果很好:

[donnie@localhost etc]$ sudo vim group
[donnie@localhost etc]$ groups vicky
vicky : vicky marketing
[donnie@localhost etc]$ groups charlie
charlie : charlie marketing
[donnie@localhost etc]$

这种方法在你需要一次性将许多成员添加到组时非常方便。

创建共享目录

在我们的场景中,下一步是创建一个共享目录,供我们市场部门的所有成员使用。这个问题实际上会引发一些争议。有些人喜欢把共享目录放在文件系统的根目录下,而另一些人则喜欢把它放在 /home/ 目录下。还有一些人有其他的偏好。但实际上,这主要是个人喜好和/或公司政策的问题。除此之外,目录放在哪儿并不重要。为了简化问题,我会直接在文件系统的根目录下创建这个目录:

[donnie@localhost ~]$ cd /
[donnie@localhost /]$ sudo mkdir marketing
[sudo] password for donnie:
[donnie@localhost /]$ ls -ld marketing
drwxr-xr-x. 2 root root 6 Nov 13 15:32 marketing
[donnie@localhost /]$

新的目录属于 root 用户。它的权限设置为 755,允许所有人读取和执行,只有 root 用户拥有写入权限。我们真正想要的是仅允许市场部门的成员访问这个目录。我们将首先更改所有权和组关联,然后设置正确的权限:

[donnie@localhost /]$ sudo chown nobody:marketing marketing
[donnie@localhost /]$ sudo chmod 770 marketing
[donnie@localhost /]$ ls -ld marketing
drwxrwx---. 2 nobody marketing 6 Nov 13 15:32 marketing
[donnie@localhost /]$

在这种情况下,我们并没有一个特定的用户希望拥有该目录,我们也不希望 root 用户拥有它。所以,将所有权分配给 nobody 伪用户账户为我们提供了解决方案。然后,我将 770 权限值分配给该目录,这样所有 marketing 组成员可以读/写/执行,而其他人无法访问。现在,让我们让我们的一位组员登录,看看她能否在这个目录中创建文件:

[donnie@localhost /]$ su - vicky
Password:
[vicky@localhost ~]$ cd /marketing
[vicky@localhost marketing]$ touch vicky_file.txt
[vicky@localhost marketing]$ ls -l
total 0
-rw-rw-r--. 1 vicky vicky 0 Nov 13 15:41 vicky_file.txt
[vicky@localhost marketing]$

好的,这样可以正常工作,除了一个小问题。文件属于 Vicky,这是应该的。但它也和 Vicky 的个人组关联。为了对这些共享文件进行最佳的访问控制,我们需要将它们与 marketing 组关联。接下来我们来解决这个问题。

在共享目录上设置 SGID 位和粘滞位

我之前告诉过你,将 SUID 或 SGID 权限设置在文件上,特别是可执行文件上,是有一定的安全风险的。但在共享目录上设置 SGID 是完全安全且非常有用的。

目录上的 SGID 行为与文件上的 SGID 行为完全不同。在目录上,SGID 会使任何人创建的文件都与目录关联的组相关联。因此,考虑到 SGID 权限值是 2000,让我们在我们的 marketing 目录上设置 SGID:

[donnie@localhost /]$ sudo chmod 2770 marketing
[sudo] password for donnie:
[donnie@localhost /]$ ls -ld marketing
drwxrws---. 2 nobody marketing 28 Nov 13 15:41 marketing
[donnie@localhost /]$

组的可执行位置上的 s 表示命令执行成功。现在让我们让 Vicky 重新登录以创建另一个文件:

[donnie@localhost /]$ su - vicky
Password:
Last login: Mon Nov 13 15:41:19 EST 2017 on pts/0
[vicky@localhost ~]$ cd /marketing
[vicky@localhost marketing]$ touch vicky_file_2.txt
[vicky@localhost marketing]$ ls -l
total 0
-rw-rw-r--. 1 vicky marketing 0 Nov 13 15:57 vicky_file_2.txt
-rw-rw-r--. 1 vicky vicky     0 Nov 13 15:41 vicky_file.txt
[vicky@localhost marketing]$

Vicky 的第二个文件与 marketing 组关联,这正是我们希望的。为了好玩,让我们让 Charlie 也这么做:

[donnie@localhost /]$ su - charlie
Password:
[charlie@localhost ~]$ cd /marketing
[charlie@localhost marketing]$ touch charlie_file.txt
[charlie@localhost marketing]$ ls -l
total 0
-rw-rw-r--. 1 charlie marketing 0 Nov 13 15:59 charlie_file.txt
-rw-rw-r--. 1 vicky   marketing 0 Nov 13 15:57 vicky_file_2.txt
-rw-rw-r--. 1 vicky   vicky     0 Nov 13 15:41 vicky_file.txt
[charlie@localhost marketing]$

再次强调,Charlie 的文件与 marketing 组关联。但由于某种原因,大家都不理解,Charlie 非常不喜欢 Vicky,出于纯粹的恶意决定删除她的文件:

[charlie@localhost marketing]$ rm vicky*
rm: remove write-protected regular empty file ‘vicky_file.txt’? y
[charlie@localhost marketing]$ ls -l
total 0
-rw-rw-r--. 1 charlie marketing 0 Nov 13 15:59 charlie_file.txt
[charlie@localhost marketing]$

系统抱怨 Vicky 的原始文件是写保护的,因为它仍然与她的个人组关联。但系统仍然允许 Charlie 删除它,即使没有 sudo 权限。而且,由于第二个文件与 marketing 组关联,Charlie 拥有写访问权限,因此系统允许他毫无阻碍地删除它。

好的。所以,Vicky 抱怨这件事并试图让 Charlie 被解雇。但我们的勇敢管理员有了一个更好的主意。他决定设置粘滞位,防止这种情况再次发生。由于 SGID 位的值为 2000,而粘滞位的值为 1000,我们可以将两者相加得到 3000

[donnie@localhost /]$ sudo chmod 3770 marketing
[sudo] password for donnie:
[donnie@localhost /]$ ls -ld marketing
drwxrws--T. 2 nobody marketing 30 Nov 13 16:03 marketing
[donnie@localhost /]$

可执行文件的其他用户位置上的 T 表示粘滞位已设置。由于 T 是大写的,我们知道其他用户的执行权限没有被设置。设置粘滞位会阻止组成员删除其他人的文件。让我们让 Vicky 来展示她尝试报复 Charlie 时会发生什么:

[donnie@localhost /]$ su - vicky
Password:
Last login: Mon Nov 13 15:57:41 EST 2017 on pts/0
[vicky@localhost ~]$ cd /marketing
[vicky@localhost marketing]$ ls -l
total 0
-rw-rw-r--. 1 charlie marketing 0 Nov 13 15:59 charlie_file.txt
[vicky@localhost marketing]$ rm charlie_file.txt
rm: cannot remove ‘charlie_file.txt’: Operation not permitted
[vicky@localhost marketing]$ rm -f charlie_file.txt
rm: cannot remove ‘charlie_file.txt’: Operation not permitted
[vicky@localhost marketing]$ ls -l
total 0
-rw-rw-r--. 1 charlie marketing 0 Nov 13 15:59 charlie_file.txt
[vicky@localhost marketing]$

即使使用 -f 选项,Vicky 仍然无法删除 Charlie 的文件。Vicky 在此系统上没有 sudo 权限,因此尝试删除是无效的。

使用 ACL 访问共享目录中的文件

目前所有 marketing 组的成员都可以读/写其他组成员的文件。限制文件访问权限仅限于特定的组成员,是我们已经讨论过的两步过程。

设置权限并创建 ACL

首先,Vicky 设置正常权限,只允许她自己对文件具有读/写权限。然后,她将创建一个 ACL,允许 Cleopatra 阅读该文件:

[vicky@localhost marketing]$ echo "This file is only for my good friend, Cleopatra." > vicky_file.txt
[vicky@localhost marketing]$ chmod 600 vicky_file.txt
[vicky@localhost marketing]$ setfacl -m u:cleopatra:r vicky_file.txt
[vicky@localhost marketing]$ ls -l
total 4
-rw-rw-r--. 1 charlie marketing 0 Nov 13 15:59 charlie_file.txt
-rw-r-----+ 1 vicky marketing 49 Nov 13 16:24 vicky_file.txt
[vicky@localhost marketing]$ getfacl vicky_file.txt
# file: vicky_file.txt
# owner: vicky
# group: marketing
user::rw-
user:cleopatra:r--
group::---
mask::r--
other::---
[vicky@localhost marketing]$

这里没有什么是你没有见过的。Vicky 只是撤销了组和其他人的所有权限,并设置了一个只允许 Cleopatra 阅读文件的 ACL。让我们看看 Cleopatra 是否真的能读取该文件:

[donnie@localhost /]$ su - cleopatra
Password:
[cleopatra@localhost ~]$ cd /marketing
[cleopatra@localhost marketing]$ ls -l
total 4
-rw-rw-r--. 1 charlie marketing 0 Nov 13 15:59 charlie_file.txt
-rw-r-----+ 1 vicky marketing 49 Nov 13 16:24 vicky_file.txt
[cleopatra@localhost marketing]$ cat vicky_file.txt
This file is only for my good friend, Cleopatra.
[cleopatra@localhost marketing]$

到目前为止,一切顺利。但 Cleopatra 能写入该文件吗?让我们看看:

[cleopatra@localhost marketing]$ echo "You are my friend too, Vicky." >> vicky_file.txt
-bash: vicky_file.txt: Permission denied
[cleopatra@localhost marketing]$

Cleopatra 无法做到这一点,因为 Vicky 在 ACL 中只授予她读取权限。

但是,现在那个狡猾的 Charlie 怎么样?他想要窥探其他用户的文件。让我们看看 Charlie 能否做到:

[donnie@localhost /]$ su - charlie
Password:
Last login: Mon Nov 13 15:58:56 EST 2017 on pts/0
[charlie@localhost ~]$ cd /marketing
[charlie@localhost marketing]$ cat vicky_file.txt
cat: vicky_file.txt: Permission denied
[charlie@localhost marketing]$

所以,确实只有 Cleopatra 能访问 Vicky 的文件,而且只能读取。

实践实验 – 创建一个共享组目录

对于这个实验,你将整合本章所学的内容,创建一个用于小组的共享目录。你可以在任何虚拟机上执行此操作:

  1. 在任何虚拟机上,创建 sales 组:
sudo groupadd sales
  1. 创建用户 mimimrgraymommy,并在创建帐户时将他们添加到 sales 组中。

在 CentOS 或 AlamaLinux 上,执行以下操作:

sudo useradd -G sales mimi
sudo useradd -G sales mrgray
sudo useradd -G sales mommy

在 Ubuntu 上,执行以下操作:

sudo useradd -m -d /home/mimi -s /bin/bash -G sales mimi
sudo useradd -m -d /home/mrgray -s /bin/bash -G sales mrgray
sudo useradd -m -d /home/mommy -s /bin/bash -G sales mommy
  1. 为每个用户分配一个密码。

  2. 在文件系统的根目录下创建 sales 目录。设置适当的所有权和权限,包括 SGID 和粘滞位:

sudo mkdir /sales
sudo chown nobody:sales /sales
sudo chmod 3770 /sales
ls -ld /sales
  1. 以 Mimi 的身份登录,并让她创建一个文件:
su - mimi
cd /sales
echo "This file belongs to Mimi." > mimi_file.txt
ls -l
  1. 让 Mimi 为她的文件设置 ACL,只允许 Gray 先生读取它。然后,让 Mimi 注销:
chmod 600 mimi_file.txt
setfacl -m u:mrgray:r mimi_file.txt
getfacl mimi_file.txt
ls -l
exit
  1. 让 Gray 先生登录,看看他能对 Mimi 的文件做什么。然后,让 Gray 先生创建自己的文件并注销:
su - mrgray
cd /sales
cat mimi_file.txt
echo "I want to add something to this file." >> mimi_file.txt    
echo "Mr. Gray will now create his own file." > mr_gray_file.txt        
ls -l
exit
  1. 现在,Mommy 将登录并试图通过窥探其他用户的文件以及尝试删除它们来制造混乱:
su - mommy
cat mimi_file.txt
cat mr_gray_file.txt
rm -f mimi_file.txt
rm -f mr_gray_file.txt
exit
  1. 实验结束。

总结

在这一章中,我们看到如何将 DAC 提升到更高的水平。我们首先了解了如何创建和管理 ACL,以对文件和目录提供更细粒度的访问控制。接着,我们了解了如何为特定目的创建用户组,并将成员添加到其中。然后,我们学习了如何使用 SGID 位、粘滞位和 ACL 来管理共享组目录。

但有时,DAC 可能不足以完成任务。对于这种情况,我们还可以使用强制访问控制(MAC),这一部分内容将在下一章讨论。到时候见。

问题

  1. 在为共享目录中的文件创建 ACL 时,必须首先做什么才能使 ACL 生效?

A. 删除文件对所有人(除了用户)的所有普通权限。

B. 确保文件的权限值为644

C. 确保组内的每个人对文件具有读/写权限。

D. 确保为文件设置 SUID 权限。

  1. 设置 SGID 权限在共享组目录中的好处是什么?

A. 无。它是一个安全风险,永远不应执行。

B. 它防止组成员删除彼此的文件。

C. 这样,每个在该目录中创建的文件都会与目录关联的组相关联。

D. 它赋予任何访问该目录的人与目录用户相同的权限。

  1. 以下哪条命令会为marketing共享组目录设置正确的权限,且设置了 SGID 和粘滞位?

A. sudo chmod 6770 marketing

B. sudo chmod 3770 marketing

C. sudo chmod 2770 marketing

D. sudo chmod 1770 marketing

  1. 以下哪种setfacl选项可以用来从 ACL 中仅删除一个特定权限?

A. -xB. -r

C. -w

D. m: :

E. -m

F. x: :

  1. 以下哪项陈述是正确的?

A. 使用tar时,必须在归档创建和提取时都使用--acls选项,以便保留归档文件中的 ACL。

B. 使用tar时,只需在归档创建时使用--acls选项即可保留归档文件中的 ACL。

C. 使用tar时,ACL 会在归档文件中自动保留。

D. 使用tar时,无法保留归档文件中的 ACL。

  1. 以下哪两种方法不是将用户 Lionel 添加到sales组的有效方法?

A. sudo useradd -g sales lionel

B. sudo useradd -G sales lionel

C. sudo usermod -g sales lionel

D. sudo usermod -G sales lionel

E. 通过手动编辑/etc/group文件。

  1. 创建继承的 ACL 时会发生什么?

A. 使用该继承的 ACL 创建的目录中的每个文件都会与该目录相关联的组相同。

B. 使用该继承的 ACL 创建的目录中的每个文件都会继承该 ACL。

C. 使用该继承 ACL 创建的每个文件都会与目录具有相同的权限设置。

D. 在该目录中创建的每个文件都会设置粘滞位。

  1. 以下哪条命令可用于授予用户 Frank 只读权限?

A. chattr -m u:frank:r somefile.txt

B. aclmod -m u:frank:r somefile.txt

C. getfacl -m u:frank:r somefile.txt

D. setfacl -m u:frank:r somefile.txt

  1. 你刚刚在共享组目录中执行了ls -l命令。如何从中判断是否为文件设置了 ACL?

A. 设置 ACL 的文件会在权限设置的开头显示+

B. 设置了 ACL 的文件,其权限设置的开头会有-符号。

C. 设置了 ACL 的文件,其权限设置的末尾会有+符号。

设置了 ACL 的文件,其权限设置的末尾会有-符号。

E. ls -l 命令会显示该文件的 ACL。

  1. 以下哪一项可以用来查看somefile.txt文件的 ACL?

A. getfacl somefile.txt

B. ls -l somefile.txt

C. ls -a somefile.txt

D. viewacl somefile.txt

进一步阅读

答案

  1. A

  2. C

  3. B

  4. D

  5. A

  6. A, C

  7. B

  8. D

  9. C

  10. A

第三部分:高级系统强化技术

本节将教你如何使用强制访问控制MAC)、安全配置文件和进程隔离技术来强化 Linux 系统。使用 auditd 和日志服务审计 Linux 系统。

本节包含以下章节:

  • 第十章使用 SELinux 和 AppArmor 实施强制访问控制

  • 第十一章内核强化与进程隔离

  • 第十二章扫描、审计与强化

  • 第十三章日志记录与日志安全

  • 第十四章漏洞扫描与入侵检测

  • 第十五章使用 fapolicyd 阻止应用程序

  • 第十六章忙碌的蜜蜂的安全提示与技巧

10 使用 SELinux 和 AppArmor 实施强制访问控制

加入我们的书籍社区,加入 Discord

packt.link/SecNet

正如我们在前几章中看到的,自主访问控制DAC)允许用户控制谁可以访问他们的文件和目录。但如果你的公司需要对谁可以访问什么内容进行更多的管理控制呢?为此,我们需要某种强制访问控制MAC)。

我知道的最好解释 DAC 和 MAC 区别的方式是回想起我的海军时光。那时,我在潜艇上服役,我需要拥有绝密许可才能执行工作。在 DAC 下,我可以把一本绝密书带到餐厅,交给一位没有相应许可的厨师。但在 MAC 下,有一些规则阻止了我这样做。在操作系统中,情况差不多。

有多种不同的 MAC 系统可供 Linux 使用。我们将在本章中介绍的两个是 SELinux 和 AppArmor。我们将看看它们是什么,如何配置它们以及如何进行故障排除。

本章将涵盖以下主题:

  • 什么是 SELinux,它如何帮助系统管理员

  • 如何为文件和目录设置安全上下文

  • 如何使用 setroubleshoot 进行 SELinux 问题排查

  • 查看 SELinux 策略以及如何创建自定义策略

  • 什么是 AppArmor,它如何帮助系统管理员

  • 查看 AppArmor 策略

  • 使用 AppArmor 命令行工具

  • 故障排除 AppArmor 问题

  • 利用恶意 Docker 容器攻击系统

让我们从了解 SELinux 开始,看看它如何帮助你。

SELinux 如何帮助系统管理员

SELinux 是一个免费的开源软件项目,由美国国家安全局(NSA)开发。虽然它理论上可以安装在任何 Linux 发行版上,但只有 Red Hat 类型的发行版已经预先设置并启用它。它利用 Linux 内核模块中的代码,以及扩展的文件系统属性,确保只有授权的用户和进程才能访问敏感文件或系统资源。SELinux 有三种使用方式:

  • 它可以帮助防止入侵者利用系统漏洞。

  • 它可以用来确保只有具有适当安全权限的用户才能访问带有安全分类标签的文件。

  • 除了 MAC 之外,SELinux 还可以作为一种基于角色的访问控制类型。

在本章中,我只会讲解这三种用法中的第一种,因为它是 SELinux 最常用的使用方式。还有一点是,讲解这三种用法需要写一本书,而我这里没有足够的空间。

提示

如果你通过了这部分关于 SELinux 的介绍后,觉得仍然需要更多的 SELinux 信息,你可以在 Packt Publishing 网站上找到专门讲解这一主题的整本书和课程。

那么,SELinux 如何能帮助繁忙的系统管理员呢?你可能还记得几年前,Shellshock 漏洞登上全球头条新闻的事情。本质上,Shellshock 是一个 Bash shell 漏洞,允许入侵者突破系统并通过获得 root 权限来利用它。对于运行 SELinux 的系统,虽然坏人仍然能够突破系统,但 SELinux 会阻止他们成功执行攻击。

SELinux 还是另一种机制,可以帮助保护用户家目录中的数据。如果你的机器设置为网络文件系统(NFS)服务器、Samba 服务器或 web 服务器,SELinux 将阻止这些守护进程访问用户的家目录,除非你明确配置 SELinux 以允许这种行为。

在 web 服务器上,你可以使用 SELinux 来阻止恶意的 CGI 脚本或 PHP 脚本的执行。如果你不需要你的服务器运行 CGI 或 PHP 脚本,你可以在 SELinux 中禁用它们。

在没有 SELinux 的情况下,使用 Docker 对普通用户来说,轻而易举地就能突破 Docker 容器并获得主机的 root 级别访问权限。正如我们将在本章末尾看到的,SELinux 是一种用于加固运行 Docker 容器的服务器的有用工具。

所以现在,你可能在想,每个人都会使用这样一个伟大的工具,对吧?可惜事实并非如此。刚开始时,SELinux 因为操作起来困难而声名狼藉,很多管理员干脆禁用它。事实上,很多网络教程或者 YouTube 上的教程都会把禁用 SELinux 作为第一步。在这一部分,我想向你展示,情况已经有所改善,SELinux 不再值得它曾经的坏名声。

设置文件和目录的安全上下文

可以将 SELinux 看作是一个改进的标签系统。它通过扩展的文件属性将标签(称为安全上下文)添加到文件和目录上。它还会将相同类型的标签,称为域,添加到系统进程上。要查看这些上下文和域,在你的 CentOS 或 AlmaLinux 机器上使用 -Z 选项与 lsps 命令。例如,我自己主目录中的文件和目录看起来如下所示:

[donnie@localhost ~]$ ls -Z
drwxrwxr-x. donnie donnie unconfined_u:object_r:user_home_t:s0 acl_demo_dir
-rw-rw-r--. donnie donnie unconfined_u:object_r:user_home_t:s0 yum_list.txt
[donnie@localhost ~]$

我的系统上的进程大致如下所示:

[donnie@localhost ~]$ ps -Z
LABEL                             PID TTY          TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1322 pts/0 00:00:00 bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 3978 pts/0 00:00:00 ps
[donnie@localhost ~]$

现在,让我们来拆解一下。在 ls -Zps -Z 命令的输出中,我们会看到以下几部分:

  • SELinux 用户:在这两种情况下,SELinux 用户都是通用的 unconfined_u

  • SELinux 角色:在 ls -Z 示例中,我们看到角色是 object_r,而在 ps -Z 示例中,它是 unconfined_r

  • 类型:在 ls -Z 输出中是 user_home_t,在 ps -Z 输出中是 unconfined_t

  • 敏感度:在 ls -Z 输出中它是 s0,而在 ps -Z 输出中,它是 s0-s0

  • 类别:我们在 ls -Z 输出中看不到类别,但在 ps -Z 输出中看到了 c0.c1023

在前面提到的所有安全上下文和安全域组件中,当前唯一对我们感兴趣的是类型。对于本章而言,我们只关注正常的 Linux 管理员需要知道的内容,以防止入侵者利用系统,而类型是我们需要使用的唯一组件。其他所有组件只有在我们设置高级、安全分类基础的访问控制和基于角色的访问控制时才会发挥作用。

好的,以下是一个简化的解释,说明这如何帮助 Linux 管理员维护安全性。我们希望系统进程仅访问我们允许它们访问的对象。(系统进程包括如 web 服务器守护进程、FTP 守护进程、Samba 守护进程和安全外壳守护进程等。对象包括如文件、目录和网络端口等内容。)为了实现这一点,我们将为所有进程和对象分配一个类型。然后,我们会创建策略,定义哪些进程类型可以访问哪些对象类型。

幸运的是,每当你安装任何 Red Hat 类型的发行版时,几乎所有的繁重工作都已经为你完成。Red Hat 类型的发行版都自带 SELinux 并且已经启用了定向策略。可以把这个定向策略看作是一个相对宽松的策略,它允许普通桌面用户坐在计算机前进行工作,而不需要调整任何 SELinux 设置。但如果你是服务器管理员,你可能会发现需要调整这个策略,以允许服务器守护进程执行你需要它们做的事情。

默认安装的目标策略是普通 Linux 管理员在日常工作中会使用的策略。如果你查看 CentOS 虚拟机的仓库,你会看到还有其他几种策略,这些我们在本书中不会涉及。

安装 SELinux 工具

出于某种我永远也无法理解的奇怪原因,管理 SELinux 所需的工具默认并不会安装,尽管 SELinux 本身会安装。因此,在 CentOS 虚拟机上你首先需要做的就是安装这些工具。

在 CentOS 7 上,运行此命令:

sudo yum install setools policycoreutils policycoreutils-python

在 CentOS 8 上,运行此命令:

sudo dnf install setools policycoreutils policycoreutils-python-utils

本章稍后的 使用 setroubleshoot 进行故障排除 部分,我们将探讨如何使用 setroubleshoot 来帮助诊断 SELinux 问题。为了在此过程中看到一些酷炫的错误信息,先安装 setroubleshoot,并通过重启 auditd 守护进程来激活它。(没有 setroubleshoot 守护进程,因为 setroubleshoot 是由 auditd 守护进程来控制的。)如下安装 setroubleshoot

对于 CentOS 7,请使用以下命令:

sudo yum install setroubleshoot
sudo service auditd restart

对于 AlmaLinux 8 和 9,请使用以下命令:

sudo dnf install setroubleshoot
sudo service auditd restart

在 Red Hat 系统中,我们必须处理的一个小巧的 systemd 问题是,不能使用普通的 systemctl 命令来停止或重启 auditd 守护进程。然而,传统的 service 命令是有效的。出于某些我无法理解的原因,Red Hat 的开发者将 auditd 服务文件配置为禁用常规的 systemd 处理方式。

根据你在安装 CentOS 或 AlmaLinux 时选择的安装类型,你可能已经安装了 setroubleshoot,也可能没有。为了确保这一点,请运行命令安装它。如果 setroubleshoot 已经存在,安装它不会有任何不良影响。

现在你已经拥有了开始所需的一切。让我们看看 SELinux 对繁忙的 Web 服务器管理员能做些什么。

创建带有 SELinux 启用的网页内容文件

现在,让我们看看如果你的网页内容文件设置了错误的 SELinux 类型会发生什么。首先,我们将在 CentOS 虚拟机上安装、启用并启动 Apache Web 服务器。(请注意,包含 --now 选项可以让我们在一步操作中同时启用和启动守护进程。)在 CentOS 7 上执行以下操作:

sudo yum install httpd
sudo systemctl enable --now httpd

在 CentOS 8 上,请使用以下命令:

sudo dnf install httpd
sudo systemctl enable --now httpd

如果你还没做过此操作,请配置防火墙以允许访问 Web 服务器:

[donnie@localhost ~]$ sudo firewall-cmd --permanent --add-service=http
success
[donnie@localhost ~]$ sudo firewall-cmd --reload
success
[donnie@localhost ~]$

当我们查看 Apache 进程的 SELinux 信息时,我们会看到以下内容:

[donnie@localhost ~]$ ps ax -Z | grep httpd
system_u:system_r:httpd_t:s0     3689 ?        Ss     0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0     3690 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0     3691 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0     3692 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0     3693 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
system_u:system_r:httpd_t:s0     3694 ?        S      0:00 /usr/sbin/httpd -DFOREGROUND
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 3705 pts/0 R+   0:00 grep --color=auto httpd

如我之前所说,我们对用户或角色不感兴趣。然而,我们对类型感兴趣,在此情况下是 httpd_t

在 Red Hat 系统中,我们通常将网页内容文件放置在 /var/www/html/ 目录中。我们来看一下该 html 目录的 SELinux 上下文:

[donnie@localhost www]$ pwd
/var/www
[donnie@localhost www]$ ls -Zd html/
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html/
[donnie@localhost www]$

类型是 httpd_sys_content,所以可以推断 httpd 守护进程应该能够访问这个目录。现在目录是空的,让我们进入其中并创建一个简单的 index 文件:

[donnie@localhost www]$ cd html
[donnie@localhost html]$ pwd
/var/www/html
[donnie@localhost html]$ sudo vim index.html

这是我将放入文件中的内容:

<html>
<head>
<title>
Test of SELinux
</title>
</head>
<body>
Let's see if this SELinux stuff really works!
</body>
</html>

好的,正如我所说,这很简单,因为我的 HTML 手动编码技能已经不如从前。但这仍然足以满足我们当前的需求。

查看 SELinux 上下文,我们发现该文件与 html 目录具有相同的类型:

[donnie@localhost html]$ ls -Z
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 index.html
[donnie@localhost html]$

现在,我可以从我可靠的 OpenSUSE 工作站的网页浏览器访问这个页面:

现在,让我们看看,如果我决定在自己的主目录中创建内容文件,并将它们移动到 html 目录,会发生什么情况。首先,让我们看一下新文件的 SELinux 上下文:

[donnie@localhost ~]$ pwd
/home/donnie
[donnie@localhost ~]$ ls -Z index.html
-rw-rw-r--. donnie donnie unconfined_u:object_r:user_home_t:s0 index.html
[donnie@localhost ~]$

现在,上下文类型为 user_home_t,这表明我是在我的主目录中创建的文件。我现在将文件移动到 html 目录,覆盖旧文件:

[donnie@localhost ~]$ sudo mv index.html /var/www/html/
[sudo] password for donnie:
[donnie@localhost ~]$ cd /var/www/html
[donnie@localhost html]$ ls -Z
-rw-rw-r--. donnie donnie unconfined_u:object_r:user_home_t:s0 index.html
[donnie@localhost html]$

即使我将文件移到 /var/www/html 目录,SELinux 类型仍然与用户的主目录相关联。现在,我将去我的主机的浏览器刷新页面:

所以,我遇到一个小问题。分配给我的文件类型与 httpd 守护进程的类型不匹配,因此 SELinux 不允许 httpd 进程访问该文件。

提示

如果我将文件复制到 html 目录,而不是移动它,SELinux 上下文会自动更改,以匹配目标目录的上下文。

修复错误的 SELinux 上下文

好的,我有这个网页内容文件,没人能访问,而且我现在真的不想创建一个新的。那么,我该怎么办呢?实际上,我们有三种不同的工具来解决这个问题:

  • chcon

  • restorecon

  • semanage

让我们看看这两种方法。

使用 chcon

有两种方法可以使用 chcon 来修复文件或目录上的错误 SELinux 类型。第一种是手动指定正确的类型:

[donnie@localhost html]$ sudo chcon -t httpd_sys_content_t index.html
[sudo] password for donnie:
[donnie@localhost html]$ ls -Z
-rw-rw-r--. donnie donnie unconfined_u:object_r:httpd_sys_content_t:s0 index.html
[donnie@localhost html]$

我们可以使用 chcon 来更改上下文的任何部分,但正如我一再强调的,我们只关心类型,它通过 -t 选项来更改。你可以在 ls -Z 输出中看到命令执行成功。

使用 chcon 的另一种方式是引用一个具有正确上下文的文件。为了演示,我将 index.html 文件的类型改回主目录类型,并在 /var/www/html/ 目录下创建了一个新文件:

[donnie@localhost html]$ ls -Z
-rw-rw-r--. donnie donnie unconfined_u:object_r:user_home_t:s0 index.html
-rw-r--r--. root   root   unconfined_u:object_r:httpd_sys_content_t:s0 some_file.html
[donnie@localhost html]$

如你所见,我在此目录中创建的任何文件都会自动拥有正确的 SELinux 上下文设置。现在,让我们使用这个新文件作为参考,来为 index.html 文件设置正确的上下文:

[donnie@localhost html]$ sudo chcon --reference some_file.html index.html
[sudo] password for donnie:
[donnie@localhost html]$ ls -Z
-rw-rw-r--. donnie donnie unconfined_u:object_r:httpd_sys_content_t:s0 index.html
-rw-r--r--. root   root   unconfined_u:object_r:httpd_sys_content_t:s0 some_file.html
[donnie@localhost html]$

因此,我使用了--reference选项,并指定了我想用作参考的文件。要更改的文件在命令的末尾列出。现在,这一切都很好,但我想找一个不需要那么多输入的更简单的方法。毕竟,我已经是个老人了,不想让自己太累。那么,让我们来看看restorecon工具。

使用 restorecon

使用restorecon非常简单。只需要输入restorecon,后跟需要更改的文件名称。再次提醒,我已将index.html文件的上下文更改回了主目录类型。不过这次,我使用restorecon来设置正确的类型:

[donnie@localhost html]$ ls -Z
-rw-rw-r--. donnie donnie unconfined_u:object_r:user_home_t:s0 index.html
[donnie@localhost html]$ sudo restorecon index.html
[donnie@localhost html]$ ls -Z
-rw-rw-r--. donnie donnie unconfined_u:object_r:httpd_sys_content_t:s0 index.html
[donnie@localhost html]$

就是这样。

提示

你也可以使用chconrestorecon来更改整个目录及其内容的上下文。对于这两者,只需使用-R选项。以下是一个示例:

sudo chcon -R -t httpd_sys_content_t /var/www/html/
sudo restorecon -R /var/www/html/

(记住:-R表示递归。)

还有最后一件事需要处理,尽管它实际上不会影响我们访问此文件的能力。那就是,我需要将文件的所有权更改为 Apache 用户:

[donnie@localhost html]$ sudo chown apache: index.html
[sudo] password for donnie:
[donnie@localhost html]$ ls -l
total 4
-rw-rw-r--. 1 apache apache 125 Nov 22 16:14 index.html
[donnie@localhost html]$

现在让我们来看一下最后一个工具——semanage

使用 semanage

在我刚才介绍的场景中,chconrestorecon都可以很好地满足你的需求。活动 SELinux 策略规定了某些目录中应该具有的安全上下文。只要你在活动 SELinux 策略中定义的目录内使用chconrestorecon,就没问题。但假设你在其他地方创建了一个目录,想要用它来提供网页内容文件。你需要在该目录及其中的所有文件上设置httpd_sys_content_t类型。然而,如果使用chconrestorecon,这种更改在系统重启后将不会生效。为了使更改永久生效,你需要使用semanage

假设,出于某种奇怪的原因,我想从我在/home/目录下创建的一个目录中提供网页内容:

[donnie@localhost home]$ pwd
/home
[donnie@localhost home]$ sudo mkdir webdir
[sudo] password for donnie:
[donnie@localhost home]$ ls -Zd webdir
drwxr-xr-x. root root unconfined_u:object_r:home_root_t:s0 webdir
[donnie@localhost home]$

由于我必须使用sudo权限来创建目录,因此它与根用户的home_root_t类型相关联,而不是正常的user_home_dir_t类型。我在此目录中创建的任何文件将具有相同的类型:

[donnie@localhost webdir]$ ls -Z
-rw-r--r--. root root unconfined_u:object_r:home_root_t:s0 index.html
[donnie@localhost webdir]$

下一步是使用semanage为该目录和httpd_sys_content_t类型添加永久映射到活动策略的上下文列表:

[donnie@localhost home]$ sudo semanage fcontext -a -t httpd_sys_content_t "/home/webdir(/.*)?"
[donnie@localhost home]$ ls -Zd /home/webdir
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 /home/webdir
[donnie@localhost home]$

好的,接下来是semanage命令的分解:

  • fcontext:由于semanage有许多用途,我们必须指定我们希望处理的是文件上下文。

  • -a:此选项指定我们正在向活动 SELinux 策略的上下文列表中添加一个新记录。

  • -t:此选项指定我们希望映射到新目录的类型。在此情况下,我们正在创建一个新的映射,类型为httpd_sys_content

  • /home/webdir(/.*)?:这一堆无意义的东西称为正则表达式。我不能在这里详细讨论正则表达式的细节,因此可以说,正则表达式是我们用来匹配文本模式的一种语言。(是的,我说的是“is”而不是“are”,因为正则表达式是整体语言的名称。)在这种情况下,我必须使用这个特定的正则表达式,以便使semanage命令递归,因为semanage没有-R选项开关。通过这个正则表达式,我想表达的是,我希望在这个目录中创建的任何内容都与目录本身具有相同的 SELinux 类型。

最后一步是在此目录上执行restorecon -R以确保设置了正确的标签:

[donnie@localhost home]$ sudo restorecon -R webdir
[donnie@localhost home]$ ls -Zd /home/webdir
drwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 /home/webdir
[donnie@localhost home]$

是的,我知道。你看着这个说,“但是这个ls -Zd输出看起来和你做完semanage命令后一样。” 你说得对。在运行semanage命令后,似乎类型已经设置正确了。但是semanage-fcontext的 man 页面说无论如何都要运行restorecon,所以我就这么做了。

提示

要了解如何使用semanage管理安全上下文的更多信息,请参考相关的 man 页面,输入man semanage-fcontext

实验动手操作 - SELinux 类型强制

在这个实验中,您将安装 Apache Web 服务器和适当的 SELinux 工具。然后,如果您准备好了,让我们开始吧:

  1. 在 CentOS 7 上安装 Apache 以及所有必需的 SELinux 工具:
sudo yum install httpd setroubleshoot setools policycoreutils policycoreutils-python

在 CentOS 8 上,使用以下命令:

sudo dnf install httpd setroubleshoot setools policycoreutils policycoreutils-python-utils
  1. 通过重新启动auditd服务激活setroubleshoot
sudo service auditd restart
  1. 启用并启动 Apache 服务并在防火墙上打开端口80
sudo systemctl enable --now httpd
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
  1. /var/www/html/目录中,创建一个包含以下内容的index.html文件:
<html>
   <head>
      <title>SELinux Test Page</title>
   </head>
   <body>
      This is a test of SELinux.
   </body>
</html>
  1. 查看关于index.html文件的信息:
ls -Z index.html
  1. 在主机机器的 Web 浏览器中,导航到 CentOS 虚拟机的 IP 地址。您应该能够查看页面。

  2. 通过将index.html文件的类型更改为不正确的类型来引发 SELinux 违规:

sudo chcon -t tmp_t index.html
ls -Z index.html
  1. 返回到主机机器的 Web 浏览器并重新加载文档。您现在应该看到一个Forbidden消息。

  2. 使用restorecon将文件更改回正确的类型:

sudo restorecon index.html
  1. 在主机机器的 Web 浏览器中重新加载页面。您现在应该能够查看页面。

  2. 实验结束。

现在我们已经看过如何使用基本的 SELinux 命令,让我们来看一个很酷的工具,可以让故障排除变得更容易。

使用 setroubleshoot 进行故障排除

所以,现在您可能会想,“当我无法访问应该能访问的东西时,如何知道这是一个 SELinux 问题?” 啊,我很高兴你问了。

查看 setroubleshoot 消息

每当发生违反 SELinux 规则的事件时,它会被记录在 /var/log/audit/audit.log 文件中。虽然有工具可以直接读取该日志,但要诊断 SELinux 问题,使用 setroubleshoot 要更好。setroubleshoot 的优点在于,它将 audit.log 文件中那些难以理解的、加密的 SELinux 消息翻译成简单、自然的语言。它发送到 /var/log/messages 文件的消息甚至包含了如何解决问题的建议。为了展示这一点,我们回到之前的问题:/var/www/html/ 目录中的某个文件被分配了错误的 SELinux 类型。当然,我们很快就知道问题出在哪,因为该目录中只有一个文件,使用简单的 ls -Z 就能显示出问题。然而,让我们暂时忽略这一点,假设我们不知道问题是什么。通过在 less 中打开 /var/log/messages 文件并搜索 sealert,我们会找到如下消息:

Nov 26 21:30:21 localhost python: SELinux is preventing httpd from open access on the file /var/www/html/index.html.#012#012*****  Plugin restorecon (92.2 confidence) suggests   ************************#012#012If you want to fix the label. #012/var/www/html/index.html default label should be httpd_sys_content_t.#012Then you can run restorecon.#012Do#012# /sbin/restorecon -v /var/www/html/index.html#012#012*****  Plugin catchall_boolean (7.83 confidence) suggests   ******************#012#012If you want to allow httpd to read user content#012Then you must tell SELinux about this by enabling the 'httpd_read_user_content' boolean.#012#012Do#012setsebool -P httpd_read_user_content 1#012#012*****  Plugin catchall (1.41 confidence) suggests   **************************#012#012If you believe that httpd should be allowed open access on the index.html file by default.#012Then you should report this as a bug.#012You can generate a local policy module to allow this access.#012Do#012allow this access for now by executing:#012# ausearch -c 'httpd' --raw | audit2allow -M my-httpd#012# semodule -i my-httpd.pp#012

这条消息的第一行告诉我们问题所在。它说 SELinux 阻止我们访问 /var/www/html/index.html 文件,因为它设置了错误的类型。然后,它给出了几个修复问题的建议,第一个建议是运行 restorecon 命令,正如我已经向你展示过的那样。

提示

在阅读这些 setroubleshoot 消息时,有一个好的经验法则需要记住,那就是消息中的第一个建议通常是解决问题的方法。

使用图形化 setroubleshoot 工具

到目前为止,我只讨论了在文本模式服务器上使用 setroubleshoot。毕竟,在 Linux 服务器上运行文本模式非常常见,因此我们所有的 Linux 用户都必须是文本模式的战士。但在安装了桌面界面的桌面系统或服务器上,当 setroubleshoot 检测到问题时,会有一个图形化工具自动提醒你:

点击该警报图标,你将看到如下内容:

点击 Troubleshoot 按钮,你将看到一个修复问题的建议列表:

请注意,这些截图来自 CentOS 7 机器,但在 AlmaLinux 8 或 AlmaLinux 9 机器上看起来也是一样的。

正如通常情况下的图形界面工具一样,它大多数情况下是自解释的,所以你应该不会有问题理解它。

在宽松模式下进行故障排除

如果你正在处理一个像我刚才展示的简单问题,那么你可以假设可以安全地按照 setroubleshoot 消息中第一个建议的操作。但是,问题可能会变得更加复杂,可能会涉及到不止一个问题。在这种情况下,你需要使用宽松模式。

当你首次安装 Red Hat 或 CentOS 系统时,SELinux 会处于强制模式,这是默认设置。这意味着 SELinux 会实际阻止违反活动 SELinux 策略的操作。这也意味着,如果你在尝试执行某个操作时遇到多个 SELinux 问题,SELinux 会在首次违规后阻止该操作的执行。发生这种情况时,SELinux 不会看到剩余的问题,这些问题也不会出现在 messages 日志文件中。如果你在强制模式下尝试故障排除这些问题,你就像那只追着自己尾巴转的狗。你会原地打转,什么也做不成。

在宽容模式下,SELinux 允许违反策略的操作发生,但会记录它们。通过切换到宽容模式并执行你之前看到的问题,当这些被禁止的操作发生时,setroubleshoot 会将所有这些操作记录在 messages 文件中。通过这种方式,你可以更清楚地了解需要做些什么才能让系统正常工作。

首先,让我们使用 getenforce 来验证当前的模式:

[donnie@localhost ~]$ sudo getenforce
Enforcing
[donnie@localhost ~]$

现在,让我们暂时将系统置于宽容模式:

[donnie@localhost ~]$ sudo setenforce 0
[donnie@localhost ~]$ sudo getenforce
Permissive
[donnie@localhost ~]$

当我说临时时,我的意思是这将只持续到你重启系统。重启后,你会回到强制模式。此外,请注意,setenforce 后面的 0 表示我正在设置宽容模式。完成故障排除后,要恢复强制模式,只需将 0 替换为 1

[donnie@localhost ~]$ sudo setenforce 1
[donnie@localhost ~]$ sudo getenforce
Enforcing
[donnie@localhost ~]$

我们现在已回到强制模式。

有时,你可能需要让宽容模式在系统重启后保持有效。例如,如果你遇到一个长期禁用 SELinux 的系统,情况就会如此。在这种情况下,你不希望只是将 SELinux 切换到强制模式然后重启。如果你这么做,系统将需要很长时间才能正确创建 SELinux 所需的文件和目录标签,且系统可能会在完成之前卡住。首先将系统置于宽容模式,可以避免系统锁死,尽管重新标记过程仍会花费很长时间。

要让宽容模式在系统重启后持续有效,你需要编辑 /etc/sysconfig/ 目录下的 selinux 文件。默认情况下,它看起来是这样的:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of three two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

你在这里看到的两个重要信息是 SELinux 处于强制模式,且它使用的是针对性策略。要切换到宽容模式,只需更改 SELINUX= 行,然后保存文件:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=permissive
# SELINUXTYPE= can take one of three two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

sestatus 工具展示了许多关于 SELinux 的有趣信息:

[donnie@localhost ~]$ sudo sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          permissive
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28
[donnie@localhost ~]$

这里我们关注的两个项目是当前模式和来自配置文件的模式。通过将配置文件更改为宽容模式,我们并没有改变当前的运行模式。所以,我们仍然处于强制模式。直到我重启这台机器,或者手动通过执行sudo setenforce 0命令切换,才能切换到宽容模式。当然,你不希望一直停留在宽容模式下。一旦不再需要宽容模式,将配置文件改回强制模式,并执行sudo setenforce 1命令来改变运行模式。

与 SELinux 策略的工作

到目前为止,我们只看到了当文件上设置了错误的 SELinux 类型时发生了什么,以及如何设置正确的类型。我们可能遇到的另一个问题是,当我们需要允许一个被活动 SELinux 策略禁止的操作时,该怎么办。

查看布尔值

布尔值是 SELinux 策略的一部分,每个布尔值代表一个二元选择。在 SELinux 策略中,布尔值要么允许某事,要么禁止某事。要查看系统上的所有布尔值,可以运行getsebool -a命令。(这是一个很长的列表,因此这里仅显示部分输出。)

[donnie@localhost ~]$ getsebool -a
abrt_anon_write --> off
abrt_handle_event --> off
abrt_upload_watch_anon_write --> on
antivirus_can_scan_system --> off
antivirus_use_jit --> off
auditadm_exec_content --> on
. . .
. . .
zarafa_setrlimit --> off
zebra_write_config --> off
zoneminder_anon_write --> off
zoneminder_run_sudo --> off
[donnie@localhost ~]$

要查看多个布尔值,-a开关是必需的。如果你恰好知道想要查看的布尔值的名称,可以不加-a直接列出它。按照我们之前提到的 Apache web 服务器主题,来看一下是否允许 Apache 访问用户主目录中的文件:

[donnie@localhost html]$ getsebool httpd_enable_homedirs
httpd_enable_homedirs --> off
[donnie@localhost html]$

这个布尔值为off意味着 Apache 服务器进程不允许访问用户主目录中的任何内容。这是一个重要的保护,你真的不希望更改它。相反,只需将 web 内容文件放到其他地方,这样你就不必更改这个布尔值。

你很少需要查看整个列表,而且你可能也不知道想要查看的具体布尔值名称。相反,你可能希望通过grep过滤输出,以便仅查看某些内容。例如,要查看所有影响 web 服务器的布尔值,可以这样做:

[donnie@localhost html]$ getsebool -a | grep 'http'
httpd_anon_write --> off
httpd_builtin_scripting --> on
httpd_can_check_spam --> off
httpd_can_connect_ftp --> off
httpd_can_connect_ldap --> off
. . .
. . .
httpd_use_nfs --> off
httpd_use_openstack --> off
httpd_use_sasl --> off
httpd_verify_dns --> off
named_tcp_bind_http_port --> off
prosody_bind_http_port --> off
[donnie@localhost html]$

这也是一个相当长的列表,但向下滚动一点,你会找到你需要的布尔值。

配置布尔值

现实情况下,你可能永远没有理由允许用户从他们的主目录提供 Web 内容。更有可能的是,你会设置类似 Samba 服务器的服务,允许 Windows 机器上的用户通过图形化的 Windows 资源管理器访问 Linux 服务器上的主目录。但如果你设置了 Samba 服务器并且没有调整 SELinux 配置,用户会抱怨他们在 Samba 服务器的主目录中看不到任何文件。因为你是一个主动型的人,为了避免听到用户抱怨,你一定会继续配置 SELinux,允许 Samba 守护进程访问用户的主目录。你可能不知道具体的布尔值名称,但可以通过以下方式轻松找到它:

[donnie@localhost html]$ getsebool -a | grep 'home'
git_cgi_enable_homedirs --> off
git_system_enable_homedirs --> off
httpd_enable_homedirs --> off
mock_enable_homedirs --> off
mpd_enable_homedirs --> off
openvpn_enable_homedirs --> on
samba_create_home_dirs --> off
samba_enable_home_dirs --> off
. . .
use_samba_home_dirs --> off
xdm_write_home --> off
[donnie@localhost html]$

好的,你知道布尔值的名称可能包含home这个词,因此你过滤了这个词。在列表的中间,你看到了samba_enable_home_dirs --> off。你需要将其更改为on,以便用户可以从他们的 Windows 机器访问他们的主目录:

[donnie@localhost html]$ sudo setsebool samba_enable_home_dirs on
[sudo] password for donnie:
[donnie@localhost html]$ getsebool samba_enable_home_dirs
samba_enable_home_dirs --> on
[donnie@localhost html]$

用户现在可以按预期访问他们的主目录,但直到你重新启动系统为止。如果没有-P选项,你通过setsebool做的任何更改都会是临时的。所以,让我们用-P选项使更改永久生效:

[donnie@localhost html]$ sudo setsebool -P samba_enable_home_dirs on
[donnie@localhost html]$ getsebool samba_enable_home_dirs
samba_enable_home_dirs --> on
[donnie@localhost html]$

恭喜,你刚刚对 SELinux 策略进行了首次修改。

保护你的 Web 服务器

再次查看getsebool -a | grep 'http'命令的输出,你会看到大多数与 httpd 相关的布尔值默认是关闭的,只有少数几个是开启的。你常常需要在设置 Web 服务器时开启其中两个。

如果你需要设置一个带有某种基于 PHP 的内容管理系统(如 Joomla 或 WordPress)的网站,你可能需要开启httpd_unified布尔值。关闭此布尔值后,Apache Web 服务器将无法与 PHP 引擎的所有组件正常互动:

[donnie@localhost ~]$ getsebool httpd_unified
httpd_unified --> off
[donnie@localhost ~]$ sudo setsebool -P httpd_unified on
[sudo] password for donnie:
[donnie@localhost ~]$ getsebool httpd_unified
httpd_unified --> on
[donnie@localhost ~]$

另一个你常常需要开启的布尔值是httpd_can_sendmail。如果你需要让网站通过表单发送邮件(或者需要设置带有 Web 前端的邮件服务器),你一定需要将此设置为on

[donnie@localhost ~]$ getsebool httpd_can_sendmail
httpd_can_sendmail --> off
[donnie@localhost ~]$ sudo setsebool -P httpd_can_sendmail on
[donnie@localhost ~]$ getsebool httpd_can_sendmail
httpd_can_sendmail --> on
[donnie@localhost ~]$

另一方面,有些布尔值是默认开启的,你可能需要考虑是否真的需要它们保持开启。例如,允许 CGI 脚本在 Web 服务器上运行确实存在潜在的安全风险。如果黑客以某种方式将恶意的 CGI 脚本上传到服务器并运行,可能会造成很大的损害。然而,出于某种奇怪的原因,默认的 SELinux 策略允许 CGI 脚本运行。如果你完全确定没有人会在你的服务器上托管的网站上需要运行 CGI 脚本,那么你可能想要考虑将此布尔值关闭:

[donnie@localhost ~]$ getsebool httpd_enable_cgi
httpd_enable_cgi --> on
[donnie@localhost ~]$ sudo setsebool -P httpd_enable_cgi off
[donnie@localhost ~]$ getsebool httpd_enable_cgi
httpd_enable_cgi --> off
[donnie@localhost ~]$

保护网络端口

系统中每个正在运行的网络守护进程都有一个或一组特定的网络端口分配给它,用于监听。/etc/services文件包含常见守护进程及其相关网络端口的列表,但它并不会阻止某人配置一个守护进程监听某个非标准端口。因此,如果没有某种机制来阻止它,某个狡猾的入侵者可能会植入某种恶意软件,导致守护进程在非标准端口上监听,可能会等待来自其主人的命令。

SELinux 通过只允许守护进程在特定端口上监听来防止此类恶意活动。使用semanage查看允许的端口列表:

[donnie@localhost ~]$ sudo semanage port -l
SELinux Port Type              Proto    Port Number
afs3_callback_port_t           tcp      7001
afs3_callback_port_t           udp      7001
afs_bos_port_t                 udp      7007
. . .
. . .
zented_port_t                  udp      1229
zookeeper_client_port_t        tcp      2181
zookeeper_election_port_t      tcp      3888
zookeeper_leader_port_t        tcp      2888
zope_port_t                    tcp      8021
[donnie@localhost ~]$

这是又一份非常长的列表,所以我这里只展示部分输出。不过,让我们稍微缩小范围。假设我只想查看 Apache Web 服务器可以监听的端口列表。为此,我将使用我亲爱的grep

[donnie@localhost ~]$ sudo semanage port -l | grep 'http'
[sudo] password for donnie:
http_cache_port_t              tcp      8080, 8118, 8123, 10001-10010
http_cache_port_t              udp      3130
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t            tcp      5988
pegasus_https_port_t           tcp      5989
[donnie@localhost ~]$

出现了几个http项,但我只关心http_port_t项,因为它是影响正常 web 服务器操作的那个项。我们可以看到,SELinux 会允许 Apache 在端口80814434888008800984439000上监听。由于 Apache 是少数几个你有合法理由添加非标准端口的守护进程之一,我们就用它来演示。

首先,让我们进入/etc/httpd/conf/httpd.conf文件,查看 Apache 当前正在监听的端口。搜索Listen,你会看到以下这一行:

Listen 80

我在这台机器上没有安装 SSL 模块,但如果有的话,我会在/etc/httpd/conf.d/目录下有一个ssl.conf文件,并且其中会有这一行:

Listen 443

所以,对于普通的、未加密的网站连接,默认配置下,Apache 只会监听端口 80。对于安全的、加密的网站连接,Apache 会监听端口 443。现在,让我们进入httpd.conf文件,并将Listen 80更改为 SELinux 不允许的端口号,例如端口 82

Listen 82

保存文件后,我会重新启动 Apache,以便读取新的配置:

[donnie@localhost ~]$ sudo systemctl restart httpd
Job for httpd.service failed because the control process exited with error code. See "systemctl status httpd.service" and "journalctl -xe" for details.
[donnie@localhost ~]$

是的,我有个问题。我会查看/var/log/messages文件,看看setroubleshoot是否能给我一些线索:

Nov 29 16:39:21 localhost python: SELinux is preventing /usr/sbin/httpd from name_bind access on the tcp_socket port 82.#012#012***** Plugin bind_ports (99.5 confidence) suggests ************************#012#012If you want to allow /usr/sbin/httpd to bind to network port 82#012Then you need to modify the port type.#012Do#012# semanage port -a -t PORT_TYPE -p tcp 82#012 where PORT_TYPE is one of the following: http_cache_port_t, http_port_t, jboss_management_port_t, jboss_messaging_port_t, ntop_port_t, puppet_port_t.#012#012***** Plugin catchall (1.49 confidence) suggests **************************#012#012If you believe that httpd should be allowed name_bind access on the port 82 tcp_socket by default.#012Then you should report this as a bug.#012You can generate a local policy module to allow this access.#012Do#012allow this access for now by executing:#012# ausearch -c 'httpd' --raw | audit2allow -M my-httpd#012# semodule -i my-httpd.pp#012

消息的第一行详细说明了 SELinux 如何阻止 httpd 绑定到端口 82。我们看到的第一个修复建议是使用 semanage 将该端口添加到允许的端口列表中。那么,咱们就这么做,然后查看 Apache 的端口列表:

[donnie@localhost ~]$ sudo semanage port -a 82 -t http_port_t -p tcp
[donnie@localhost ~]$ sudo semanage port -l | grep 'http_port_t'
http_port_t                    tcp      82, 80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t            tcp      5988
[donnie@localhost ~]$

setroubleshoot的消息中并不明确,但你需要在port -a后指定你想要添加的端口号。-t http_port_t部分指定你要添加端口的类型,而-p tcp则指定你要使用 TCP 协议。

现在,关键时刻到了。Apache 守护进程这次能启动吗?让我们看看:

[donnie@localhost ~]$ sudo systemctl restart httpd
[sudo] password for donnie:
[donnie@localhost ~]$ sudo systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Wed 2017-11-29 20:09:51 EST; 7s ago
     Docs: man:httpd(8)
. . .
. . .

它有效,我们达成了目标。但现在,我决定不再需要这个非标准端口了。删除它和添加它一样简单:

[donnie@localhost ~]$ sudo semanage port -d 82 -t http_port_t -p tcp
[donnie@localhost ~]$ sudo semanage port -l | grep 'http_port_t'
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t            tcp      5988
[donnie@localhost ~]$

我所要做的就是将 port -a 替换为 port -d。当然,我还需要进入 /etc/httpd/conf/httpd.conf 文件,将 Listen 82 改回 Listen 80

创建自定义策略模块

有时,你会遇到一个问题,既无法通过更改类型,也无法通过设置布尔值来解决。在这种情况下,你需要创建一个自定义策略模块,并使用 audit2allow 工具来完成。

这是几年前我帮助客户在 CentOS 7 上设置 Postfix 邮件服务器时遇到的一个问题的截图:

所以,由于某种我从未理解的奇怪原因,SELinux 不允许 Dovecot(邮件服务器的邮件投递代理MDA)组件)读取它自己的 dict 文件。没有布尔值可以更改,也没有类型问题,所以 setroubleshoot 建议我创建一个自定义策略模块。这很容易做到,但需要注意的是,这在普通用户账户上使用 sudo 是无法工作的。这是少数几次你必须进入 root 用户命令行提示符的情况,同时你还需要在 root 用户的主目录中:

sudo su -

在你操作之前,确保将 SELinux 设置为宽容模式,然后做一些操作以诱发 SELinux 错误。这样,你可以确保一个问题不会掩盖其他问题。

当你运行命令创建新的策略模块时,确保将 mypol 替换为你自己选择的自定义策略名称。以我为例,我将模块命名为 dovecot_dict,命令如下所示:

grep dict /var/log/audit/audit.log | audit2allow -M dovecot_dict

我使用 grep 搜索 audit.log 文件中的 SELinux 消息,查找包含 dict 词语的内容。然后,我将该输出管道传递给 audit2allow,并使用 -M 选项创建一个名为 dovecot_dict 的自定义模块。

在我创建了新的策略模块后,我将其插入到 SELinux 策略中,如下所示:

semodule -i dovecot_dict.pp

还有第二个问题,需要另一个自定义模块,但我只是重复了这个过程,生成了一个不同名称的模块。完成所有这些后,我重新加载了 SELinux 策略,以使新模块生效:

semodule -R

使用 semodule 时,-R 选项代表重新加载,而不是像大多数 Linux 命令中那样代表递归。

完成所有这些后,我将 SELinux 设置回强制模式,并退出回到自己的用户账户。我还测试了设置,确保问题已经解决。

当然,你还需要记住,不能每次在日志文件中看到 sealert 消息时就修改 SELinux 策略或上下文。例如,考虑我在 Oracle Linux 7 机器上的 messages 文件中的这一片段,我主要是为了运行 Docker 和 Docker 容器而设置该机器:

Jun  8 19:32:17 docker-1 setroubleshoot: SELinux is preventing /usr/bin/docker from getattr access on the file /etc/exports. For complete SELinux messages. run sealert -l b267929a-d3ad-45d5-806e-907449fc2739
Jun  8 19:32:17 docker-1 python: SELinux is preventing /usr/bin/docker from getattr access on the file /etc/exports.#012#012*****  Plugin catchall (100\. confidence) suggests   **************************#012#012If you believe that docker should be allowed getattr access on the exports file by default.#012Then you should report this as a bug.#012You can generate a local policy module to allow this access.#012Do#012allow this access for now by executing:#012# grep docker /var/log/audit/audit.log | audit2allow -M mypol#012# semodule -i mypol.pp#012
Jun  8 19:32:17 docker-1 setroubleshoot: SELinux is preventing /usr/bin/docker from getattr access on the file /etc/shadow.rpmnew. For complete SELinux messages. run sealert -l
. . .

这些消息是由于早期版本的 Docker 尝试访问主机上的资源而引起的。如您所见,Docker 正在尝试访问一些相当敏感的文件,而 SELinux 正在阻止 Docker 这样做。使用 Docker 时,如果没有某种 MAC,普通的无权限用户可能会轻松地逃脱 Docker 容器并在主机系统上获得 root 权限。显然,当您看到这些类型的消息时,您不想自动让 SELinux 允许被禁止的操作。SELinux 可能正在阻止一些真正不好的事情发生。

提示

确保从opensource.com/business/13/11/selinux-policy-guide获取您的《SELinux 涂色书》副本。

实践实验 – SELinux 布尔值和端口

在这个实验中,您将查看 Apache 尝试在未授权端口上监听时的效果:

  1. 查看 SELinux 允许 Apache Web 服务器守护进程使用的端口:
sudo semanage port -l | grep 'http'
  1. 打开 /etc/httpd/conf/httpd.conf 文件,在您喜欢的文本编辑器中找到包含 Listen 80 的行,并将其更改为 Listen 82。通过输入以下命令重启 Apache:
sudo systemctl restart httpd
  1. 通过输入以下命令查看您收到的错误信息:
sudo tail -20 /var/log/messages
  1. 将端口 82 添加到授权端口列表中并重启 Apache:
sudo semanage port -a 82 -t http_port_t -p tcp
sudo semanage port -l
sudo systemctl restart httpd
  1. 删除您刚刚添加的端口:
sudo semanage -d 82 -t http_port_t -p tcp
  1. 返回到 /etc/httpd/conf/httpd.conf 文件,将 Listen 82 更改回 Listen 80。重启 Apache 守护进程以恢复正常操作。

  2. 实验结束。

好的,您已经了解了 SELinux 如何保护您免受各种不良事件的侵害,以及如何排除出现问题的情况。接下来,我们将关注 AppArmor。

AppArmor 如何使系统管理员受益

AppArmor 是与 SUSE 和 Ubuntu 系列 Linux 一起安装的 MAC 系统。虽然它的设计目的与 SELinux 基本相同,但其工作方式有很大不同:

  • SELinux 为所有系统进程以及所有对象(如文件、目录或网络端口)打上标签。对于文件和目录,SELinux 会将标签存储在它们各自的 inode 中作为扩展属性。(inode 是文件系统的基本组件,包含有关文件的所有信息,除了文件名。)

  • AppArmor 使用路径名强制执行,这意味着您指定要让 AppArmor 控制的可执行文件路径。这样,就无需将标签插入文件或目录的扩展属性中。

  • 使用 SELinux,您可以获得开箱即用的系统级保护。

  • 使用 AppArmor,您可以为每个单独的应用程序创建配置文件。

  • 无论是 SELinux 还是 AppArmor,你可能偶尔会需要从头开始创建自定义的策略模块,特别是当你处理第三方应用程序或自制软件时。使用 AppArmor 这会更容易一些,因为编写 AppArmor 配置文件的语法比编写 SELinux 策略的语法要简单得多。而且,AppArmor 提供了一些工具,可以帮助你自动化这个过程。

  • 就像 SELinux 一样,AppArmor 也可以帮助防止恶意攻击者破坏你的工作,并帮助保护用户数据。

所以,你会看到 SELinux 和 AppArmor 各有优缺点,许多 Linux 管理员对自己更喜欢哪一个有强烈的看法。(为了避免卷入争论,我不会表明自己的偏好。)另外,请注意,尽管我们使用的是 Ubuntu 虚拟机,但除了 Ubuntu 特定的包安装命令外,我在这里提供的信息同样适用于 SUSE Linux 系统。

查看 AppArmor 配置文件

首先,我们将安装 lxc 包,以便我们可以查看更多内容:

sudo apt install lxc

/etc/apparmor.d/ 目录中,你将看到系统的 AppArmor 配置文件。(SELinux 用户称之为 策略,而 AppArmor 用户称之为 配置文件。)

donnie@ubuntu3:/etc/apparmor.d$ ls -l
total 72
drwxr-xr-x 5 root root  4096 Oct 29 15:21 abstractions
drwxr-xr-x 2 root root  4096 Nov 15 09:34 cache
drwxr-xr-x 2 root root  4096 Oct 29 14:43 disable
. . .
. . .
-rw-r--r-- 1 root root   125 Jun 14 16:15 usr.bin.lxc-start
-rw-r--r-- 1 root root   281 May 23  2017 usr.lib.lxd.lxd-bridge-proxy
-rw-r--r-- 1 root root 17667 Oct 18 05:04 usr.lib.snapd.snap-confine.real
-rw-r--r-- 1 root root  1527 Jan  5  2016 usr.sbin.rsyslogd
-rw-r--r-- 1 root root  1469 Sep  8 15:27 usr.sbin.tcpdump
donnie@ubuntu3:/etc/apparmor.d$

在这个目录中看到的所有文本文件都是 AppArmor 配置文件。如果你已经安装了 lxc 包,你会在 lxclxc-containers 子目录中找到一些其他的配置文件。不过,尽管如此,在应用程序配置文件方面,还是没有太多内容。

提示

出于某些原因,OpenSUSE 的默认安装比 Ubuntu Server 安装了更多的配置文件。要在 Ubuntu 上安装更多配置文件,只需运行以下命令:

sudo apt install apparmor-profiles apparmor-profiles-extra

abstractions 子目录中,你会找到一些不是完整配置文件的文件,但它们可以被包含到完整的配置文件中。任何一个抽象文件都可以被包含在多个配置文件中。这样,你就不需要每次创建配置文件时重复写相同的代码。只需包含一个抽象文件即可。

提示

如果你熟悉编程概念,可以将抽象文件看作是另一种形式的 include 文件。

下面是部分抽象文件的列表:

donnie@ubuntu3:/etc/apparmor.d/abstractions$ ls -l
total 320
-rw-r--r-- 1 root root  695 Mar 15  2017 apache2-common
drwxr-xr-x 2 root root 4096 Oct 29 15:21 apparmor_api
-rw-r--r-- 1 root root  308 Mar 15  2017 aspell
-rw-r--r-- 1 root root 1582 Mar 15  2017 audio
. . .
. . .
-rw-r--r-- 1 root root  705 Mar 15  2017 web-data
-rw-r--r-- 1 root root  739 Mar 15  2017 winbind
-rw-r--r-- 1 root root  585 Mar 15  2017 wutmp
-rw-r--r-- 1 root root 1819 Mar 15  2017 X
-rw-r--r-- 1 root root  883 Mar 15  2017 xad
-rw-r--r-- 1 root root  673 Mar 15  2017 xdg-desktop
donnie@ubuntu3:/etc/apparmor.d/abstractions$

为了了解 AppArmor 规则是如何工作的,让我们看看 web-data 抽象文件的内容:

 /srv/www/htdocs/ r,
  /srv/www/htdocs/** r,
  # virtual hosting
  /srv/www/vhosts/ r,
  /srv/www/vhosts/** r,
  # mod_userdir
  @{HOME}/public_html/ r,
  @{HOME}/public_html/** r,
  /srv/www/rails/*/public/ r,
  /srv/www/rails/*/public/** r,
  /var/www/html/ r,
  /var/www/html/** r,

这个文件只是一个目录列表,列出了 Apache 守护进程被允许读取文件的目录。我们来详细分析一下:

  • 请注意,每个规则都以 r, 结尾。这表示我们希望 Apache 对每个列出的目录拥有读取权限。还要注意,每个规则都必须以逗号结尾。

  • /srv/www/htdocs/ r, : 这意味着列出的目录本身对 Apache 拥有读取权限。

  • /srv/www.htdocs/* * r, : * * 通配符使得这个规则是递归的。换句话说,Apache 可以读取指定目录下所有子目录中的所有文件。

  • # mod_userdir :如果安装了该 Apache 模块,它允许 Apache 从用户主目录中的子目录读取 Web 内容文件。接下来的两行与此相关。

  • @{HOME}/public_html/ r,@{HOME}/public_html/ r,@{HOME}变量允许该规则作用于任何用户的主目录。(你将在/etc/apparmor.d/tunables/home文件中看到这个变量的定义。)

  • 请注意,没有特定的规则禁止 Apache 从其他位置读取文件。只是理解为任何未在此列出的内容,Apache web 服务器守护进程都无法访问。

tunables子目录包含预定义的变量文件。你还可以使用该目录来定义新变量或进行配置文件调整:

donnie@ubuntu3:/etc/apparmor.d/tunables$ ls -l
total 56
-rw-r--r-- 1 root root  624 Mar 15  2017 alias
-rw-r--r-- 1 root root  376 Mar 15  2017 apparmorfs
-rw-r--r-- 1 root root  804 Mar 15  2017 dovecot
-rw-r--r-- 1 root root  694 Mar 15  2017 global
-rw-r--r-- 1 root root  983 Mar 15  2017 home
. . .
. . .
-rw-r--r-- 1 root root  440 Mar 15  2017 proc
-rw-r--r-- 1 root root  430 Mar 15  2017 securityfs
-rw-r--r-- 1 root root  368 Mar 15  2017 sys
-rw-r--r-- 1 root root  868 Mar 15  2017 xdg-user-dirs
drwxr-xr-x 2 root root 4096 Oct 29 15:02 xdg-user-dirs.d
donnie@ubuntu3:/etc/apparmor.d/tunables$

由于篇幅所限,我无法在此展示如何从头编写自己的配置文件。感谢接下来我们将要查看的一组实用工具,你可能根本不需要手动编写配置文件。不过,为了帮助你更好地理解 AppArmor 是如何工作的,以下是一些示例规则,它们可能出现在任何给定的配置文件中:

规则 解释
/var/run/some_program.pid rw, 该进程将对该进程 ID 文件具有读写权限。
/etc/ld.so.cache r, 该进程将具有读取此文件的权限。
/tmp/some_program.* l, 该进程将能够创建和删除名为some_program的链接。
/bin/mount ux 该进程具有mount工具的可执行权限,随后将不受限制地运行。(不受限制意味着没有 AppArmor 配置文件。)

现在你已经了解了 AppArmor 配置文件,接下来我们来看一些基本的 AppArmor 工具。

使用 AppArmor 命令行工具

是否拥有所需的所有 AppArmor 工具,取决于你使用的 Linux 发行版。在我的 OpenSUSE Leap 工作站上,这些工具是开箱即用的。在我的 Ubuntu Server 虚拟机上,我则需要手动安装它们:

sudo apt install apparmor-utils

首先,我们查看 Ubuntu 机器上 AppArmor 的状态。由于输出内容较长,我们将分段查看。下面是第一部分:

donnie@ubuntu2204-packt:~$ sudo aa-status
apparmor module is loaded.
61 profiles are loaded.
43 profiles are in enforce mode.
   /snap/snapd/17029/usr/lib/snapd/snap-confine
   /snap/snapd/17029/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
   /snap/snapd/17336/usr/lib/snapd/snap-confine
   /snap/snapd/17336/usr/lib/snapd/snap-confine//mount-namespace-capture-helper
   /usr/bin/lxc-start
   /usr/bin/man
   /usr/bin/pidgin
   /usr/bin/pidgin//sanitized_helper
. . .
. . .

首先需要注意的是,AppArmor 有enforce模式和complain模式。这里显示的 enforce 模式与 SELinux 中的 enforcing 模式对应,执行相同的功能。它防止系统进程执行活动策略不允许的操作,并记录任何违规行为。

现在,这是第二部分:

. . .
. . .
0 processes are in enforce mode.
1 processes are in complain mode.
   /usr/sbin/dnsmasq (2485) dnsmasq
0 processes are unconfined but have a profile defined.
0 processes are in mixed mode.
0 processes are in kill mode.
donnie@ubuntu2204-packt:~$

"Complain"模式与 SELinux 中的"permissive"模式相同。它允许进程执行活动策略禁止的操作,但会将这些操作记录在/var/log/audit/audit.log文件或系统日志文件中,具体取决于是否安装了auditd。 (与 Red Hat 类发行版不同,Ubuntu 默认没有安装auditd。)你可以使用 complain 模式来帮助故障排除或测试新配置文件。

我们在这里看到的大多数 enforce 模式配置文件都与网络管理或lxc容器管理有关。我们看到的两个例外是snapd的两个配置文件,snapd是使 snap 打包技术工作的重要守护进程。第三个例外是mysqld配置文件。

Snap 包是旨在适用于多个发行版的通用二进制文件。Snap 技术目前已在大多数主要的 Linux 发行版上提供。

奇怪的是,当你在 Ubuntu 上安装守护进程包时,有时会得到一个预定义的配置文件,也有时不会。即使安装的包中有配置文件,它有时已经处于 enforce 模式,也有时没有。例如,如果你正在设置域名服务DNS)服务器并安装了bind9包,你将得到一个已经处于 enforce 模式的 AppArmor 配置文件。如果你正在设置一个数据库服务器并安装mysql-server包,你也会得到一个已经处于 enforce 模式的有效配置文件。

但是,如果你正在设置一个数据库服务器并且更倾向于安装mariadb-server而不是mysql-server,你会得到一个完全禁用且无法启用的 AppArmor 配置文件。当你查看随着mariadb-server包安装的usr.sbin.mysqld配置文件时,你会看到如下内容:

# This file is intentionally empty to disable apparmor by default for newer
# versions of MariaDB, while providing seamless upgrade from older versions
# and from mysql, where apparmor is used.
#
# By default, we do not want to have any apparmor profile for the MariaDB
# server. It does not provide much useful functionality/security, and causes
# several problems for users who often are not even aware that apparmor
# exists and runs on their system.
#
# Users can modify and maintain their own profile, and in this case it will
# be used.
#
# When upgrading from previous version, users who modified the profile
# will be promptet to keep or discard it, while for default installs
# we will automatically disable the profile.

好吧,显然 AppArmor 并不适合所有情况。(而且写这个的人应该去上一堂拼写课。)

然后是 Samba,它在多个方面都是一个特例。当你安装samba包来设置 Samba 服务器时,你根本不会得到任何 AppArmor 配置文件。对于 Samba 和其他一些应用程序,你需要单独安装 AppArmor 配置文件:

sudo apt install apparmor-profiles apparmor-profiles-extra

当你安装这两个配置文件包时,所有配置文件都会处于complain模式。这没关系,因为我们有一个方便的工具可以将它们切换到enforce模式。由于 Samba 有两个不同的守护进程需要保护,因此我们需要将两个不同的配置文件切换到enforce模式:

donnie@ubuntu5:/etc/apparmor.d$ ls *mbd
usr.sbin.nmbd  usr.sbin.smbd
donnie@ubuntu5:/etc/apparmor.d$

我们将使用aa-enforce来激活这两个配置文件的enforce模式:

donnie@ubuntu5:/etc/apparmor.d$ sudo aa-enforce /usr/sbin/nmbd usr.sbin.nmbd
Setting /usr/sbin/nmbd to enforce mode.
Setting /etc/apparmor.d/usr.sbin.nmbd to enforce mode.
donnie@ubuntu5:/etc/apparmor.d$ sudo aa-enforce /usr/sbin/smbd usr.sbin.smbd
Setting /usr/sbin/smbd to enforce mode.
Setting /etc/apparmor.d/usr.sbin.smbd to enforce mode.
donnie@ubuntu5:/etc/apparmor.d$

要使用aa-enforce,首先需要指定你想要保护的进程的可执行文件路径。(幸运的是,你通常不需要自己查找路径,因为路径名通常是配置文件文件名的一部分。)命令的最后部分是配置文件的名称。请注意,你需要重启 Samba 守护进程才能使这个 AppArmor 保护生效。

将配置文件切换到其他模式同样简单。你只需将aa-enforce工具替换为你需要使用的模式工具。以下是其他模式的工具列表:

命令 说明
aa-audit 审计模式与执行模式相同,只是允许的操作也会被记录,而不仅仅是被阻止的操作。(执行模式只记录被阻止的操作。)
aa-disable 完全禁用一个配置文件。
aa-complain 将配置文件置于告警模式。

好的,我们继续前进。现在你已经了解了基本的 AppArmor 命令。接下来,我们将学习如何排查 AppArmor 的问题。

排查 AppArmor 问题

当我在 2017 年写这本书的第一版时,我在这里绞尽脑汁地坐了好几天,试图想出一个好的故障排除场景。结果我发现我根本不需要这样做。Ubuntu 的开发团队以一个有问题的 Samba 配置文件,给我提供了一个现成的场景。当时,我使用的是 Ubuntu 16.04,它有这个 bug 的原始版本。这个 bug 在 Ubuntu 18.04 中被修复了,但又被两个新问题替代了。在 Ubuntu 22.04 中,配置文件终于没有问题了。尽管如此,我还是想展示一下如何排查 AppArmor 问题,所以我保留了 16.04 和 18.04 的故障排除写作部分。(你仍然可以下载并安装 Ubuntu 16.04 和 18.04,如果你愿意,可以创建一些虚拟机跟着做。我把这个决定留给你自己。)

排查 AppArmor 配置文件问题 —— Ubuntu 16.04

正如你刚刚看到的,我使用了aa-enforce将两个与 Samba 相关的配置文件设置为执行模式。但看看当我尝试重启 Samba 以使配置文件生效时会发生什么:

donnie@ubuntu3:/etc/apparmor.d$ sudo systemctl restart smbd
Job for smbd.service failed because the control process exited with error code. See "systemctl status smbd.service" and "journalctl -xe" for details.
donnie@ubuntu3:/etc/apparmor.d$

好的,这不太好。查看smbd服务的状态,我看到了以下信息:

donnie@ubuntu3:/etc/apparmor.d$ sudo systemctl status smbd
● smbd.service - LSB: start Samba SMB/CIFS daemon (smbd)
   Loaded: loaded (/etc/init.d/smbd; bad; vendor preset: enabled)
   Active: failed (Result: exit-code) since Tue 2017-12-05 14:56:35 EST; 13s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 31160 ExecStop=/etc/init.d/smbd stop (code=exited, status=0/SUCCESS)
  Process: 31171 ExecStart=/etc/init.d/smbd start (code=exited, status=1/FAILURE)
Dec 05 14:56:35 ubuntu3 systemd[1]: Starting LSB: start Samba SMB/CIFS daemon (smbd)...
Dec 05 14:56:35 ubuntu3 smbd[31171]:  * Starting SMB/CIFS daemon smbd
Dec 05 14:56:35 ubuntu3 smbd[31171]:    ...fail!
. . .

这里需要注意的重要点是,所有出现fail一词的地方。

原始错误信息建议使用journalctl -xe查看日志信息。如果你愿意,可以这样做,或者你也可以使用lesstail直接查看/var/log/syslog日志文件:

Dec  5 20:09:10 ubuntu3 smbd[14599]:  * Starting SMB/CIFS daemon smbd
Dec  5 20:09:10 ubuntu3 kernel: [174226.392671] audit: type=1400 audit(1512522550.765:510): apparmor="DENIED" operation="mknod" profile="/usr/sbin/smbd" name="/run/samba/msg.
lock/14612" pid=14612 comm="smbd" requested_mask="c" denied_mask="c" fsuid=0 ouid=0
Dec  5 20:09:10 ubuntu3 smbd[14599]:    ...fail!
Dec  5 20:09:10 ubuntu3 systemd[1]: smbd.service: Control process exited, code=exited status=1
Dec  5 20:09:10 ubuntu3 systemd[1]: Failed to start LSB: start Samba SMB/CIFS daemon (smbd).
Dec  5 20:09:10 ubuntu3 systemd[1]: smbd.service: Unit entered failed state.
Dec  5 20:09:10 ubuntu3 systemd[1]: smbd.service: Failed with result 'exit-code'.

所以,我们看到apparmor=DENIED。显然,Samba 正在尝试做一些配置文件不允许的事情。Samba 需要写入临时文件到/run/samba/msg.lock目录,但它没有权限。我猜测配置文件缺少允许该操作的规则。

但即便这个日志文件条目没有给我任何线索,我也可以作弊,使用一种多年来一直非常有效的故障排除技巧。也就是说,我可以将日志文件中的错误信息复制粘贴到我最喜欢的搜索引擎中。几乎每次我这么做时,我都能发现之前有人已经遇到过相同的问题:

好的,我没有粘贴完整的错误信息,但我粘贴了足够的信息供 DuckDuckGo 使用。果不其然,它有效:

嗯,看起来我的配置文件可能缺少了一个重要的行。因此,我将打开usr.sbin.smbd文件,并在规则集的末尾添加这一行:

/run/samba/** rw,

这一行将允许对 /run/samba/ 目录中的所有内容进行读写访问。进行编辑后,我需要重新加载这个配置文件,因为它已经通过 aa-enforce 被加载了。为此,我将使用 apparmor_parser 工具:

donnie@ubuntu3:/etc/apparmor.d$ sudo apparmor_parser -r usr.sbin.smbd
donnie@ubuntu3:/etc/apparmor.d$

你只需要使用 -r 选项进行重新加载,并列出配置文件的名称。现在,让我们尝试重启 Samba:

donnie@ubuntu3:/etc/apparmor.d$ sudo systemctl restart smbd
donnie@ubuntu3:/etc/apparmor.d$ sudo systemctl status smbd
● smbd.service - LSB: start Samba SMB/CIFS daemon (smbd)
   Loaded: loaded (/etc/init.d/smbd; bad; vendor preset: enabled)
   Active: active (running) since Wed 2017-12-06 13:31:32 EST; 3min 6s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 17317 ExecStop=/etc/init.d/smbd stop (code=exited, status=0/SUCCESS)
  Process: 16474 ExecReload=/etc/init.d/smbd reload (code=exited, status=0/SUCCESS)
  Process: 17326 ExecStart=/etc/init.d/smbd start (code=exited, status=0/SUCCESS)
    Tasks: 3
   Memory: 9.3M
      CPU: 594ms
   CGroup: /system.slice/smbd.service
           ├─17342 /usr/sbin/smbd -D
           ├─17343 /usr/sbin/smbd -D
           └─17345 /usr/sbin/smbd -D
Dec 06 13:31:28 ubuntu3 systemd[1]: Stopped LSB: start Samba SMB/CIFS daemon (smbd).
Dec 06 13:31:28 ubuntu3 systemd[1]: Starting LSB: start Samba SMB/CIFS daemon (smbd)...
Dec 06 13:31:32 ubuntu3 smbd[17326]:  * Starting SMB/CIFS daemon smbd
Dec 06 13:31:32 ubuntu3 smbd[17326]:    ...done.
Dec 06 13:31:32 ubuntu3 systemd[1]: Started LSB: start Samba SMB/CIFS daemon (smbd).
donnie@ubuntu3:/etc/apparmor.d$

干得好!这两个 Samba 配置文件已经处于强制模式,Samba 最终成功启动了。

奇怪的是,我在 Ubuntu 16.04 和 Ubuntu 17.10 中也遇到了相同的问题。因此,bug 已经存在很长时间了。

故障排除 AppArmor 配置文件 – Ubuntu 18.04

如我之前所说,Ubuntu 16.04 中的 bug 被 Ubuntu 18.04 中的两个新 bug 替代了。我们来看看具体情况。

我在我的 Ubuntu 18.04 虚拟机上安装了 Samba 和额外的 AppArmor 配置文件,然后将两个 Samba 配置文件设置为 强制 模式,就像我之前在 Ubuntu 16.04 中所展示的那样。当我尝试重启 Samba 时,重启失败了。于是,我查看了 /var/log/syslog 文件,并发现了这两条消息:

Oct 15 19:22:05 ubuntu-ufw kernel: [ 2297.955842] audit: type=1400 audit(1571181725.419:74): apparmor="DENIED" operation="capable" profile="/usr/sbin/smbd" pid=15561 comm="smbd" capability=12  capname="net_admin"
Oct 15 19:22:05 ubuntu-ufw kernel: [ 2297.960193] audit: type=1400 audit(1571181725.427:75): apparmor="DENIED" operation="sendmsg" profile="/usr/sbin/smbd" name="/run/systemd/notify" pid=15561 comm="smbd" requested_mask="w" denied_mask="w" fsuid=0 ouid=0

现在我们知道如何读取 AppArmor 错误信息了,这就容易解决了。看起来我们需要允许 SMBD 服务拥有 net_admin 权限,以便它能够正确访问网络。而且,看起来我们还需要添加一条规则,允许 SMBD 写入 /run/systemd/notify 套接字文件。所以,让我们编辑 /etc/apparmor.d/usr.sbin.smbd 文件并添加这两行缺失的内容。

首先,在包含所有 capability 行的部分,我将在底部添加这一行:

capability net_admin,

然后,在规则列表的底部,/var/spool/samba/** rw, 这一行下方,我将添加这一行:

/run/systemd/notify rw,

现在只需重新加载策略并重启 SMBD 服务,和我们在 Ubuntu 16.04 中所做的相同。

实操实验室 – 故障排除 AppArmor 配置文件

在 Ubuntu 18.04 虚拟机上执行此实验。按照以下步骤进行故障排除:

  1. 安装 AppArmor 工具和额外的配置文件:
sudo apt install apparmor-utils apparmor-profiles apparmor-profiles-extra
  1. 安装 Samba 并验证它是否在运行:
sudo apt install samba
sudo systemctl status smbd
sudo systemctl status nmbd
  1. 将之前提到的两个 Samba 策略设置为强制模式并尝试重启 Samba:
cd /etc/apparmor.d
sudo aa-enforce /usr/sbin/smbd usr.sbin.smbd
sudo aa-enforce /usr/sbin/nmbd usr.sbin.nmbd
sudo systemctl restart smbd
  1. 注意,Samba 应该无法重启。(它会花费相当长的时间才最终出错,因此请耐心等待。)

  2. 查看 /var/log/syslog 文件,看看能否找到问题。

  3. 编辑 /etc/apparmor.d/usr.sbin.smbd 文件。在 capability 部分,添加这一行:

capability net_admin,
  1. 在规则部分的底部,/var/spool/samba/** rw, 这一行下方,添加这一行:
/run/systemd/notify rw,
  1. 保存文件并重新加载策略:
sudo apparmor_parser -r usr.sbin.smbd
  1. 如之前所述,尝试重启 Samba 服务,并验证它是否正常启动:
sudo systemctl restart smbd
sudo systemctl status smbd
  1. 实验结束。

好的,你刚刚探索了故障排除有缺陷的 AppArmor 配置文件的基础知识。这是非常有用的知识,尤其是在你的组织需要部署自己定制的配置文件时,这些配置文件可能也会存在同样的问题。

故障排除 Ubuntu 22.04 中的 Samba 问题

我之前告诉过你,Samba 的 AppArmor 配置文件 bug 在 Ubuntu 22.04 中已修复。那么,万岁对吧?嗯,别急。就在我写这篇文章的 2022 年 11 月,某些版本的 Samba 现在出现了一个 bug,导致如果启用了 AppArmor 配置文件,Samba 无法启动。

这个 bug 在 SUSE 15.3 中也存在,但在 SUSE 15.4 中已经修复。它在 Ubuntu 22.10 中也已修复。等你读到这篇文章时,它可能也已在 Ubuntu 22.04 中修复。

首先,按照我之前描述的步骤安装 sambaapparmor-profilesapparmor-profiles-extraapparmor-util 包。执行 systemctl status smbd 命令应该会显示 Samba 服务正常运行。接下来,我们将把两个 Samba 配置文件设置为 强制 模式:

cd /etc/apparmor.d
sudo aa-enforce /usr/sbin/smbd usr.sbin.smbd
sudo aa-enforce /usr/sbin/nmbd usr.sbin.nmbd
sudo systemctl restart smbd

这一次,在执行 systemctl status smbd 命令之前,你不会看到任何错误信息:

donnie@ubuntu2204-packt:~$ systemctl status smbd
× smbd.service - Samba SMB Daemon
     Loaded: loaded (/lib/systemd/system/smbd.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Mon 2022-11-14 21:01:28 UTC; 7s ago
       Docs: man:smbd(8)
             man:samba(7)
             man:smb.conf(5)
    Process: 1966 ExecStartPre=/usr/share/samba/update-apparmor-samba-profile (code=exited, status=0/SUCCESS)
    Process: 1976 ExecStart=/usr/sbin/smbd --foreground --no-process-group $SMBDOPTIONS (code=exited, status=1/FAILURE)
   Main PID: 1976 (code=exited, status=1/FAILURE)
     Status: "daemon failed to start: Samba failed to init printing subsystem"
      Error: 13 (Permission denied)
        CPU: 133ms

与之前不同的是,系统日志文件中的搜索不会告诉你任何信息。如果你搜索 usr.sbin.smbd 文件,你将根本看不到任何问题。相反,这次的问题出在 Samba 服务的打印服务功能上。幸运的是,这是一个容易修复的问题,只要你不介意没有 Samba 打印共享。只需在文本编辑器中打开 /etc/samba/smb.conf 文件,并在 [global] 部分添加以下一行:

disable spoolss = yes

Samba 服务现在应该能够顺利启动。

现在,我必须承认,我并不知道这个指令的具体影响是什么。smb.conf 的 man 页面上说打印可能会影响到 Windows NT/2000 系统,但并没有说明它对新版 Windows 的影响。不管怎样,如果你真的需要 Samba 打印服务器支持,我就把这部分实验留给你自己去做。

好了,够了,Samba 的事就到此为止。让我们继续讨论一些既邪恶又有趣的事情吧。

利用恶意 Docker 容器攻击系统

你可能认为容器有点像虚拟机,你部分是对的。不同之处在于,虚拟机会运行一个完整的自包含操作系统,而容器则不会。相反,容器带有来宾操作系统的包管理和库,但它使用宿主操作系统的内核资源。这使得容器更加轻量化。因此,你可以在一台服务器上放置更多的容器,而不像虚拟机那样,这有助于减少硬件和能源成本。容器已经存在了很多年,但直到 Docker 出现,容器才变得流行起来。

但是,正是容器使用宿主机内核资源的特点使其如此轻量化——这也可能带来一些有趣的安全问题。采用某种形式的 MAC(强制访问控制)是你可以做的一件事情,来帮助缓解这些问题。

一个问题是,要运行 Docker,用户需要具有适当的sudo权限,或者是docker组的成员。无论哪种方式,任何登录容器的人都会处于该容器的 root 命令提示符下。通过创建一个挂载主机根文件系统的容器,非特权用户可以完全控制主机系统。

实验操作 – 创建一个恶意的 Docker 容器

为了演示,我将使用 CentOS 7 虚拟机来展示 SELinux 如何帮助保护你。(我使用 CentOS 7 是因为 RHEL 8/9 类的发行版使用podman而非docker,这将阻止此漏洞的发生。)此外,你需要从虚拟机的本地控制台进行操作,因为 root 用户将无法通过 SSH 登录(稍后你会明白我的意思):

  1. 在你的 CentOS 7 虚拟机上,安装 Docker 并启用守护进程:
sudo yum install docker
sudo systemctl enable --now docker
  1. 创建docker组。
sudo groupadd docker
  1. 为 Katelyn 创建一个用户账户,我的十几岁玳瑁猫,同时将她添加到docker组:
sudo useradd -G docker katelyn
sudo passwd katelyn
  1. 从你自己的用户账户登出,然后以 Katelyn 的身份重新登录。

  2. 让 Katelyn 创建一个挂载主机机器/分区到/homeroot挂载点的 Debian 容器,并打开一个 root 用户的 Bash shell 会话:

docker run -v /:/homeroot -it debian bash

注意 Katelyn 是如何在没有使用任何sudo权限的情况下完成这一操作的。同时注意/:/homeroot部分没有空格。

  1. 目标是让 Katelyn 成为主机上的 root 用户。为了实现这一点,她需要编辑/etc/passwd文件,将自己的用户 ID 改为0。为了做到这一点,她需要安装一个文本编辑器。(Katelyn 偏好使用 vim,但如果你真的想,也可以使用 nano。)仍在 Debian 容器内,运行以下命令:
apt update
apt install vim
  1. 让 Katelyn 进入主机的/etc/目录,并尝试在文本编辑器中打开passwd文件:
cd /homeroot/etc
vim passwd
  1. 她做不到,因为 SELinux 阻止了它。

  2. 输入exit退出容器。

  3. 从 Katelyn 的账户登出,然后重新登录到你的账户。

  4. 将 SELinux 设置为宽松模式:

sudo setenforce 0
  1. 从你的账户登出,然后以 Katelyn 的身份重新登录。

  2. 重复步骤 57。这一次,Katelyn 将能够在文本编辑器中打开/etc/passwd文件。

  3. passwd文件中,让 Katelyn 找到自己用户账户的那一行。让她将用户 ID 改为0。那一行现在应该像这样:

katelyn:x:0:1002::/home/katelyn:/bin/bash
  1. 保存文件,输入exit让 Katelyn 退出容器。让 Katelyn 登出虚拟机,然后重新登录。这时,你将看到她成功登录到了 root 用户的 shell。

  2. 实验结束

好的,你刚刚看到了 Docker 的一个安全弱点,以及 SELinux 如何保护你免受此类问题的影响。由于凯特琳没有sudo权限,她无法将 SELinux 设置为宽容模式,这阻止了她在 Docker 中进行任何恶意操作。在 RHEL 8/9 类型的发行版上,情况甚至更好。即使 SELinux 处于宽容模式或完全禁用状态,你仍无法在 AlmaLinux 机器上的任何容器内编辑passwd文件。这是因为 RHEL 8/9 及其衍生版本使用podman,这是 Red Hat 替代docker的工具。podman在主机安全性方面比 docker 有多个优势。

所以现在你可能在想,AppArmor 在 Ubuntu 上是否对我们有帮助。嗯,并非默认情况下,因为 Docker 守护程序没有预先构建的 AppArmor 配置文件。当你在 Ubuntu 机器上运行 Docker 时,它会自动在/tmpfs/目录中为容器创建一个 Docker 配置文件,但实际上没有多大作用。我在启用 AppArmor 的 Ubuntu 18.04 虚拟机上测试了此过程,并且凯特琳能够很好地完成她的恶意操作。(请注意,现在podman也可用于非 Red Hat Linux 发行版,包括 Ubuntu。)

在本章的早些时候,我说过我不会表明我更喜欢这两个 MAC 系统中的哪一个。但其实我撒了个谎。如果你现在还没发现的话,我绝对是 SELinux 的忠实粉丝,因为它提供比 AppArmor 更好的开箱即用保护。如果你选择使用 Ubuntu,那么计划在你的开发团队部署新应用程序时编写新的 AppArmor 策略。

提示

如果你不想处理创建 AppArmor 配置文件的复杂性,你可以在你的服务的 systemd 单元文件中放置安全指令。你可能会觉得这样做更容易一些,并且可以提供与 AppArmor 类似的保护。详细内容请参阅我的另一本书,Linux Service Management Made Easy with systemd

我确信这就是我们关于 MAC 讨论的总结。

总结

在本章中,我们讨论了 MAC 的基本原理,并比较了两种不同的 MAC 系统。我们了解了 SELinux 和 AppArmor 的作用,以及它们如何帮助保护系统免受恶意行为的侵害。然后我们看了如何基本使用它们以及如何进行故障排除。我们还看到,尽管它们都是为了完成同样的工作,但它们的工作方式却大不相同。最后,我们通过一个实际的例子展示了 SELinux 如何保护你免受恶意 Docker 容器的侵害。

无论你是在使用 AppArmor 还是 SELinux,你都应该在将系统投入生产前,先在 complainpermissive 模式下彻底测试新系统。确保你想要保护的内容得到保护,同时你想要允许的内容也能够被允许。在将机器投入生产后,不要只是认为每次看到策略违规时就可以自动修改策略设置。可能是你的 MAC 设置没有问题,MAC 只是按照其职责在保护你免受恶意攻击。

这两个话题有很多内容超出了我们能在这里讨论的范围。不过,希望我已经为你提供了足够的内容,能激发你的兴趣,并帮助你在日常工作中解决问题。

在下一章中,我们将探讨更多加固内核和隔离进程的技巧。到时见。

问题

  1. 以下哪项代表了 MAC 原则?

A. 你可以根据需要设置自己文件和目录的权限。

B. 你可以允许任何系统进程访问你希望它访问的资源。

C. 系统进程只能访问 MAC 策略允许它们访问的资源。

D. 即使 DAC 不允许访问,MAC 也会允许。

  1. SELinux 是如何工作的?

A. 它会在每个系统对象上加上标签,并根据 SELinux 策略所说的标签内容,允许或拒绝访问。

B. 它仅仅是根据每个系统进程的配置文件来查看该进程可以执行的操作。

C. 它使用扩展属性,管理员通过 chattr 工具进行设置。

D. 它允许每个用户设置自己的 MAC。

  1. 你将使用哪个工具来修复不正确的 SELinux 安全上下文?

A. chattr

B. chcontext

C. restorecon

D. setsebool

  1. 对于 Red Hat 类型服务器的日常管理,管理员最关心的安全上下文方面是以下哪项?

A. 用户

B. 角色

C. 类型

D. 敏感度

  1. 你已经设置了一个特定的目录,通常该目录不会被某个守护进程访问,你希望永久允许该守护进程访问该目录。你将使用以下哪个工具来完成此操作?

A. chcon

B. restorecon

C. setsebool

D. semanage

  1. 以下哪一项构成了 SELinux 和 AppArmor 之间的一个区别?

A. 在 SELinux 中,你必须为每个需要控制的系统进程安装或创建一个策略配置文件。

B. 使用 AppArmor 时,你必须为每个需要控制的系统进程安装或创建一个策略配置文件。

C. AppArmor 通过在每个系统对象上应用标签来工作,而 SELinux 只是通过查看每个系统对象的配置文件来工作。

D. 编写 SELinux 的策略配置文件更容易,因为其语言更容易理解。

  1. /etc/apparmor.d/ 子目录中的哪个包含预定义变量的文件?

A. tunables

B. variables

C. var

D. functions

  1. 你将使用哪个工具来启用 AppArmor 策略?

A. aa-enforce

B. aa-enable

C. set-enforce

D. set-enable

  1. 你已经为一个守护进程启用了 AppArmor 策略,但现在需要更改该策略。你将使用哪个工具来重新加载修改后的策略?

A. aa-reload

B. apparmor_reload

C. aa-restart

D. apparmor_parser

  1. 你正在测试一个新的 AppArmor 配置文件,并且希望在将服务器投入生产之前找到可能的问题。你会使用哪个 AppArmor 模式来测试该配置文件?

A. 宽松模式

B. enforce

C. 测试

D. complain

深入阅读

SELinux:

AppArmor:

答案

  1. C

  2. A

  3. C

  4. C

  5. D

  6. B

  7. A

  8. A

  9. D

  10. D

第十章:11 内核强化与进程隔离

加入我们的书籍社区,在 Discord 上

packt.link/SecNet

虽然 Linux 内核本身设计上已经相当安全,但仍有一些方法可以进一步锁定它。一旦你知道该寻找什么,这些操作非常简单。调整内核可以帮助防止某些网络攻击和某些类型的信息泄露。(但别担心——你不需要重新编译整个内核就能利用这些方法。)

通过进程隔离,我们的目标是防止恶意用户进行垂直或水平权限提升。通过将进程彼此隔离,我们可以帮助防止某人控制根用户进程或其他用户的进程。这两种类型的权限提升都可能帮助攻击者控制系统或访问敏感信息。

在本章中,我们将快速浏览/proc文件系统,并向你展示如何配置其中的某些参数,以帮助增强安全性。然后,我们将讨论进程隔离的话题,介绍确保进程相互隔离的各种方法。

在本章中,我们将涵盖以下主题:

  • 理解/proc文件系统

  • 使用sysctl设置内核参数

  • 配置sysctl.conf文件

  • 进程隔离概述

  • 控制组

  • 命名空间隔离

  • 内核能力

  • SECCOMP 和系统调用

  • 使用 Docker 容器的进程隔离

  • 使用 Firejail 进行沙箱化

  • 使用 Snappy 进行沙箱化

  • 使用 Flatpak 进行沙箱化

所以,如果你准备好了,我们将从查看/proc文件系统开始。

理解/proc文件系统

如果你cd进入任何 Linux 发行版的/proc/目录并四处查看,你可能会认为这里没什么特别的。你会看到文件和目录,所以它看起来可能只是另一个目录。但实际上,它是非常特别的。它是 Linux 系统上几种伪文件系统之一。(伪这个词的定义是,所以你也可以把它看作一个假文件系统。)

如果你把 Linux 机器的主操作系统驱动器取出并将其作为第二个驱动器安装到另一台机器上,你会看到该驱动器上有一个/proc/目录,但你不会看到任何内容。这是因为/proc/目录的内容是每次启动 Linux 机器时从头开始创建的,然后每次关闭机器时都会清空。在/proc/目录中,你会发现两类常见的信息:

  • 关于用户模式进程的信息

  • 关于操作系统内核级别活动的信息

我们将首先查看用户模式进程。

查看用户模式进程

如果你在 /proc/ 中调用 ls 命令,你会看到一大堆以数字命名的目录。以下是我在 CentOS 虚拟机上的部分列出:

[donnie@localhost proc]$ ls -l
total 0
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 1
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 10
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 11
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 12
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 13
dr-xr-xr-x. 9 root root 0 Oct 19 14:24 1373
dr-xr-xr-x. 9 root root 0 Oct 19 14:24 145
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 15
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 16
dr-xr-xr-x. 9 root root 0 Oct 19 14:23 17
. . .
. . .

每个这些编号的目录对应于用户模式进程的 进程 IDPID)编号。在任何 Linux 系统中,PID 1 始终是 init 系统进程,它是开机启动时第一个启动的用户模式进程。

在 Debian/Ubuntu 系统中,PID 1 的名称是init。在 RHEL/CentOS/AlmaLinux 系统中,它叫做systemd。所有这些发行版都运行 systemd init 系统,但 Debian/Ubuntu 用户选择保留旧的 init 名称作为 PID 1。

在每个编号目录中,你会看到包含特定正在运行的进程信息的各种文件和子目录。例如,在 1 目录中,你会看到与 init 进程相关的内容。以下是部分列出:

[donnie@localhost 1]$ ls -l
ls: cannot read symbolic link 'cwd': Permission denied
ls: cannot read symbolic link 'root': Permission denied
ls: cannot read symbolic link 'exe': Permission denied
total 0
dr-xr-xr-x. 2 root root 0 Oct 19 14:23 attr
-rw-r--r--. 1 root root 0 Oct 19 15:08 autogroup
-r--------. 1 root root 0 Oct 19 15:08 auxv
-r--r--r--. 1 root root 0 Oct 19 14:23 cgroup
--w-------. 1 root root 0 Oct 19 15:08 clear_refs
-r--r--r--. 1 root root 0 Oct 19 14:23 cmdline
-rw-r--r--. 1 root root 0 Oct 19 14:23 comm
. . .
. . .

如你所见,有一些符号链接我们没有 root 权限无法访问。当我们使用 sudo 时,我们可以看到这些符号链接指向哪里:

[donnie@localhost 1]$ sudo ls -l
total 0
dr-xr-xr-x. 2 root root 0 Oct 19 14:23 attr
-rw-r--r--. 1 root root 0 Oct 19 15:08 autogroup
-r--------. 1 root root 0 Oct 19 15:08 auxv
-r--r--r--. 1 root root 0 Oct 19 14:23 cgroup
--w-------. 1 root root 0 Oct 19 15:08 clear_refs
-r--r--r--. 1 root root 0 Oct 19 14:23 cmdline
-rw-r--r--. 1 root root 0 Oct 19 14:23 comm
-rw-r--r--. 1 root root 0 Oct 19 15:08 coredump_filter
-r--r--r--. 1 root root 0 Oct 19 15:08 cpuset
lrwxrwxrwx. 1 root root 0 Oct 19 15:08 cwd -> /
. . .
. . .

你可以使用 cat 命令查看其中一些项目的内容,但并不是所有的都能查看。然而,即使你能够查看内容,除非你是操作系统程序员,否则很难理解其中的含义。与其直接查看这些信息,不如使用 topps,它们从 /proc/ 获取信息并进行解析,使得人类可以阅读。

我假设你们中的大多数人已经熟悉 topps。对于那些不熟悉的人,下面是简短的说明。

ps 提供了一个静态的、关于你机器进程的显示。它有许多选项开关,可以显示不同量的信息。我最喜欢的 ps 命令是 ps aux,它提供了关于每个进程的相当完整的信息。

top 提供了一个动态的、不断变化的机器进程显示。虽然有一些选项开关可以使用,但通常只需调用 top 而不带任何选项即可。

接下来,我们来看一下内核信息。

查看内核信息

/proc/ 的顶层,带有实际名称的文件和目录包含有关 Linux 内核运行状态的信息。以下是部分视图:

[donnie@localhost proc]$ ls -l
total 0
. . .
dr-xr-xr-x. 2 root root 0 Oct 19 14:24 acpi
dr-xr-xr-x. 5 root root 0 Oct 19 14:24 asound
-r--r--r--. 1 root root 0 Oct 19 14:26 buddyinfo
dr-xr-xr-x. 4 root root 0 Oct 19 14:24 bus
-r--r--r--. 1 root root 0 Oct 19 14:23 cgroups
-r--r--r--. 1 root root 0 Oct 19 14:23 cmdline
-r--r--r--. 1 root root 0 Oct 19 14:26 consoles
-r--r--r--. 1 root root 0 Oct 19 14:24 cpuinfo
. . .

就像在用户模式下的操作一样,你可以使用 cat 来查看一些不同的文件。例如,以下是 cpuinfo 文件的部分输出:

[donnie@localhost proc]$ cat cpuinfo
processor    : 0
vendor_id    : AuthenticAMD
cpu family    : 16
model        : 4
model name    : Quad-Core AMD Opteron(tm) Processor 2380
stepping    : 2
microcode    : 0x1000086
cpu MHz        : 2500.038
cache size    : 512 KB
physical id    : 0
siblings    : 1
core id        : 0
cpu cores    : 1
. . .

在这里,你可以看到我的 CPU 的类型和速度评级、缓存大小,以及这个 CentOS 虚拟机只运行在主机机器的八个 CPU 核心中的一个上。(在 Fedora 主机操作系统上执行此操作会显示关于主机机器所有八个核心的信息。)

是的,你没看错。我在一本旧版书的部分章节中,曾用一台配备 Opteron 处理器的 2009 年款 HP 工作站进行写作。我是从 eBay 上以非常便宜的价格买到的,它与 Fedora 的 LXDE 版本运行得非常流畅。而你在本书其他部分看到的 openSUSE 机器正是与此同款,来自同一个供应商。(所以,现在你知道我到底有多节俭了。)然而,很遗憾的是,RHEL 9 类型的发行版无法在第一代 x86_64 CPU 上运行,所以现在我用的是一台更新的机器来写这本 第三版

然而,针对我们目前的讨论,我们无需深入了解 /proc/ 中的每一个细节。对我们讨论来说,更重要的是可以从 /proc/ 内部设置的不同参数。例如,在 /proc/sys/net/ipv4/ 目录中,我们可以看到许多不同的项,它们可以调整以改变 IPv4 网络性能。以下是部分列表:

[donnie@localhost ipv4]$ pwd
/proc/sys/net/ipv4
[donnie@localhost ipv4]$ ls -l
total 0
-rw-r--r--. 1 root root 0 Oct 19 16:11 cipso_cache_bucket_size
-rw-r--r--. 1 root root 0 Oct 19 16:11 cipso_cache_enable
-rw-r--r--. 1 root root 0 Oct 19 16:11 cipso_rbm_optfmt
-rw-r--r--. 1 root root 0 Oct 19 16:11 cipso_rbm_strictvalid
dr-xr-xr-x. 1 root root 0 Oct 19 14:23 conf
-rw-r--r--. 1 root root 0 Oct 19 16:11 fib_multipath_hash_policy
-rw-r--r--. 1 root root 0 Oct 19 16:11 fib_multipath_use_neigh
-rw-r--r--. 1 root root 0 Oct 19 16:11 fwmark_reflect
-rw-r--r--. 1 root root 0 Oct 19 16:11 icmp_echo_ignore_all
. . .
. . .

我们可以使用 cat 命令查看每个参数,像这样:

[donnie@localhost ipv4]$ cat icmp_echo_ignore_all 
0
[donnie@localhost ipv4]$

所以,icmp_echo_ignore_all 参数被设置为 0,这意味着它被禁用。如果我从另一台机器 ping 这台机器,假设防火墙配置允许这种操作,这台机器将响应 ping 请求。如果需要更改此设置,以下是几种方法:

  • 从命令行使用 echo 向参数输入新值。

  • 使用命令行中的 sysctl 工具。

  • 配置 /etc/sysctl.conf 文件。

  • 将一个包含新配置的 .conf 文件添加到 /etc/sysctl.d/ 目录中。

  • 从 shell 脚本中运行命令。

让我们继续详细查看这些不同的方法。

使用 sysctl 设置内核参数。

你在旧版 Linux 教科书中看到的传统方法是 echo 一个值到 /proc/ 参数中。这不能直接与 sudo 一起使用,因此你需要使用 bash -c 命令强制执行命令。你可以看到我正在更改 icmp_echo_ignore_all 参数的值:

[donnie@localhost ~]$ sudo bash -c "echo '1' > /proc/sys/net/ipv4/icmp_echo_ignore_all"
[donnie@localhost ~]$ cat /proc/sys/net/ipv4/icmp_echo_ignore_all
1
[donnie@localhost ~]$

当值设置为 1 时,这台机器将忽略所有 ping 数据包,无论防火墙如何配置。像这样设置的任何值都是临时的,重启机器后将恢复为默认设置。

列表中的下一个设置是 icmp_echo_ignore_broadcasts,它的设置如下:

[donnie@localhost ipv4]$ cat icmp_echo_ignore_broadcasts 
1
[donnie@localhost ipv4]$

默认情况下已经启用,所以开箱即用时,Linux 已经能抵抗 拒绝服务DoS)攻击,这些攻击涉及 ICMP 广播洪泛。

使用 echo 配置 /proc/ 参数已经过时,个人而言,我不喜欢这么做。最好使用 sysctl,这是更现代的操作方式。它易于使用,你可以在 sysctl 的手册页中阅读到更多信息。

要查看所有参数设置的列表,只需执行以下操作:

[donnie@localhost ~]$ sudo sysctl -a
abi.vsyscall32 = 1
crypto.fips_enabled = 1
debug.exception-trace = 1
debug.kprobes-optimization = 1
dev.hpet.max-user-freq = 64
dev.mac_hid.mouse_button2_keycode = 97
dev.mac_hid.mouse_button3_keycode = 100
dev.mac_hid.mouse_button_emulation = 0
dev.raid.speed_limit_max = 200000
dev.raid.speed_limit_min = 1000
dev.scsi.logging_level = 0
fs.aio-max-nr = 65536
. . .
. . .

要设置一个参数,可以使用-w选项写入新值。诀窍在于,目录路径中的正斜杠被替换为点,并且忽略/proc/sys/路径部分。因此,要将icmp_echo_ignore_all值更改回0,我们需要执行以下操作:

[donnie@localhost ~]$ sudo sysctl -w net.ipv4.icmp_echo_ignore_all=0
net.ipv4.icmp_echo_ignore_all = 0
[donnie@localhost ~]$

在这种情况下,变更是永久性的,因为我只是将参数更改回其默认设置。不过,通常情况下,我们进行的任何更改只会持续到重启机器为止。有时,这样就可以了。而有时,我们可能需要使更改永久生效。

配置 sysctl.conf 文件

Ubuntu 和 CentOS/AlmaLinux 的默认配置之间有一些显著的差异。它们都使用/etc/sysctl.conf文件,但在 CentOS 和 AlmaLinux 中,该文件除了些许解释性注释外并没有其他内容。Ubuntu 和 CentOS/AlmaLinux 都在/usr/lib/sysctl.d/目录下有包含默认设置的文件,但 CentOS 和 AlmaLinux 的文件比 Ubuntu 的更多。在 Ubuntu 中,你会在/etc/sysctl.d/目录下找到其他带有默认值的文件。而在 CentOS 和 AlmaLinux 中,该目录只包含一个指向/etc/sysctl.conf文件的符号链接。此外,你会发现一些内容是硬编码到 Linux 内核中的,并未在任何配置文件中提到。典型的 Linux 方式是,每个发行版都有不同的配置方法,确保用户感到困惑。唯一一致的是,在任何虚拟机上,你都可以使用systemd-analyze cat-config sysctl.d命令查看在这些文件中定义的内核设置的摘要。(不过,要查看完整的内核设置列表,你仍然需要使用sysctl -a命令。)

在 AlmaLinux 机器的/usr/lib/sysctl.d/README文件中,你将找到有关该如何工作的额外信息。

现在,介绍部分已经结束,让我们看看能否弄清楚这一切。

配置 sysctl.conf – Ubuntu

在 Ubuntu 机器的/etc/sysctl.conf文件中,你会看到许多注释和一些可以调整的示例。注释很好地解释了各种设置的作用。因此,我们将从这里开始。

该文件的大部分内容包含了有助于提高网络安全性的设置。在文件的顶部,我们看到:

# Uncomment the next two lines to enable Spoof protection (reverse-path filter)
# Turn on Source Address Verification in all interfaces to
# prevent some spoofing attacks
#net.ipv4.conf.default.rp_filter=1
#net.ipv4.conf.all.rp_filter=1

伪造攻击是指恶意行为者向你发送带有伪造 IP 地址的网络数据包。伪造可以用于几种不同的目的,如 DoS 攻击、匿名端口扫描或欺骗访问控制。启用这些设置时,操作系统会验证它是否能访问数据包头中的源地址。如果无法访问,则会拒绝该数据包。你可能会想知道为什么这是禁用的,因为它看起来是件好事。然而,事实并非如此。它在另一个文件中已启用。如果你查看/etc/sysctl.d/10-network-security.conf文件,你会看到它在那里已启用。所以,不需要取消注释这两行。

接下来,我们看到的是:

# Uncomment the next line to enable TCP/IP SYN cookies
# See http://lwn.net/Articles/277146/
# Note: This may impact IPv6 TCP sessions too
#net.ipv4.tcp_syncookies=1

一种 DoS 攻击形式是向目标机器发送大量 SYN 数据包,但没有完成剩余的三次握手。这可能导致受害机器有大量半开连接,最终耗尽机器接受更多合法连接的能力。启用 SYN cookies 可以帮助防止这种攻击。在我用来写这本书前一版的 Ubuntu 18.04 上,SYN cookies 在/etc/sysctl.d/10-network-security.conf文件中已经启用。而在 Ubuntu 22.04 上,除了sysctl.conf文件中的禁用设置外,其他任何sysctl配置文件中都找不到该设置。然而,在 22.04 机器上运行sudo sysctl -a命令,会显示 SYN cookies 确实已启用:

donnie@ubuntu-2204:~$ sudo sysctl -a | grep syncookie
net.ipv4.tcp_syncookies = 1
donnie@ubuntu-2204:~$

要找到这个设置的位置,我们需要做一些巧妙的侦探工作。在 22.04 机器的/boot/目录下,你会看到每个已安装内核都有一个config文件。当我使用grep -i执行不区分大小写的搜索,在某个config文件中查找syncookies文本字符串时,我什么也没找到。于是我将搜索范围扩展到所有包含syn的文本字符串。这是搜索结果:

donnie@ubuntu-2204:/boot$ grep -i 'syn' config-5.15.0-52-generic 
. . .
. . .
CONFIG_SYN_COOKIES=y
. . .
. . .

所以,我们看到,在 Ubuntu 22.04 上,开发者选择将 SYN cookies 的设置硬编码到内核中,而不是将其设置在sysctl配置文件中。正如我们之前在 Ubuntu 18.04 机器上看到的那样,sysctl.conf文件中不需要取消注释syncookies这一行。

接下来我们看到的是:

# Uncomment the next line to enable packet forwarding for IPv4
#net.ipv4.ip_forward=1

取消注释这一行将允许网络数据包在具有多个网络接口的机器之间流动。除非你在设置路由器或虚拟私人网络服务器,否则保持这个设置不变。

到目前为止,我们只看到了 IPv4 的内容。接下来我们来看一个关于 IPv6 的:

# Uncomment the next line to enable packet forwarding for IPv6
#  Enabling this option disables Stateless Address Autoconfiguration
#  based on Router Advertisements for this host
#net.ipv6.conf.all.forwarding=1

一般来说,你也会希望将这行保持为注释状态,就像现在这样。在 IPv6 环境中禁用无状态地址自动配置意味着你需要设置一个 DHCPv6 服务器,或者为所有主机设置静态 IPv6 地址。

下一节控制 ICMP 重定向:

# Do not accept ICMP redirects (prevent MITM attacks)
#net.ipv4.conf.all.accept_redirects = 0
#net.ipv6.conf.all.accept_redirects = 0
# _or_
# Accept ICMP redirects only for gateways listed in our default
# gateway list (enabled by default)
# net.ipv4.conf.all.secure_redirects = 1
#

允许 ICMP 重定向可能会让 中间人攻击MITM)成功。取消注释此片段顶部两行代码将完全禁用 ICMP 重定向。底部部分的最后一行允许重定向,但仅限于来自受信任网关的重定向。这行代码有些迷惑人,因为即使它被注释掉,并且其他配置文件中没有涉及此内容,安全重定向实际上默认是启用的。我们可以通过使用 grep 过滤 sysctl -a 输出,来看到这一点:

donnie@ubuntu-2204:/etc$ sudo sysctl -a | grep 'secure_redirects'
net.ipv4.conf.all.secure_redirects = 1
net.ipv4.conf.default.secure_redirects = 1
net.ipv4.conf.enp0s3.secure_redirects = 1
net.ipv4.conf.lo.secure_redirects = 1
donnie@ubuntu-2204:/etc$

在这里,我们可以看到所有网络接口上都启用了安全重定向。但是,如果你确定你的机器永远不会作为路由器使用,最好还是完全禁用 ICMP 重定向。(稍后我们会做这个操作。)

本文件中的最后一个网络项涉及火星数据包:

# Log Martian Packets
#net.ipv4.conf.all.log_martians = 1
#

现在,如果你和我一样年纪大,可能记得 60 年代有一个很傻的电视节目叫做 My Favorite Martian。不过,这个设置和那个没有任何关系。火星数据包的源地址通常不能被特定的网络接口接受。例如,如果你的面向互联网的服务器接收到带有私有 IP 地址或回环设备地址的数据包,那就是一个火星数据包。为什么它们被称为火星数据包呢?因为有人说这些数据包不是来自地球。无论如何,火星数据包可能会消耗网络资源,所以了解它们是很有帮助的。你可以通过取消注释前面代码片段中的那一行,或者在 /etc/sysctl.d/ 目录下放置一个覆盖文件来启用日志记录功能。(我们稍后也会做这个。)

以下代码片段是 Magic 系统请求键 的内核参数:

# Magic system request key
# 0=disable, 1=enable all
# Debian kernels have this set to 0 (disable the key)
# See https://www.kernel.org/doc/Documentation/sysrq.txt
# for what other values do
#kernel.sysrq=438

当启用此参数时,你可以通过按下特定的 Magic 键 序列来执行某些功能,例如关闭或重启系统、向进程发送信号、转储进程调试信息以及其他几个功能。你可以通过按下 Alt + SysReq + 命令键的组合键来实现。(SysReq 键是一些键盘上的 PrtScr 键,而命令键是触发某些特定命令的键。)将此值设置为 0 会完全禁用此功能,设置为 1 会启用所有 Magic 键功能,设置大于 1 的值只会启用特定的功能。在本文件中,此选项似乎是禁用的,但实际上它在 /etc/sysctl.d/10-magic-sysrq.conf 文件中是启用的。如果你正在处理一台被锁在机房中且无法通过串口控制台远程访问的服务器,这可能不是什么大问题。然而,对于一台暴露在外或可以通过串口控制台访问的机器,你可能希望禁用此功能。(稍后我们也会进行此操作。)

在某些情况下,攻击者可能会创建指向敏感文件的链接,从而轻松访问它们。链接保护功能在 Ubuntu 18.04 的/etc/sysctl.d/10-link-restrictions.conf文件中启用,而在 Ubuntu 22.04 的/usr/lib/sysctl.d/99-protect-links.conf文件中启用。以下是 22.04 上的设置示例:

# Protected links
#
# Protects against creating or following links under certain conditions
# Debian kernels have both set to 1 (restricted) 
# See https://www.kernel.org/doc/Documentation/sysctl/fs.txt
fs.protected_fifos = 1
fs.protected_hardlinks = 1
fs.protected_regular = 2
fs.protected_symlinks = 1

这基本上覆盖了 Ubuntu 中的设置。接下来,我们来看看 CentOS 和 AlmaLinux。

配置 sysctl.conf – CentOS 和 AlmaLinux

在 CentOS 和 AlmaLinux 中,/etc/sysctl.conf 文件为空,仅包含一些注释。这些注释告诉你查找其他地方的默认配置文件,并通过在/etc/sysctl.d/目录中创建新的配置文件来进行更改。

CentOS 和 AlmaLinux 的默认安全设置与 Ubuntu 非常相似,唯一的区别在于它们的配置位置不同。例如,在 CentOS 和 AlmaLinux 中,防伪(rp_filter)参数和链接保护参数位于/usr/lib/sysctl.d/50-default.conf文件中。

通过将sysctl -a命令传递给grep,你也能看到 SYN cookies 已启用:

[donnie@alma9 ~]$ sudo sysctl -a | grep 'syncookie'
net.ipv4.tcp_syncookies = 1
[donnie@alma9 ~]$

secure_redirects 的情况也是如此:

[donnie@alma9 ~]$ sudo sysctl -a | grep 'secure_redirects'
net.ipv4.conf.all.secure_redirects = 1
net.ipv4.conf.default.secure_redirects = 1
net.ipv4.conf.enp0s3.secure_redirects = 1
net.ipv4.conf.lo.secure_redirects = 1
net.ipv4.conf.virbr0.secure_redirects = 1
net.ipv4.conf.virbr0-nic.secure_redirects = 1
[donnie@alma9 ~]$

就像在 Ubuntu 中一样,SYN cookies 设置是硬编码到 Linux 内核中的,正如我们在这里看到的:

[donnie@alma9 ~]$ cd /boot
[donnie@alma9 boot]$ grep -i cookies config-5.14.0-70.30.1.el9_0.x86_64 
CONFIG_SYN_COOKIES=y
[donnie@alma9 boot]$

好的,到目前为止一切顺利。我们继续进行。

设置额外的内核硬化参数

到目前为止,我们看到的情况还不错。我们查看的多数参数已经设置为最安全的值。但是否还有改进的空间?确实有。虽然通过查看任何配置文件你是看不出来的,但在 Ubuntu、CentOS 和 AlmaLinux 中,有很多项目的默认值并未在任何常规配置文件中设置。查看这些问题的最佳方法是使用系统扫描工具,例如Lynis

Lynis 是一个安全扫描工具,可以显示系统的许多信息。(我们将在第十四章漏洞扫描与入侵检测中详细介绍它。)现在,我们只讨论它如何帮助我们加固 Linux 内核。

扫描完成后,你会在屏幕输出中看到一个[+] Kernel Hardening部分。它相当长,这里只是其中的一部分:

[+] Kernel Hardening
------------------------------------
 - Comparing sysctl key pairs with scan profile
 - fs.protected_hardlinks (exp: 1) [ OK ]
 - fs.protected_symlinks (exp: 1) [ OK ]
 - fs.suid_dumpable (exp: 0) [ OK ]
 - kernel.core_uses_pid (exp: 1) [ OK ]
 - kernel.ctrl-alt-del (exp: 0) [ OK ]
 - kernel.dmesg_restrict (exp: 1) [ DIFFERENT ]
 - kernel.kptr_restrict (exp: 2) [ DIFFERENT ]
 - kernel.randomize_va_space (exp: 2) [ OK ]
 - kernel.sysrq (exp: 0) [ DIFFERENT ]
. . .
. . .

所有标记为OK的项表示最佳安全配置。标记为DIFFERENT的项应更改为建议的exp:值,括号中的exp代表期望的值。让我们现在在动手实验中进行这些更改。

动手实验 – 使用 Lynis 扫描内核参数

Lynis 在 Ubuntu 的正常仓库中可用,在 CentOS 和 AlmaLinux 的 EPEL 仓库中也能找到。虽然它总是比从作者网站上直接下载的版本落后几个版本,但目前这没问题。当我们进入第十四章漏洞扫描与入侵检测时,我会展示如何获取最新版本。现在我们开始吧:

  1. 从 Ubuntu 的软件仓库中安装 Lynis,如下所示:
sudo apt update
sudo apt install lynis

对于 CentOS 07,请执行以下操作:

sudo yum install lynis

对于 AlmaLinux 8 或 9,执行以下操作:

sudo dnf install lynis
  1. 使用以下命令扫描系统:
sudo lynis audit system
  1. 扫描完成后,向上滚动回[+] Kernel Hardening部分的输出。将sysctl键对复制并粘贴到文本文件中。将其保存为secure_values.conf文件,保存在你自己的主目录中。文件内容应如下所示:
 - fs.protected_hardlinks (exp: 1) [ OK ]
 - fs.protected_symlinks (exp: 1) [ OK ]
 - fs.suid_dumpable (exp: 0) [ OK ]
 - kernel.core_uses_pid (exp: 1) [ OK ]
 - kernel.ctrl-alt-del (exp: 0) [ OK ]
 - kernel.dmesg_restrict (exp: 1) [ DIFFERENT ]
 - kernel.kptr_restrict (exp: 2) [ DIFFERENT ]
 - kernel.randomize_va_space (exp: 2) [ OK ]
 - kernel.sysrq (exp: 0) [ DIFFERENT ]
 - kernel.yama.ptrace_scope (exp: 1 2 3) [ DIFFERENT ]
 - net.ipv4.conf.all.accept_redirects (exp: 0) [ DIFFERENT ]
 - net.ipv4.conf.all.accept_source_route (exp: 0) [ OK ]
. . .
. . .
  1. 使用grep将所有DIFFERENT行发送到一个新文件。命名为60-secure_values.conf
grep 'DIFFERENT' secure_values.conf > 60-secure_values.conf
  1. 编辑60-secure_values.conf文件,将其转换为sysctl配置格式。将每个参数设置为当前在括号内的exp值。最终文件应如下所示:
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.sysrq = 0
kernel.yama.ptrace_scope = 1 2 3
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.default.log_martians = 1
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
  1. 将该文件复制到/etc/sysctl.d/目录:
sudo cp 60-secure_values.conf /etc/sysctl.d/
  1. 重启机器以读取新文件中的值:
sudo shutdown -r now

重复步骤 2。大多数项目现在应该显示出它们最安全的值。然而,你可能会看到一些DIFFERENT行出现。没关系;只需将这些参数的行移动到主/etc/sysctl.conf文件中,然后再次重启机器。

这就是实验的结束——恭喜你!

我们已经讨论了一些在这个过程中更改的项目。以下是其余项目的详细说明:

  • kernel.dmesg_restrict = 1:默认情况下,任何非特权用户都可以运行dmesg命令,这个命令允许用户查看不同类型的内核信息。其中一些信息可能是敏感的,因此我们希望将此参数设置为1,使得只有具有 root 权限的人才能使用dmesg

  • kernel.kptr_restrict = 2:此设置防止/proc暴露内存中的内核地址。将其设置为0会完全禁用它,而将其设置为1则会阻止非特权用户查看地址信息。将其设置为2,如我们所做的,防止任何人查看地址信息,无论其权限等级如何。不过,需要注意的是,将其设置为12可能会阻止某些性能监控程序(例如perf)的运行。如果你必须进行性能监控,可能需要将其设置为0。(这并不像听起来那么糟糕,因为启用了kernel.dmesg_restrict = 1设置可以帮助缓解这个问题。)

  • kernel.yama.ptrace_scope = 1 2 3:这会对ptrace工具进行限制,ptrace是一个调试程序,黑客也可以使用它。1限制ptrace仅调试父进程。2意味着只有具有 root 权限的人才能使用ptrace,而3则阻止任何人使用ptrace追踪进程。

在本节中,你学习了如何配置各种内核参数以帮助加固你的系统。接下来,我们将通过限制谁能查看进程信息来进一步加固系统。

防止用户看到彼此的进程

默认情况下,用户可以使用像 pstop 这样的工具查看其他用户的进程,以及自己的进程。为了演示这一点,下面是 ps aux 命令的部分输出:

[donnie@localhost ~]$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.7 179124 13752 ?        Ss   12:05   0:03 /usr/lib/systemd/systemd --switched-root --system --deserialize 17
root         2  0.0  0.0      0     0 ?        S    12:05   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        I<   12:05   0:00 [rcu_gp]
. . .
. . .
colord 2218 0.0 0.5 323948 10344 ? Ssl 12:06 0:00 /usr/libexec/colord
gdm 2237 0.0 0.2 206588 5612 tty1 Sl 12:06 0:00 /usr/libexec/ibus-engine-simple
root 2286 0.0 0.6 482928 11932 ? Sl 12:06 0:00 gdm-session-worker [pam/gdm-password]
donnie 2293 0.0 0.5 93280 9696 ? Ss 12:06 0:00 /usr/lib/systemd/systemd --user
donnie 2301 0.0 0.2 251696 4976 ? S 12:06 0:00 (sd-pam)
donnie 2307 0.0 0.6 1248768 12656 ? S<sl 12:06 0:00 /usr/bin/pulseaudio --daemonize=no
. . .

即使只是拥有普通用户权限,我也可以查看属于 root 用户和其他系统用户的进程,以及我自己的进程。(如果我的猫也登录了,我还可以查看它们的进程。)这些信息对管理员来说很有用,但也可能帮助坏人。这些信息可以帮助黑客计划对系统的攻击,甚至可能泄露一些敏感信息。应对这一问题的最佳方法是使用 hidepid 选项挂载 /proc 文件系统,只要你不是在使用某种 RHEL 9 类型的发行版。

这个功能在 RHEL 9 类型的发行版中已被禁用。所以,不要在任何 AlmaLinux 9 机器上尝试这个方法。它不仅不起作用,可能还会破坏你的机器。

在 Ubuntu、CentOS 7 或 AlmaLinux 8 虚拟机上,你可以通过在 /etc/fstab 文件末尾添加一行来实现这一点,如下所示:

proc    /proc    proc    hidepid=2   0    0

然后,重新挂载 /proc,如下所示:

sudo mount -o remount proc

现在,任何没有 sudo 权限的用户只能查看自己的进程。(很巧妙,对吧?)

hidepid 选项的三个值如下:

0:这是默认设置,允许所有用户查看彼此的进程。

1:这允许所有用户查看 /proc 中其他用户的进程目录。然而,用户只能进入自己的进程目录(cd)。此外,他们只能使用 pstop 查看自己的进程信息。

2:这会隐藏所有其他用户的进程信息,包括 /proc 中的进程目录。

现在,既然你已经了解了 /proc 文件系统的内部工作原理以及如何为最佳安全配置它,让我们来看一下进程隔离

理解进程隔离

网络入侵者的主要目标是获得执行恶意行为所需的权限。这通常涉及以普通用户身份登录,然后进行某种形式的权限提升垂直提升涉及获得 root 权限,而水平提升则是获得其他普通用户的权限。如果其他普通用户在其可以访问的文件夹中有敏感文档,那么水平提升可能就足以满足入侵者的需求。自主访问控制(DAC)和强制访问控制(MAC)可以提供帮助,但我们还希望将进程相互隔离,并确保进程仅以最低的权限运行。

在规划针对这些类型攻击的防御时,考虑到攻击可能来自外部或内部。因此,是的,你确实需要防范来自公司员工的攻击。

在这一节中,我们将探讨 Linux 内核中促进进程隔离的各种功能。然后,我们将探讨一些有趣的使用这些功能的方式。

理解控制组(cgroups)

控制组,更常见的叫法是cgroups,最早是在 2010 年 Red Hat Enterprise Linux 6 中引入的。最初,它们只是一个附加功能,用户必须手动创建它们。如今,随着systemd init系统的出现,cgroups 已经成为操作系统的一个重要组成部分,每个进程默认运行在自己的 cgroup 中。

当我编写这本书的前两版时,所有企业级的systemd基础 Linux 发行版都在运行cgroups Version 1。然而现在,它们都已切换到cgroup Version 2。(是的,Version 1 是cgroups,Version 2 是cgroup,我也不知道为什么。)尽管这两个版本在架构上有较大的差异,但它们的使用方法差别不大。主要的不同在于 Version 2 提供了更多的功能,并且可以设置的一些参数名称不同。如果你想深入了解 cgroups,可以阅读我的另一本书《Linux 服务管理与 systemd 简明教程》。

使用 cgroups 时,进程运行在它们自己的内核空间和内存空间中。如果需要,管理员可以轻松配置一个 cgroup 来限制该进程可以使用的资源。这不仅有助于安全性,也有助于优化系统性能。

那么,什么是 cgroup?嗯,它其实就是一组进程,为了某个特定目的而聚集在一起。以下是你可以用 cgroups 做的事情:

  • 设置资源限制:对于每个 cgroup,你可以设置 CPU 使用、I/O 使用和内存使用的资源限制。

  • 执行不同的计费功能:你可以衡量每个 cgroup 的资源使用情况,这使得根据客户使用的资源来收费变得更加容易。

  • 优先分配资源:你可以为一个疯狂占用资源的用户设置限制。

  • 冻结、检查点和重启:这些功能对于故障排除非常有用。它们允许你停止一个进程,拍摄系统状态快照,并从备份中恢复系统状态。

  • 将进程分配到特定的 CPU 核心:这是 Version 2 的一项酷炫功能。

没有足够的空间来查看所有这些功能,但没关系。现在,我们主要关注的是设置资源限制。除了少数几个例外,所有基于systemd的 Linux 发行版在这方面的工作方式基本相同。目前,让我们在一台 AlmaLinux 9 机器上查看这个功能。首先,我们会安装 Apache web 服务器包,如下所示:

sudo dnf install httpd
sudo systemctl enable --now httpd

默认情况下,系统中的每个 cgroup 没有定义的资源限制。定义资源限制的第一步是启用 CPU 使用、内存使用和 I/O 使用的会计功能。我们可以通过手动编辑每个服务的systemd服务文件来实现这一点,但更简单的方法是直接运行systemctl命令,如下所示:

sudo systemctl set-property httpd.service MemoryAccounting=1 CPUAccounting=1 BlockIOAccounting=1

我们刚刚在我们的 AlmaLinux 9 机器上为 Apache web 服务器开启了会计功能。(在 Ubuntu 机器上执行的命令也是一样,只不过我们使用apache2.service代替httpd.service。)现在,当我们查看/etc/systemd/system.control/目录时,我们会看到创建了一个httpd.service.d目录。在该目录下是启用会计功能的文件:

[donnie@localhost httpd.service.d]$ pwd
/etc/systemd/system.control/httpd.service.d
[donnie@localhost httpd.service.d]$ ls -l
total 12
-rw-r--r--. 1 root root 153 Oct 30 15:07 50-BlockIOAccounting.conf
-rw-r--r--. 1 root root 149 Oct 30 15:07 50-CPUAccounting.conf
-rw-r--r--. 1 root root 152 Oct 30 15:07 50-MemoryAccounting.conf
[donnie@localhost httpd.service.d]$

每个文件中都有两行,用于修改原始的httpd.service文件,以启用会计功能。例如,这是CPUAccounting的设置:

[donnie@localhost httpd.service.d]$ cat 50-CPUAccounting.conf 
# This is a drop-in unit file extension, created via "systemctl set-property"
# or an equivalent operation. Do not edit.
[Service]
CPUAccounting=yes
[donnie@localhost httpd.service.d]$

现在我们已经为 Apache 服务启用了会计功能,我们可以为其设置一些资源限制。(默认情况下,没有限制。)假设我们希望将 Apache 的 CPU 使用限制为 40%并将内存使用限制为 500 MB。我们可以通过以下命令设置这两个限制:

[donnie@localhost ~]$ sudo systemctl set-property httpd.service CPUQuota=40% MemoryLimit=500M
[donnie@localhost ~]$

这个命令在/etc/systemd/system.control/httpd.service.d/目录中创建了另外两个文件:

[donnie@localhost httpd.service.d]$ ls -l
total 20
-rw-r--r--. 1 root root 153 Oct 30 15:07 50-BlockIOAccounting.conf
-rw-r--r--. 1 root root 149 Oct 30 15:07 50-CPUAccounting.conf
-rw-r--r--. 1 root root 144 Oct 30 15:18 50-CPUQuota.conf
-rw-r--r--. 1 root root 152 Oct 30 15:07 50-MemoryAccounting.conf
-rw-r--r--. 1 root root 153 Oct 30 15:18 50-MemoryLimit.conf
[donnie@localhost httpd.service.d]$

让我们使用cat命令查看其中一个文件,看看文件的格式:

[donnie@localhost httpd.service.d]$ cat 50-CPUQuota.conf 
# This is a drop-in unit file extension, created via "systemctl set-property"
# or an equivalent operation. Do not edit.
[Service]
CPUQuota=40%
[donnie@localhost httpd.service.d]$

我们可以用相同的方式为其他服务分配资源。例如,如果这是一个Linux-Apache-MySQL/MariaDB-PHPLAMP)服务器,我们可以将剩余的 CPU 和内存资源的一部分分配给 PHP 服务,其余的分配给 MySQL/MariaDB 服务。

LAMP 是许多流行内容管理系统(例如 WordPress 和 Joomla)的基础。

我们还可以为用户账户设置资源限制。例如,让我们将 Katelyn 的 CPU 使用限制为 20%和内存使用限制为 500 MB。首先,我们需要获取 Katelyn 的用户 ID 号。我们可以通过id命令来完成:

[donnie@localhost ~]$ id katelyn
uid=1001(katelyn) gid=1001(katelyn) groups=1001(katelyn)
[donnie@localhost ~]$

所以,她的 UID 是1001。让我们为她启用会计功能并设置限制:

[donnie@localhost ~]$ sudo systemctl set-property user-1001.slice MemoryAccounting=1 CPUAccounting=1 BlockIOAccounting=1
[donnie@localhost ~]$ sudo systemctl set-property user-1001.slice CPUQuota=20% MemoryLimit=500M
[donnie@localhost ~]

如果我们查看/etc/systemd/system.control/user-1001.slice.d/目录,我们会看到为httpd服务创建的相同文件集。

我之前已经提到过在 CentOS/AlmaLinux 和 Ubuntu 上进行此操作的区别。也就是说,某些服务在不同发行版中的名称不同。在这种情况下,CentOS/AlmaLinux 上的服务名是httpd.service,而 Ubuntu 上则是apache2.service。除此之外,Ubuntu、CentOS 8 和 AlmaLinux 8/9 的操作方式是相同的。

在 CentOS 7 中,/etc/systemd/ 目录下没有 system.control 目录。相反,httpd.service.d 目录是在 /etc/systemd/system/ 目录下创建的。当我第一次尝试为 Katelyn 设置限制时,依然使用 UID 1001,CentOS 7 不允许我这么做,直到 Katelyn 登录并激活她的 slice。她的文件被创建在 /run/systemd/system/user-1001.slice.d/ 目录中,该目录仅包含短暂的运行时文件。因此,与 AlmaLinux 8/9 不同,这些文件在重启后不会保留。这意味着,如果你需要在 CentOS 7 上设置用户资源限制,你需要意识到它们会在重启机器后消失。

cgroups 远不止我在这里展示的内容,但这没关系。在本节中,我们已经看了 cgroups 如何增强安全性的两种方式:

  • 它们提供进程隔离。

  • 使用它们来限制资源使用可以帮助防止 DoS 攻击。

接下来,我们将简要了解命名空间和命名空间隔离的概念。

理解命名空间隔离

命名空间 是一种内核安全特性,最早在 2002 年 Linux 内核版本 2.4.19 中引入。命名空间允许一个进程拥有自己的计算资源集,其他进程无法看到这些资源。它们在多个用户共享同一台服务器资源时尤其有用。每个用户的进程将拥有自己的命名空间。目前,有七种类型的命名空间:

  • 挂载 (mnt):这是最早的命名空间,于 Linux 内核 2.4.19 引入。那时,这是唯一的命名空间。它允许每个进程拥有自己的根文件系统,其他进程无法看到,除非你选择共享它。这是防止信息泄漏的好方法。

  • UTS:UTS 命名空间允许每个进程拥有自己独特的主机名和域名。

  • PID:每个正在运行的进程可以拥有自己的一组 PID 编号。PID 命名空间可以进行嵌套,以便父命名空间可以看到子命名空间的 PID(注意:子命名空间不能看到父命名空间)。

  • 网络 (net):这允许你为每个进程创建一个完整的虚拟网络。每个虚拟网络可以拥有自己的子网、虚拟网络接口、路由表和防火墙。

  • 进程间通信 (ipc):这也通过防止两个进程共享相同的内存空间来防止数据泄漏。每个运行中的进程可以访问自己的内存空间,但其他进程将被阻止。

  • 控制组 (cgroup):此命名空间隐藏进程所属 cgroup 的身份。

  • 用户:用户命名空间允许用户在不同进程中具有不同级别的权限。例如,用户在一个进程中可以拥有 root 权限,而在另一个进程中则仅拥有普通用户权限。

要查看这些命名空间,只需进入 /proc 文件系统中的任何一个编号目录,并查看 ns 目录的内容。以下是我某台机器上的一个示例:

[donnie@localhost ns]$ pwd
/proc/7669/ns
[donnie@localhost ns]$ sudo ls -l
total 0
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 net -> 'net:[4026531992]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 pid -> 'pid:[4026531836]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 user -> 'user:[4026531837]'
lrwxrwxrwx. 1 donnie donnie 0 Oct 30 16:16 uts -> 'uts:[4026531838]'
[donnie@localhost ns]$ 

眼尖的你会注意到,在这个目录中有一个我们尚未讨论的额外项。pid_for_children 项用于跟踪子命名空间中的 PID。

虽然你当然可以创建自己的命名空间,但除非你是软件开发人员,否则很可能永远不会这么做。大多数情况下,你只会使用已经内置了命名空间技术的产品。一些现代 Web 浏览器使用命名空间为每个打开的标签页创建一个沙盒。你可以使用像 Firejail 这样的产品,在它自己的安全沙盒内运行一个普通程序。(我们稍后会讨论这个问题。)还有 Docker,使用命名空间帮助隔离 Docker 容器之间以及容器与主机操作系统之间的关系。

我们刚刚对命名空间有了一个高层次的概览。接下来,让我们看看内核能力。

理解内核能力

当你执行 ps aux 命令时——或者如果你以 hidepid=1hidepid=2 选项挂载了 /proc,则执行 sudo ps aux 命令——你会看到许多由 root 用户拥有的进程。这是因为这些进程必须访问一些普通用户无法访问的系统资源。然而,让服务以完全 root 权限运行可能会带来一些安全问题。幸运的是,有一些方法可以缓解这个问题。

例如,任何网络服务器服务,如 Apache 或 Nginx,都需要以 root 权限启动,以便绑定到 80443 端口,这些都是特权端口。然而,Apache 和 Nginx 都通过在服务启动后降低 root 权限,或通过生成属于非特权用户的子进程来缓解这个问题。在这里,我们可以看到主 Apache 进程生成了属于非特权 apache 用户的子进程:

[donnie@centos7-tm1 ~]$ ps aux | grep http
root 1015 0.0 0.5 230420 5192 ? Ss 15:36 0:00 /usr/sbin/httpd -DFOREGROUND
apache 1066 0.0 0.2 230420 3000 ? S 15:36 0:00 /usr/sbin/httpd -DFOREGROUND
apache 1067 0.0 0.2 230420 3000 ? S 15:36 0:00 /usr/sbin/httpd -DFOREGROUND
apache 1068 0.0 0.2 230420 3000 ? S 15:36 0:00 /usr/sbin/httpd -DFOREGROUND
apache 1069 0.0 0.2 230420 3000 ? S 15:36 0:00 /usr/sbin/httpd -DFOREGROUND
apache 1070 0.0 0.2 230420 3000 ? S 15:36 0:00 /usr/sbin/httpd -DFOREGROUND
donnie 1323 0.0 0.0 112712 964 pts/0 R+ 15:38 0:00 grep --color=auto http
[donnie@centos7-tm1 ~]$

但并不是所有软件都能做到这一点。有些程序设计为始终以 root 权限运行。在某些情况下——并不是所有情况,但有些——你可以通过为程序可执行文件应用内核能力来解决这个问题。

能力允许 Linux 内核将 root 用户可以执行的操作划分为不同的单位。假设你刚刚编写了一个很酷的自定义程序,它需要访问一个特权网络端口。如果没有能力,你就必须以 root 权限启动该程序,并让它以 root 权限运行,或者不得不编写代码使其在启动后能够放弃 root 权限。通过应用适当的能力,非特权用户也能启动该程序,并且程序只会以该用户的权限运行。(稍后会详细介绍。)

能力种类太多,无法在这里一一列举(总共有大约 40 种),但你可以使用以下命令查看完整列表:

man capabilities

回到我们之前的例子,假设我们需要使用 Python 设置一个非常基础的 Web 服务器,任何非特权用户都可以启动。 (我们必须使用 Python 2,因为它在 Python 3 中无法工作。)我们在 AlmaLinux 8 机器上做这个操作,因为 Python 2 在 AlmaLinux 9 和 Ubuntu 22.04 中都不可用。

运行一个简单的 Python 网络服务器的命令是:

python2 -m SimpleHTTPServer 80

然而,这样做不行,因为它需要绑定到 80 端口,这是一个特权端口,通常由 Web 服务器使用。在此命令的输出底部,你将看到问题所在:

socket.error: [Errno 13] Permission denied

在命令前加上sudo可以解决问题并允许网络服务器运行。然而,我们并不希望这样做。我们更希望允许非特权用户启动它,而且更希望它在没有 root 用户权限的情况下运行。解决这个问题的第一步是找到 Python 可执行文件,如下所示:

[donnie@localhost ~]$ which python2
/usr/bin/python2
[donnie@localhost ~]$

大多数情况下,pythonpython2 命令是一个符号链接,指向另一个可执行文件。我们可以通过简单的 ls -l 命令来验证这一点:

[donnie@localhost ~]$ ls -l /usr/bin/python2
lrwxrwxrwx. 1 root root 9 Oct  8 17:08 /usr/bin/python2 -> python2.7
[donnie@localhost ~]$

所以,python2 链接指向 python2.7 可执行文件。现在,让我们查看这个文件是否分配了任何能力:

[donnie@localhost ~]$ getcap /usr/bin/python2.7 
[donnie@localhost ~]$

如果没有输出,意味着没有能力设置。当我们查看能力的 man 页面时,我们会发现 CAP_NET_BIND_SERVICE 能力似乎就是我们需要的。它的单行描述是:将套接字绑定到互联网域特权端口端口号小于 1024)。好的,这听起来不错。那么,让我们在 python2.7 可执行文件上设置这个能力,看看会发生什么。既然我们使用 getcap 来查看文件能力,你可能猜到我们将使用 setcap 来设置一个能力。(你猜得对。)现在我们来做吧:

[donnie@localhost ~]$ sudo setcap 'CAP_NET_BIND_SERVICE+ep' /usr/bin/python2.7
[sudo] password for donnie: 
[donnie@localhost ~]$ getcap /usr/bin/python2.7 
/usr/bin/python2.7 = cap_net_bind_service+ep
[donnie@localhost ~]$

能力名称末尾的 +ep 表示我们将能力设置为 有效(激活)和 允许。现在,当我尝试仅使用我自己的正常权限运行这个 Web 服务器时,它将正常工作:

[donnie@localhost ~]$ python2 -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...

当我在主机上使用 Firefox 连接到这个服务器时,我将看到我的主目录中所有文件和目录的列表:

图 11.1:使用 SimpleHTTPServer 的目录列表

图 11.1:使用 SimpleHTTPServer 的目录列表

Linux 能力在其他方面也非常有用。在任何 Linux 系统上,ping 工具需要 root 权限才能构造它所需的网络数据包。然而,任何人都可以作为普通用户使用 ping。如果你查看某些 Linux 发行版上的 ping 可执行文件,你会看到它已经被 Linux 维护者分配了两个能力,就像在 CentOS 7 机器上看到的那样:

[donnie@localhost ~]$ getcap /usr/bin/ping
/usr/bin/ping = cap_net_admin,cap_net_raw+p
[donnie@localhost ~]$

请注意,在 AlmaLinux 8 或 9 中,这两种能力都没有设置,只有 cap_net_raw 能力在 Ubuntu 22.04 中设置了。

尽管这一切看起来很酷,但也有一些缺点:

  • 确定一个程序究竟需要哪些能力并不是总能一目了然。实际上,这可能需要一些实验,才能弄清楚正确的设置。

  • 设置能力并不是万能的。很多时候,你会发现设置了特定的能力仍然不能让程序做你需要它做的事。实际上,可能没有任何一种能力能让程序在不需要 root 权限的情况下按你希望的方式运行。

  • 执行系统更新可能会替换你为可执行文件分配的能力。(对于 ping,我们不必担心这一点,因为能力是由 Linux 维护者设置的。)

好吧,所以你很可能永远不需要设置任何能力。不过,这是我在物联网安全领域客户用来帮助锁定物联网设备的工具之一,因此它确实有实际用途。而且,能力是我们稍后将要探讨的某些技术的构建块。

实验室操作 – 设置内核能力

对于这个实验,你将允许普通用户运行 Python 网页服务器。你需要使用 Python 2,而最新的 Linux 发行版中并不包含它。(Python 3 不行。)所以,使用你的 AlmaLinux 8 虚拟机开始吧。让我们开始吧:

  1. 如果 Apache 已安装在你的虚拟机上,确保它已经停止:
sudo systemctl stop httpd
  1. 安装 Python 2:
sudo dnf install python2
  1. 从你自己的主目录开始,尝试以普通用户权限启动 Python SimpleHTTPServer,并记录错误信息:
python2 -m SimpleHTTPServer 80
  1. 查看 Python 可执行文件上是否设置了任何能力:
getcap /usr/bin/python2.7
  1. 在 Python 可执行文件上设置 CAP_NET_BIND_SERVICE 能力:
sudo setcap 'CAP_NET_BIND_SERVICE+ep' /usr/bin/python2.7
  1. 重复 步骤 34。这次,应该能成功。

  2. 确保虚拟机防火墙上的端口 80 是开放的,并使用主机机器的网页浏览器访问服务器。

  3. 使用 Ctrl + C 关闭网页服务器。

  4. 查看 ping 可执行文件上分配的能力:

getcap /usr/bin/ping
  1. 查阅 man 页面中的能力部分,特别是关于各种能力的内容。

实验结束了 – 恭喜你!

到目前为止,你已经了解了如何设置文件能力以及它们能为你做什么、不能做什么。接下来,我们将探讨如何控制系统调用。

理解 SECCOMP 和系统调用

每次你在 Linux 机器上运行任何命令时,都会发生多个系统调用(syscalls)。每个系统调用都会接收用户输入的命令,并将其传递给 Linux 内核。这告诉 Linux 内核需要执行某种特权操作。打开或关闭文件、写入文件,或更改文件权限和所有权,都是需要进行系统调用的操作之一。大约有 330 个系统调用内建于 Linux 内核中。我无法准确说出确切数量,因为新的系统调用会不时添加进来。除此之外,系统调用在不同的 CPU 架构之间也有所不同。因此,ARM CPU 和 x86_64 CPU 的系统调用集并不完全相同。查看您机器上可用的系统调用列表的最佳方法是查看 man 页面,如下所示:

man syscalls

请注意,每个单独的系统调用都有其自己的 man 页面。

为了理解这如何运作,这里是 strace 命令,它展示了简单的 ls 命令所调用的系统调用:

[donnie@localhost ~]$ strace -c -f -S name ls 2>&1 1>/dev/null | tail -n +3 | head -n -2 | awk '{print $(NF)}'
access
arch_prctl
brk
close
execve
. . .
. . .
set_robust_list
set_tid_address
statfs
write
[donnie@localhost ~]$

总共,执行 ls 只需调用 22 个系统调用。(由于格式限制,我无法在这里显示所有的系统调用。)

安全计算SECCOMP),最初为 Google Chrome 浏览器创建,允许你启用进程所需的某些系统调用子集,或者禁用你希望防止进程使用的某些系统调用。除非你是软件开发人员或 Docker 容器开发人员,否则你可能不会直接与它打交道。然而,这仍然是普通人日常使用的技术的另一个构建模块。

接下来,让我们通过看看这些技术如何在实际生活中应用,来更好地理解它们。

使用 Docker 容器进行进程隔离

容器技术已经存在了一段时间,但正是 Docker 让容器变得流行。与虚拟机不同,容器不包含完整的操作系统。容器只包含足够的操作系统部分,以便在其独立的沙箱中运行应用程序。容器没有自己的操作系统内核,因此它们使用主机 Linux 机器的内核。容器之所以如此受欢迎,是因为你可以在一台物理服务器上放置比虚拟机更多的容器。因此,它们非常适合降低数据中心的运行成本。

Docker 容器使用了我们在本章中介绍的技术。内核功能、cgroups、命名空间和 SECCOMP 都帮助 Docker 容器在默认情况下保持彼此以及与主机操作系统的隔离,除非我们选择更改这一点。默认情况下,Docker 容器运行时具有减少的功能和系统调用集,Docker 开发人员可以为他们创建的容器进一步减少这些。

我无法详细讲解 Docker 中所有这些技术如何运作,因为这需要解释 Docker 容器的开发过程。不过没关系,在这一部分中,你将理解如果有人想在你的数据中心部署 Docker 容器,你需要注意哪些问题。

尽管这些听起来都很不错,但 Docker 的安全性远非完美。正如我在第十章中演示的,使用 SELinux 和 AppArmor 实现强制访问控制docker 组的任何非特权成员都可以将主机的根文件系统挂载到他或她自己创建的容器中。通常情况下,docker 组的非特权成员在容器内拥有 root 权限,这些权限会扩展到挂载的主机根文件系统中。在演示中,我向你展示了,只有一个有效的强制访问控制系统,特别是 SELinux,才能阻止 Katelyn 控制整个主机。

为了解决这个相当严重的设计缺陷,Red Hat 的开发人员创建了自己的 Docker 替代品。他们称之为 podman,它可以在 RHEL 8/9 和 AlmaLinux 8/9 的软件库中找到。podman 的安全性大大提高了,即使没有 SELinux,我在演示中展示的那种攻击也无法对其生效。就个人而言,我甚至不会考虑除 Podman 之外的任何容器运行方式。

Podman 曾经仅在 RHEL 和 Fedora 类型的 Linux 发行版上可用。现在,它已在大多数其他 Linux 发行版上也可用。

现在,既然我已经给你概述了 Docker 中如何使用进程隔离技术,让我们来看看这些技术在普通人更有可能使用的技术中是如何运作的。我们将从 Firejail 开始。

使用 Firejail 进行沙箱化

Firejail 使用命名空间、SECCOMP 和内核能力将不可信的应用程序运行在各自独立的沙箱中。这可以帮助防止应用程序之间的数据泄露,也能帮助防止恶意程序破坏你的系统。它在 Debian 及其衍生版的正常软件库中,包括适用于 Raspberry Pi 设备的 Raspbian,以及可能包括 Ubuntu 系列的所有成员。在 Red Hat 方面,它在 EPEL 库中,因此你需要先安装它。Firejail 主要用于单用户桌面系统,因此我们需要使用 Linux 的桌面版本。所以,先去设置一个带有 Gnome 桌面的 AlmaLinux 9 虚拟机。

在本书的上一版中,我用 Lubuntu 演示了这一点,当时 Lubuntu 是带有 LXDE 桌面的 Ubuntu。(Lubuntu 现在已经切换到 LXQT 桌面。)不幸的是,Ubuntu 发行版现在默认将某些软件包安装为 Snap 包,而这些 Snap 包与 Firejail 不兼容。因此,在本版中,我切换到了 AlmaLinux 9,以使演示能够正常进行。

在我们深入之前,先来考虑一下 Firejail 的一些使用场景:

  • 你希望确保你的网络浏览器在访问银行的网页门户时不会泄露敏感信息。

  • 您需要运行从互联网下载的不受信任的应用程序。

要在您的 AlmaLinux 9 机器上安装 Firejail,请使用以下命令:

sudo dnf install epel-release
sudo dnf install firejail

这将安装 Firejail,以及各种不同应用程序的配置文件。当您使用 Firejail 调用一个应用程序时,如果存在相应的配置文件,它将自动加载正确的配置文件。如果调用一个没有配置文件的应用程序,Firejail 将加载一个通用的配置文件。要查看配置文件,请 cd 进入 /etc/firejail/ 并查看:

donnie@donnie-VirtualBox:/etc/firejail$ ls -l
total 4996
-rw-r--r-- 1 root root   894 Dec 21  2017 0ad.profile
-rw-r--r-- 1 root root   691 Dec 21  2017 2048-qt.profile
-rw-r--r-- 1 root root   399 Dec 21  2017 7z.profile
-rw-r--r-- 1 root root  1414 Dec 21  2017 abrowser.profile
-rw-r--r-- 1 root root  1079 Dec 21  2017 akregator.profile
-rw-r--r-- 1 root root   615 Dec 21  2017 amarok.profile
-rw-r--r-- 1 root root   722 Dec 21  2017 amule.profile
-rw-r--r-- 1 root root   837 Dec 21  2017 android-studio.profile
. . .
. . .

要轻松计算配置文件的数量,只需执行以下操作:

donnie@donnie-VirtualBox:/etc/firejail$ ls -l | wc -l
1231
donnie@donnie-VirtualBox:/etc/firejail$

从输出顶部的 total 4996 行减去,我们得到了 1,230 个配置文件的总数。

使用 Firejail 的最简单方法是在要运行的应用程序名称前加上 firejail。我们先从 Firefox 开始:

firejail firefox

Firejail 的主要问题在于其一致性不佳。几年前,一位客户让我写了一份关于 Firejail 的报告,我在我的 Fedora 工作站和搭载 Raspbian 的 Raspberry Pi 上大部分时间都能让它工作。但即使对于它能工作的程序,我也失去了一些重要的功能。例如,在我的 Fedora 机器上使用 Firejail 运行网络浏览器时,我无法在包括 YouTube 在内的多个不同网站上观看视频。Dropbox 和 Keepass 在 Firejail 下根本无法工作,尽管它们都有特定的配置文件。

在 Lubuntu 虚拟机上,使用 Firejail 运行 Firefox 只会给我一个空白的浏览器页面,无论我尝试去哪里冲浪。所以,我安装了 chromium-browser 并尝试了它。它表现得更好,我甚至可以用它观看 YouTube 视频。然后,我安装了 LibreOffice,它似乎在 Firejail 下运行良好。

Firejail 提供的众多选项中包括确保程序运行时没有启用任何内核能力或仅使用您指定的能力。手册建议的一点是对于不需要 root 权限的程序,放弃所有能力。因此,对于 Chromium,我们会这样做:

firejail --caps.drop=all chromium-browser

那么,如果您只想像通常一样从“开始”菜单启动应用程序,但仍然希望有 Firejail 保护怎么办?对此,您可以这样做:

sudo firecfg

此命令在 /usr/local/bin/ 目录中为每个具有 Firejail 配置文件的程序创建符号链接。它们看起来像这样:

donnie@donnie-VirtualBox:/usr/local/bin$ ls -l
total 0
lrwxrwxrwx 1 root root 17 Nov 14 18:14 audacious -> /usr/bin/firejail
lrwxrwxrwx 1 root root 17 Nov 14 18:14 chromium-browser -> /usr/bin/firejail
lrwxrwxrwx 1 root root 17 Nov 14 18:14 evince -> /usr/bin/firejail
lrwxrwxrwx 1 root root 17 Nov 14 18:14 file-roller -> /usr/bin/firejail
lrwxrwxrwx 1 root root 17 Nov 14 18:14 firefox -> /usr/bin/firejail
lrwxrwxrwx 1 root root 17 Nov 14 18:14 galculator -> /usr/bin/firejail
. . .
. . .

如果发现某个程序在 Firejail 下不工作,只需进入 /usr/local/bin/ 并删除其链接。

现在,你需要注意 Firejail 文档中的一个非常有趣的地方。在 Firejail 的手册页和 Firejail 网站的主页上都提到,你可以使用 Firejail 对桌面应用程序、服务器应用程序以及用户登录会话进行沙箱化。然而,如果你点击 Firejail 网站上的文档标签,你会看到它写着 Firejail 仅适用于单用户桌面系统。这是因为,为了执行其任务,Firejail 可执行文件必须设置 SUID 权限位。Firejail 开发人员认为允许多个用户访问带有此 SUID 程序的机器是一种安全风险。

好了,话不多说。让我们开始动手练习吧。

实验练习——使用 Firejail

对于这个实验,你将使用带有 Gnome 桌面的 AlmaLinux 9 虚拟机。让我们开始吧:

  1. 创建一个带有 Gnome 桌面选项的 AlmaLinux 虚拟机。

  2. 使用以下命令安装 EPEL 仓库并更新虚拟机:

sudo dnf install epel-release
sudo dnf upgrade

然后,重启机器。

  1. 安装 Firejail、LibreOffice 和 Chromium:
sudo dnf install firejail libreoffice-x11 chromium
  1. 在一个终端窗口中,启动 Chromium,但不使用任何内核功能:
firejail --caps.drop=all chromium-browser
  1. 浏览不同的网站,看看一切是否都能按预期正常运行。

  2. 在另一个终端窗口中,启动 LibreOffice,同样没有任何功能:

firejail --caps.drop=all libreoffice
  1. 创建各种类型的 LibreOffice 文档,并尝试使用不同的 LibreOffice 功能,看看哪些仍然能够正常工作。

  2. 关闭 Chromium 和 LibreOffice。

  3. 配置 Firejail,使其能够自动对每个启动的应用程序进行沙箱化,即使你是通过正常的开始菜单来启动:

sudo firecfg
  1. 查看已创建的符号链接:
ls -l /usr/local/bin
  1. 尝试从正常菜单打开 Firefox,并验证它是否能够正常工作。然后,关闭 Firefox。

  2. 为了能在没有 Firejail 的情况下运行 Firefox,只需从 /user/local/bin/ 目录中删除其符号链接,如下所示:

sudo rm /usr/local/bin/firefox
  1. 再次尝试运行 Firefox。你应该会看到它正常启动。

你已经完成了这个实验——祝贺你!

Firejail 有很多更多的选项,我无法在这里一一展示。欲了解更多信息,请查看 Firejail 的手册页和 Firejail 网站上的文档。

到目前为止,你已经看到了使用 Firejail 的优点和缺点。接下来,我们将看看几个适用于 Linux 的通用打包系统。

使用 Snappy 进行沙箱化

在 Windows 和 Mac 世界中,操作系统与它们能够运行的应用程序是独立销售的。因此,你购买一台运行 Windows 或 macOS 的计算机,然后单独购买应用程序。更新时,你必须先更新操作系统,再分别更新每个应用程序。

在 Linux 世界中,大多数你需要的应用程序都在你的 Linux 发行版的仓库中。要安装一个应用程序,你只需使用发行版的包管理工具——aptyumdnf或其他工具——进行安装。然而,这既是福也是祸。它的确使得跟踪你的应用程序和保持最新的错误修复及安全更新变得更容易。但除非你使用的是像 Arch 这样的滚动更新发行版,否则在 Linux 发行版生命周期结束之前,应用程序包将会过时。这是因为发行版维护者使用的是在发行版发布时当前的应用程序版本,并且不会在下一个版本发布之前升级到新版本。这也使得应用程序开发者感到困难,因为每个 Linux 发行版家族使用的是不同的打包格式。那么,难道我们不希望有一个通用的打包格式,可以在所有 Linux 发行版上使用,并且能轻松保持更新吗?

通用打包格式的出现始于几年前的AppImage包。然而,这些包并没有真正流行开来,而且它们没有提供任何沙箱功能。所以,关于它们我就只说这么多。

接下来是 Ubuntu 的 Snappy 系统,它允许开发者创建可以在任何安装了 Snappy 系统的系统上运行的 snap 包。每个 snap 应用都运行在自己的隔离沙箱中,这有助于保护系统免受恶意程序的侵害。每个 snap 包都是一个自包含的单元,这意味着你不需要担心安装依赖项。你甚至可以为包含多个服务的服务器创建 snap 包。snapd守护进程始终在后台运行,自动更新自己以及任何已安装的 snap 应用程序。

尽管这一切听起来很不错,但 Snappy 有一些让人争议的地方。首先,Ubuntu 的开发团队拒绝发布 Snappy 应用服务器的源代码。因此,无法查看源代码,也无法搭建自己的本地 Snappy 服务器。如果你开发 snap 包并希望部署它们,即使仅仅是在你自己的本地网络上,你也别无选择,只能使用由 Canonical(Ubuntu 的母公司)运营的中央 snap 包门户。这显然违背了软件自由的原则,而软件自由正是整个 GNU/Linux 生态系统应当代表的。然而,Canonical 的开发团队这么做是为了验证所提供的 snap 包的安全性。

其次,尽管 snap 包已经被沙箱化以保护系统,但仍然可能发生其他奇怪的事情。Snappy 系统上线不久后,一位包开发者被发现偷偷将 Monero 挖矿软件捆绑进他的包中。虽然他只是想通过此方式变现自己的努力,并没有恶意,但在不告知潜在用户的情况下将这种东西偷偷塞进包中显然不好。之后,Canonical 的开发人员加大了对上传到门户网站的包进行扫描的力度,以防止此类事件的再次发生。

然后,还有用户控制的问题。用户可以将 snap 更新延迟最多 60 天,但无法完全关闭更新。在某个时刻,snapd 守护进程将会更新你安装的软件包,无论你是否希望更新。

最后,每个 snap 包都作为一个独立单元,并且保存每个 snap 的前三个版本,这增加了磁盘空间的使用。每个包都包含它的应用程序所使用的所有链接库,这意味着你可能会有多个包使用相同的库。这在今天的大容量硬盘上不一定是大问题,但如果磁盘空间不足,可能就会成为一个问题。

如果你正在运行任何最近版本的 Ubuntu,Snappy 服务已经在后台运行。某个时候(我记得不太清楚),Ubuntu 开发人员开始在 Ubuntu Server 的默认安装中包含 Snappy。现在,它也默认安装在 Ubuntu 桌面版中。事实上,Ubuntu 现在会自动使用 Snappy 安装某些软件包,比如 Firefox 和 Chromium,即使你使用普通的sudo apt install命令来安装它们。

Snappy 也在许多非 Ubuntu 的发行版的仓库中可用。在 Fedora 的正常仓库中有,在 Red Hat 及其所有衍生版的 EPEL 仓库中也有。(不过要注意,我已经放弃在我的 Fedora 系统上使用 snaps,因为它导致了 SELinux 出现太多奇怪的问题。)

那么,Snappy 如何对忙碌的管理员有所帮助呢?假设你的老板刚刚告诉你要搭建一个 Nextcloud 服务器,方便员工有一个集中存储文档的地方。但是,你时间紧迫,不想花时间逐个设置 LAMP 系统的所有组件。没问题——只需安装一个 snap。首先,让我们看看在 Ubuntu 服务器上可以使用的 snap:

donnie@ubuntu2204:~$ snap search nextcloud
Name Version Publisher Notes Summary
nextcloud 16.0.5snap3 nextcloud√ - Nextcloud Server - A safe home for all your data
spreedme 0.29.5snap1 nextcloud√ - Spreed.ME audio/video calls and conferences feature for the Nextcloud Snap
onlyoffice-desktopeditors 5.4.1 onlyoffice√ - A comprehensive office suite for editing documents, spreadsheets and presentations
qownnotes 19.11.13 pbek - Plain-text file markdown note taking with Nextcloud / ownCloud integration
nextcloud-port8080 1.01 arcticslyfox - Nextcloud Server
. . .
. . .

有不少选择。我们可以使用info选项来稍微缩小范围:

donnie@ubuntu2204:~$ snap info nextcloud
name: nextcloud
summary: Nextcloud Server - A safe home for all your data
publisher: Nextcloud√
contact: https://github.com/nextcloud/nextcloud-snap
license: AGPL-3.0+
description: |
 Where are your photos and documents? With Nextcloud you pick a server of your choice, at home, in
 a data center or at a provider. And that is where your files will be. Nextcloud runs on that
 server, protecting your data and giving you access from your desktop or mobile devices. Through
 Nextcloud you also access, sync and share your existing data on that FTP drive at school, a
 Dropbox or a NAS you have at home.
. . .
. . .

看起来这个正是我需要的。那么,接下来让我们安装它:

donnie@ubuntu2204:~$ snap install nextcloud

我们这里不担心使用sudo,因为 snap 在执行这个操作时会提示你输入密码。请注意,对于其他 snap 操作,这种方式不适用,因此你需要使用sudo

要启动它,只需执行以下操作:

donnie@ubuntu2204:~$ sudo snap start nextcloud
Started.
donnie@ubuntu2204:~$

最后,从桌面机器上,导航到Nextcloud服务器的 IP 地址并创建一个管理员账户。填写完所有信息后,点击完成设置

图 11.2:Nextcloud

图 11.2:Nextcloud

很简单,对吧?想象一下,如果用传统方式来做,这需要花多长时间。唯一的小问题是它在一个未加密的 HTTP 连接上运行,因此你肯定不想将其暴露到互联网上。(有方法可以重新配置它以使用加密连接,但这超出了本话题的范围。)

Snapcraft 商店是 Canonical 官方的 snap 包仓库。任何人都可以创建一个账户并上传自己的 snap 包。这里有许多适用于桌面/工作站、服务器和物联网设备的应用程序。支持多种不同的机器架构,包括 x86_64、ARM 和 PowerPC。(所以,是的,这对于你的 Raspberry Pi 设备也很有用。)这可以从下面的截图中看到:

图 11.3:Snapcraft 商店

图 11.3:Snapcraft 商店

这几乎就是全部内容。尽管存在争议,但这仍然是一个相当酷的概念。

如果你需要部署物联网设备,可能需要考虑 Ubuntu Core。它是一个精简版的 Ubuntu,完全由 snap 包组成。由于篇幅所限,我不能在这里详细介绍,但你可以在ubuntu.com/core阅读详细信息。

现在你已经了解了如何使用 Ubuntu 的 Snappy 系统,我们将来看一下 Fedora 的 Flatpak 系统。

使用 Flatpak 的沙箱机制

Flatpak 系统是由 Fedora Linux 团队创建的,目标与 Ubuntu 的 Snappy 系统相同,但它们在实现上有显著的差异。你可以在任何给定的 Linux 机器上运行其中一个或两个系统。使用任一系统,你都可以创建一个通用包,能够在任何安装了 Snappy 或 Flatpak 的机器上运行。而且,两个系统都会在各自的安全沙箱中运行每个应用程序。

然而,正如我之前提到的,存在一些差异:

  • 与每个应用程序包完全自包含不同,Flatpak 安装了共享的运行时库,应用程序可以访问这些库。这有助于减少磁盘空间的使用。

  • Fedora 团队运营着一个名为 Flathub 的中央仓库。不过,他们也将服务器代码公开,供任何人设置自己的 Flatpak 仓库。

  • Flatpak 的设置需要稍微多一点的工作,因为安装后,你必须配置它以使用所需的仓库。

  • Snapcraft 商店提供适用于服务器、桌面和物联网(IoT)使用的包,而 Flathub 主要提供桌面应用程序。

根据你运行的发行版,你可能已经安装了 Flatpak 系统,也可能没有。在 Debian/Ubuntu 系统上,可以使用以下命令安装它:

sudo apt update
sudo apt install flatpak

在 RHEL、CentOS、AlmaLinux 和 Fedora 系统上,很有可能它已经安装好了。如果没有安装,只需使用常规的 yumdnf 命令安装。安装完 Flatpak 后,前往 Flatpak 快速设置 页面查看如何配置它。点击你使用的发行版的图标,并按照说明操作。

你可以在这里找到 快速设置 页面:flatpak.org/setup/

安装完仓库后,重启机器。机器重启后,你就可以开始安装一些应用程序了。要选择一个,前往 Flathub 网站并浏览,直到找到你想要的应用。

你可以在这里找到 Flathub:flathub.org/home

假设你已经浏览了生产力类应用,并找到了 Bookworm 电子书阅读器。点击链接进入 Bookworm 应用页面。你会看到页面顶部有一个 安装 按钮。如果点击该按钮,你将下载 Bookworm 的安装文件。要安装它,你仍然需要在命令行中输入命令。最好的方法是滚动到页面底部,在那里你将看到同时下载和安装应用的命令:

图 11.4:Flathub 仓库

图 11.4:Flathub 仓库

还有运行应用的命令,但你可能不需要它。根据你使用的发行版,可能会为你在 开始 菜单中创建一个图标,也可能不会。

Flatpak 有时需要管理员权限,有时则不需要。每当需要时,它会提示你输入密码,所以无需在 flatpak 命令前加上 sudo

与 Snappy 不同,Flatpak 不会自动更新其应用程序。你需要定期执行以下命令来手动更新:

flatpak update

在本节中,你了解了如何使用 Snappy 和 Flatpak 通用打包系统的基础知识。使用这两个系统时,开发人员只需打包一次应用,而不是为多种包类型分别打包。最终用户可以使用这些应用,从而保证它们始终保持最新,而不必依赖于发行版仓库中的旧版本。而且,在本书的总体背景下,理解通过在独立的沙盒中运行应用,这两种系统都提供了额外的应用安全措施。

总结

所以,另一个重要的章节已经过去,我们看到了很多很酷的内容。我们从查看 /proc 文件系统开始,了解如何配置其中的一些设置以实现最佳的安全性。接着,我们查看了如何使用 cgroups、命名空间、内核能力和 SECCOMP 来隔离进程。我们以一些使用这些酷技术的实用程序和包管理系统示例结束了这一章。

在下一章中,我们将讨论不同的扫描、审计和加固系统的方法。到时候见。

问题

  1. 以下哪个是正确的?

    1. /proc就像 Linux 文件系统中的任何其他目录一样。

    2. /proc是 Linux 中唯一的伪文件系统。

    3. /proc是 Linux 中的多个伪文件系统之一。

    4. 你可以使用systemctl命令为/proc参数设置值。

  2. 以下哪个命令可以用来设置/proc参数的值?

    1. sudo systemctl -w

    2. sudo sysctl -w

    3. sudo procctl -w

    4. sudo sysctl -o

    5. sudo systemctl -o

  3. 你需要一个程序可执行文件以特定的 root 权限运行,而无需授予运行它的用户任何 root 权限。你会怎么做?

    1. 添加一个命名空间。

    2. 创建一个 SECCOMP 配置文件。

    3. 添加 SUID 权限。

    4. 添加一个内核能力。

  4. 你在哪里可以找到关于用户进程的信息?

    1. /proc文件系统的编号子目录中。

    2. /proc文件系统的按字母排序的子目录中。

    3. /dev目录中。

    4. 在每个用户的主目录中。

  5. 什么是系统调用(syscall)?

    1. 它告诉 Linux 内核代表用户执行特权操作。

    2. 它将新的系统信息传递给内核。

    3. 它跟踪系统内核正在做的所有事情。

    4. 它将系统资源的调用彼此隔离开。

  6. 在非 RHEL 9 类型的 Linux 发行版中,允许用户只查看自己进程信息的最佳方法是什么?

    1. 在 GRUB 配置中添加hidepid=2选项到内核启动参数。

    2. 在 GRUB 配置中添加nopid=1选项到内核启动参数。

    3. /etc/fstab文件中添加nopid=1选项。

    4. /etc/fstab文件中添加hidepid=1选项。

  7. 以下哪个命令可以用来查看哪些内核参数需要为最佳安全性进行更改?

    1. sudo audit system

    2. sudo lynis audit system

    3. sudo system audit

    4. sudo lynis system audit

  8. 以下哪个命令允许非特权用户在不使用 root 权限的情况下启动一个 Python Web 服务器,并绑定到Port 80

    1. sudo setcap 'CAP_NET_SERVICE+ep' /usr/bin/python2.7

    2. sudo setcap 'CAP_NET_BIND_SERVICE+ep' /usr/bin/python2.7

    3. sudo getcap 'CAP_NET_BIND_SERVICE+ep' /usr/bin/python2.7

    4. sudo setcap 'CAP_NET_SERVICE+ep' /usr/bin/python2.7

  9. Snappy 和 Flatpak 系统之间的主要区别是什么?

    1. 没有。

    2. Flatpak 包完全自包含,但 Snappy 包需要安装单独的运行时包。

    3. Snappy 包完全自包含,但 Flatpak 包需要安装单独的运行时包。

    4. Flatpak 包运行在自己的沙盒中,但 Snappy 包没有。

    5. Snappy 包运行在自己的沙盒中,但 Flatpak 包没有。

  10. 你需要限制你的 Docker 容器可以发出的系统调用次数。你会怎么做?

    1. 在自己的 cgroup 中创建容器,并为该 cgroup 配置系统调用限制。

    2. 在其独立的命名空间中创建容器,并为该命名空间配置系统调用限制。

    3. 在 Firejail 下运行容器。

    4. 使用 SECCOMP 配置文件创建容器。

进一步阅读

答案

  1. c

  2. b

  3. d

  4. a

  5. a

  6. d

  7. b

  8. b

  9. c

  10. d

第十一章:12 扫描、审计和加固

加入我们的 Discord 书籍社区

packt.link/SecNet

一个常见的误解是,Linux 用户从不需要担心恶意软件。没错,Linux 相比 Windows 对病毒的抵抗力更强。但病毒只是恶意软件的一种类型,其他类型的恶意软件也可以被植入 Linux 系统。如果你运行一个与 Windows 用户共享文件的服务器,你就需要确保不会与他们共享任何被病毒感染的文件。

虽然 Linux 系统日志文件很好,但它们并不总能清晰显示谁做了什么或谁访问了什么。可能是入侵者或内部人员正在试图访问他们没有授权访问的数据。我们真正需要的是一个好的审计系统,能够在有人做出不该做的事情时发出警报。

还有合规性问题。你的组织可能需要应对一个或多个监管机构,要求你加固服务器以防止攻击。如果不符合规定,可能会面临罚款或停业。

幸运的是,我们有办法处理所有这些问题,而且它们并不复杂。

本章我们将讨论以下主题:

  • 安装和更新 ClamAV 和 maldet

  • 使用 ClamAV 和 maldet 进行扫描

  • SELinux 考虑事项

  • 使用 Rootkit Hunter 扫描 rootkit

  • 使用 strings 和 VirusTotal 进行快速恶意软件分析

  • 控制 auditd 守护进程

  • 创建审计规则

  • 使用 ausearchaureport 工具在审计日志中搜索问题

  • 使用 inotifywait 进行快速简便的审计

  • oscap,用于管理和应用 OpenSCAP 策略的命令行工具

  • OpenSCAP Workbench,管理和应用 OpenSCAP 策略的 GUI 工具

  • OpenSCAP 策略文件以及每个文件所设计满足的合规性标准

  • 在操作系统安装过程中应用策略

如果你准备好了,我们就从一个基于 Linux 的病毒扫描解决方案开始。

安装和更新 ClamAV 和 maldet

Although we don't have to worry much about viruses infecting our Linux machines, we do need to worry about sharing infected files with Windows users. ClamAV is a Free Open Source Software (FOSS) antivirus solution that is available for Linux, Windows, and macOS. The included freshclam utility allows you to update virus signatures.
Linux Malware Detect, which you'll often see abbreviated as either LMD or maldet, is another FOSS antivirus program that can work alongside ClamAV. (To save typing, I'll just refer to it as either LMD or maldet from now on.) As far as I know, it's not available in the repositories of any Linux distro, but it's still simple enough to install and configure. One of its features is that it automatically generates malware detection signatures when it sees malware on the network's edge intrusion detection systems. End users can also submit their own malware samples. When you install it, you'll get a systemd service that's already enabled and a cron job that will periodically update both the malware signatures and the program itself. It works with the Linux kernel's inotify capability to automatically monitor directories for files that have changed.

你可以在 www.rfxn.com/projects/linux-malware-detect/ 获取关于 LMD 的详细信息。

我们一起安装 ClamAV 和 LMD 的原因是,正如 LMD 开发者所承认的,ClamAV 扫描引擎在扫描大文件集时表现更好。此外,两个工具一起使用时,ClamAV 可以使用 LMD 的恶意软件签名以及其自身的恶意软件签名。

只是为了澄清一下...

病毒对于运行 Windows 操作系统的计算机来说是一个真正的问题。但根据目前的了解,似乎没有病毒能够危害基于 Linux 的操作系统。因此,唯一需要在 Linux 机器上运行杀毒软件的真实原因,是为了防止感染你网络中的任何 Windows 机器。这意味着你不需要担心在基于 Linux 的 DNS 服务器、DHCP 服务器等设备上安装杀毒产品。但是,如果你有基于 Linux 的邮件服务器、Samba 服务器、下载服务器或任何与 Windows 计算机共享文件的 Linux 机器,那么安装一个杀毒解决方案是个不错的选择。

好的,理论部分就到这里。接下来我们开始动手操作,怎么样?

动手实验 – 安装 ClamAV 和 maldet

我们将首先安装 ClamAV。(它在 Ubuntu 的常规仓库中,但在 CentOS 或 AlmaLinux 中没有。对于 CentOS 和 AlmaLinux,你需要安装 EPEL 仓库,正如我在第一章《在虚拟环境中运行 Linux》中所展示的。)我们还将安装wget,用于下载 LMD。在这个实验中,你可以使用 Ubuntu、CentOS 7 或任何版本的 AlmaLinux。开始吧:

  • 以下命令将在 Ubuntu 上安装 ClamAV,inotify-toolswget
donnie@ubuntu3:~$ sudo apt install clamav wget inotify-tools

以下命令将在 CentOS 7 上安装 ClamAV,inotify-toolswget

[donnie@localhost ~]$ sudo yum install clamav clamav-update wget inotify-tools

对于 AlmaLinux 8 或 AlmaLinux 9,请执行以下操作:

[donnie@localhost ~]$ sudo dnf install clamav clamav-update wget inotify-tools
[donnie@localhost ~]$ sudo systemctl enable --now clamav-freshclam

请注意,如果在创建 CentOS 或 AlmaLinux 虚拟机(VM)时选择了最小化安装选项,你可能还需要安装perltar包。

对于 Ubuntu,clamav包包含了你需要的一切。对于 CentOS 或 AlmaLinux,你还需要安装clamav-update以便获取病毒更新。

剩下的步骤对于任意虚拟机(VM)来说都是相同的。

  1. 接下来,你需要下载并安装 LMD。

这里,你需要做一件我很少告诉别人做的事。那就是,你需要登录到 root 用户的 shell。原因是,尽管 LMD 安装程序在使用sudo时也能正常工作,但程序文件会被安装者的用户所拥有,而不是 root 用户。如果从 root 用户的 shell 进行安装,可以避免安装后需要追踪并更改文件所有权的麻烦。所以,按如下方式下载文件:

sudo su -
wget http://www.rfxn.com/downloads/maldetect-current.tar.gz

现在,你的文件应该在 root 用户的主目录下。

  1. 解压档案并进入解压后的目录:
tar xzvf maldetect-current.tar.gz
cd maldetect-1.6.4/
  • 运行安装程序。安装程序完成后,将README文件复制到你自己的主目录,以便随时参考。(这个README文件是 LMD 的文档。)然后,从 root 用户的 shell 退出,返回到你自己的 shell:
root@ubuntu3:~/maldetect-1.6.4# ./install.sh
Created symlink from /etc/systemd/system/multi-user.target.wants/maldet.service to /usr/lib/systemd/system/maldet.service.
update-rc.d: error: initscript does not exist: /etc/init.d/maldet
. . .
. . .
maldet(22138): {sigup} signature set update completed
maldet(22138): {sigup} 15218 signatures (12485 MD5 | 1954 HEX | 779 YARA | 0 USER)
root@ubuntu3:~/maldetect-1.6.4# cp README /home/donnie
root@ubuntu3:~/maldetect-1.6.4# exit
logout
donnie@ubuntu3:~$

如你所见,安装程序会自动创建符号链接,启用maldet服务,并自动下载和安装最新的恶意软件签名。

  1. 对于 CentOS 或 AlmaLinux,安装程序复制到 /lib/systemd/system/ 目录的 maldet.service 文件具有错误的 SELinux 上下文,这将阻止 maldet 启动。通过以下方式修正 SELinux 上下文:
sudo restorecon /lib/systemd/system/maldet.service

恭喜你,已经完成了实验的全部内容!

实验环节 – 配置 maldet

在之前的版本中,maldet 默认配置为自动监控和扫描用户的家目录。在当前版本中,默认只监控 /dev/shm//var/tmp//tmp/ 目录。我们将重新配置它,以便添加其他目录。开始吧:

  1. 打开 /usr/local/maldetect/conf.maldet 文件进行编辑。找到这两行:
default_monitor_mode="users"
# default_monitor_mode="/usr/local/maldetect/monitor_paths"

将它们更改为如下所示:

# default_monitor_mode="users"
default_monitor_mode="/usr/local/maldetect/monitor_paths"
  1. 在文件的顶部,启用电子邮件提醒并将你的用户名设置为电子邮件地址。这两行应该类似于以下内容:
email_alert="1"
email_addr="donnie"
  1. LMD 默认并未配置将可疑文件移动到 quarantine 文件夹,我们需要让它执行这一操作。在 conf.maldet 文件的进一步位置,查找以下行:
quarantine_hits="0"
  1. 将其更改为以下内容:
quarantine_hits="1"

你还会看到一些可以配置的其他隔离操作,但现在这就是我们需要做的所有配置。

  • 保存 conf.maldet 文件,因为这是我们需要进行的所有更改。

  • 打开 /usr/local/maldetect/monitor_paths 文件进行编辑。添加你想要监控的目录,像这样:

/var/tmp
/tmp
/home
/root

由于病毒影响的是 Windows 而不是 Linux,因此只需监控与 Windows 机器共享的文件所在的目录。

  • 保存文件后,启动 maldet 守护进程:
sudo systemctl start maldet

你可以随时向 monitor_paths 文件添加更多目录,但记住每次添加后都需要重新启动 maldet 守护进程,以便读取新的添加内容。

恭喜你,已经完成了实验的全部内容!

现在,让我们来谈谈如何保持 ClamAV 和 maldet 的更新。

更新 ClamAV 和 maldet

对于忙碌的管理员来说,好消息是你无需做任何事情来保持这两个软件的更新。为了验证它们是否正在更新,我们可以查看系统日志文件:

Dec 8 20:02:09 localhost freshclam[22326]: ClamAV update process started at Fri Dec 8 20:02:09 2017
Dec 8 20:02:29 localhost freshclam[22326]: Can't query current.cvd.clamav.net
Dec 8 20:02:29 localhost freshclam[22326]: Invalid DNS reply. Falling back to HTTP mode.
Dec 8 20:02:29 localhost freshclam[22326]: Reading CVD header (main.cvd):
Dec 8 20:02:35 localhost freshclam[22326]: OK
Dec 8 20:02:47 localhost freshclam[22326]: Downloading main-58.cdiff [100%]
Dec 8 20:03:19 localhost freshclam[22326]: main.cld updated (version: 58, sigs: 4566249, f-level: 60, builder: sigmgr)
. . .
. . .
Dec 8 20:04:45 localhost freshclam[22326]: Downloading daily.cvd [100%]
. . .
. . .

无论是在 Ubuntu 日志、CentOS 日志,还是 AlmaLinux 日志中,你都会看到这些相同的条目。然而,更新自动执行的方式有所不同。

在你的 Ubuntu 或 AlmaLinux 虚拟机的 /lib/systemd/system/ 目录中,你会看到 clamav-freshclam.service 文件:

[donnie@localhost ~]$ cd /lib/systemd/system
[donnie@localhost system]$ ls -l clamav-freshclam.service 
-rw-r--r--. 1 root root 389 Nov  7 06:51 clamav-freshclam.service
[donnie@localhost system]$

该服务在 Ubuntu 上会自动启用并启动,但在 AlmaLinux 上需要你手动启用并启动,方法如下:

[donnie@localhost ~]$ sudo systemctl enable --now clamav-freshclam
Created symlink /etc/systemd/system/multi-user.target.wants/clamav-freshclam.service → /usr/lib/systemd/system/clamav-freshclam.service.
[donnie@localhost ~]$

如果没有 freshclam.conf 配置文件,AlmaLinux 会每两小时运行一次更新服务。而 Ubuntu 则使用 /etc/clamav/freshclam.conf 文件将更新间隔设置为每小时一次,正如你在文件底部所看到的:

# Check for new database 24 times a day
Checks 24
DatabaseMirror db.local.clamav.net
DatabaseMirror database.clamav.net

如果你在 AlmaLinux 8/9 机器上将加密策略设置为 FUTURE 模式,ClamAV 数据库更新将无法工作。因为 ClamAV 网站使用的安全证书与 FUTURE 模式不兼容。所以,如果你想在任何类型的 RHEL 8 或 9 机器上运行 ClamAV,你需要将加密策略设置为 DEFAULT 模式。

在你的 CentOS 7 机器上,你会看到一个 clamav-updatecron 作业文件,位于 /etc/cron.d/ 目录下,内容如下:

## Adjust this line...
MAILTO=root
## It is ok to execute it as root; freshclam drops privileges and becomes
## user 'clamupdate' as soon as possible
0  */3 * * * root /usr/share/clamav/freshclam-sleep

左侧第二列的 */3 表示 ClamAV 每三小时检查一次更新。如果你愿意,可以更改此值,但你也需要在 /etc/sysconfig/freshclam 文件中更改该设置。

假设你希望 CentOS 7 每小时检查一次 ClamAV 更新。在 cron 作业文件中,将 */3 改为 *。 (你不需要设置 */1,因为单独的星号已经表示作业会每小时运行一次。)然后,在 /etc/sysconfig/freshclam 文件中,查找以下这一行:

# FRESHCLAM_MOD=

取消该行的注释,并添加你希望每次更新之间的时间间隔。如果要将其设置为 1 小时,以便与 cron 作业匹配,应该如下所示:

FRESHCLAM_MOD=60

一个禁用的 clamav-freshclam.service 文件也会在 CentOS 7 上安装。如果你更倾向于使用服务而不是 cron 作业,只需删除 /etc/cron.d/clamav-update 文件,然后启用 clamav-freshclam 服务。

要证明 maldet 正在更新,你可以查看它自己日志文件所在的 /usr/local/maldetect/logs/ 目录。在 event_log 文件中,你会看到以下这些信息:

Dec 06 22:06:14 localhost maldet(3728): {sigup} performing signature update check...
Dec 06 22:06:14 localhost maldet(3728): {sigup} local signature set is version 2017070716978
Dec 06 22:07:13 localhost maldet(3728): {sigup} downloaded https://cdn.rfxn.com/downloads/maldet.sigs.ver
Dec 06 22:07:13 localhost maldet(3728): {sigup} new signature set (201708255569) available
Dec 06 22:07:13 localhost maldet(3728): {sigup} downloading https://cdn.rfxn.com/downloads/maldet-sigpack.tgz
. . .
. . .
Dec 06 22:07:43 localhost maldet(3728): {sigup} unpacked and installed maldet-clean.tgz
Dec 06 22:07:43 localhost maldet(3728): {sigup} signature set update completed
Dec 06 22:07:43 localhost maldet(3728): {sigup} 15218 signatures (12485 MD5 | 1954 HEX | 779 YARA | 0 USER)
Dec 06 22:14:55 localhost maldet(4070): {scan} signatures loaded: 15218 (12485 MD5 | 1954 HEX | 779 YARA | 0 USER)

/usr/local/maldetect/conf.maldet 文件中,你会看到这两行,但它们之间有一些注释:

autoupdate_signatures="1"
autoupdate_version="1"

LMD 不仅会自动更新其恶意软件签名,还会确保你拥有最新版本的 LMD 本身。

使用 ClamAV 和 maldet 扫描

LMD 的 maldet 守护进程会持续监控你在 /usr/local/maldetect/monitor_paths 文件中指定的目录。当它发现可疑文件时,会执行你在 conf.maldet 文件中指定的操作。

You can test your setup by downloading a simulated virus file from the European Institute for Computer Antivirus Research (EICAR) site.

你可以从 www.eicar.org/download-anti-malware-testfile/ 下载四种不同的模拟病毒文件。请注意,如果你在 Windows 主机上运行,这些文件可能会被 Windows 防病毒软件标记。因此,最好的方法是直接将文件下载到你的 Linux 虚拟机中。

只需下载一个或所有的 EICAR 测试文件,并将其传输到虚拟机的主目录。最好的做法是直接将文件下载到虚拟机中,使用以下四个命令:

wget https://secure.eicar.org/eicar.com
wget https://secure.eicar.org/eicar.com.txt
wget https://secure.eicar.org/eicar_com.zip
wget https://secure.eicar.org/eicarcom2.zip

等待片刻,你应该看到文件消失。然后,在 /usr/local/maldetect/logs/event_log 文件中查看,验证 LMD 是否已将文件移至隔离区:

Dec 01 15:18:31 localhost maldet(6388): {hit} malware hit {HEX}EICAR.TEST.3 found for /home/donnie/eicar.com.txt
Dec 01 15:18:31 localhost maldet(6388): {quar} malware quarantined from '/home/donnie/eicar.com.txt' to '/usr/local/maldetect/quarantine/eicar.com.txt.113345162'
Dec 01 15:18:31 localhost maldet(6388): {mon} scanned 5 new/changed files with clamav engine
Dec 01 15:20:32 localhost maldet(6388): {mon} warning clamd service not running; force-set monitor mode file scanning to every 120s
. . .
. . .
Dec 01 15:20:56 localhost maldet(6388): {quar} malware quarantined from '/home/donnie/eicar_com.zip' to '/usr/local/maldetect/quarantine/eicar_com.zip.216978719'

忽略 warning clamd service not running 的消息,因为我们不需要使用该服务。

LMD 的功能远不止我在这里能展示的内容。不过,你可以在随附的README文件中了解所有相关信息。

SELinux 注意事项

过去,在 Red Hat 类系统上进行杀毒扫描会触发 SELinux 警报。但是,在本章校对的过程中,所有扫描都正常工作,SELinux 从未打扰过我一次。

如果你在病毒扫描时生成了 SELinux 警报,只需更改一个布尔值就能解决:

[donnie@localhost ~]$ getsebool -a | grep 'virus'
antivirus_can_scan_system --> off
antivirus_use_jit --> off
[donnie@localhost ~]$

我们在这里关注的是默认关闭的antivirus_can_scan_system布尔值。要启用病毒扫描,只需执行以下操作:

[donnie@localhost ~]$ sudo setsebool -P antivirus_can_scan_system on
[sudo] password for donnie:
[donnie@localhost ~]$ getsebool antivirus_can_scan_system
antivirus_can_scan_system --> on
[donnie@localhost ~]$

这应该解决你可能遇到的任何与 SELinux 相关的扫描问题。不过,就目前情况来看,你可能不需要太担心这个问题。

使用 Rootkit Hunter 扫描根套件

根套件是极其恶劣的恶意软件,确实能毁掉你的一天。它们可以监听主人的命令,窃取敏感数据并发送给主人,或者为主人提供一个便捷的后门。它们的设计目标是隐秘,能够躲避普通视野的检查。有时,它们会用自己篡改过的版本替换像lsps这样的工具,这样就能显示系统上的所有文件或进程,除了与根套件相关的那些。根套件可以感染任何操作系统,甚至是我们喜爱的 Linux。

为了植入根套件,攻击者必须已经在系统上获得了管理员权限。这是我每次看到有人从 root 用户的 shell 进行所有工作时都感到不安的原因之一,也是我始终坚信尽可能使用sudo的原因。我的意思是,真的,为什么我们要让坏人这么容易得手?

几年前,在 Windows XP 的黑暗时期,索尼音乐公司遇到了一些麻烦,当时有人发现他们在音乐 CD 上植入了根套件。他们并不打算做任何恶意的事情,只是希望阻止人们使用计算机进行非法复制。当然,大多数人都使用 Windows XP 管理员账户,这使得根套件很容易感染他们的计算机。如今,Windows 用户仍然大多数使用管理员账户,但至少现在有了用户访问控制来帮助缓解这些问题。

有几种不同的程序可以扫描 rootkit,它们的使用方法基本相同。一个叫做 Rootkit Hunter,另一个叫做 chkrootkit。现在,请理解我向您展示这些程序,因为作为 Linux 管理员,您需要了解它们。实际上,它们并不是特别有用,因为有很多 rootkit 它们无法检测到。如果您真的想验证这一点,可以去 Github 上做一个 rootkit 的关键字搜索。找到一个可以在 Linux 上运行的 rootkit,将源代码下载到虚拟机上,然后按照包含的说明进行编译和安装。一旦安装完成,使用其中任何一个 rootkit 扫描程序进行扫描。很可能,rootkit 不会被检测到。另外,也不要指望 AppArmor 或 SELinux 能防止别人安装 rootkit,因为它们无法做到这一点。

不是每个 Github 上的 rootkit 都能正确编译,所以找到有效的可能需要一些反复试验。我成功编译并正确安装的一个是 Reptile rootkit,您可以从这里下载:github.com/f0rb1dd3n/Reptile

好的,让我们继续进行实验。

实验室操作 – 安装和更新 Rootkit Hunter

对于 Ubuntu,Rootkit Hunter 位于正常的仓库中。对于 CentOS 或 AlmaLinux,您需要安装 EPEL 仓库,正如我在第一章中展示的那样,在虚拟环境中运行 Linux。对于所有这些 Linux 发行版,软件包名称是 rkhunter。让我们开始吧:

  1. 使用以下命令之一安装 Rootkit Hunter,具体取决于您的系统。对于 Ubuntu,请执行以下操作:
sudo apt install rkhunter

对于 CentOS 7,请执行以下操作:

sudo yum install rkhunter

对于 AlmaLinux 8 或 AlmaLinux 9,请执行以下操作:

sudo dnf install rkhunter
  1. 安装完成后,您可以通过以下命令查看其选项:
man rkhunter
  • 接下来,使用 --update 选项更新 rootkit 签名:
[donnie@localhost ~]$ sudo rkhunter --update
[ Rootkit Hunter version 1.4.4 ]
Checking rkhunter data files...
 Checking file mirrors.dat [ Updated ]
 Checking file programs_bad.dat [ Updated ]
 Checking file backdoorports.dat [ No update ]
 Checking file suspscan.dat [ Updated ]
 Checking file i18n/cn [ No update ]
 Checking file i18n/de [ Updated ]
 Checking file i18n/en [ Updated ]
 Checking file i18n/tr [ Updated ]
 Checking file i18n/tr.utf8 [ Updated ]
 Checking file i18n/zh [ Updated ]
 Checking file i18n/zh.utf8 [ Updated ]
 Checking file i18n/ja [ Updated ]
  1. 现在,我们准备好进行扫描了。

您已完成实验室操作 – 恭喜!

扫描 rootkit

要运行扫描,使用 -c 选项。(-c 代表检查。)请耐心等待,因为这会花费一些时间:

sudo rkhunter -c

当您以这种方式运行扫描时,Rootkit Hunter 会定期停下来,要求您按 Enter 键继续。扫描完成后,您将在 /var/log/ 目录中找到 rkhunter.log 文件。

要让 Rootkit Hunter 自动作为 cron 任务运行,使用 --cronjob 选项,这将使程序完整运行,而无需您不断按 Enter 键。您还可能希望使用 --rwo 选项,这样程序只会报告警告,而不会报告所有正常情况。从命令行看,命令如下:

sudo rkhunter -c --cronjob --rwo

要创建一个 cron 任务,让 Rootkit Hunter 每晚自动运行,请打开 root 用户的 crontab 编辑器:

sudo crontab -e -u root

假设您希望 Rootkit Hunter 每晚 10:20 运行。请输入以下内容到 crontab 编辑器:

20 22 * * * /usr/bin/rkhunter -c --cronjob --rwo

因为 cron 只能使用 24 小时制时间,你必须将晚上 10:00 表达为 22.(只需将你习惯使用的普通 P.M.时间加 12 即可。)三个星号意味着该任务将每月的每一天、每年的每个月以及每周的每一天运行。你需要列出命令的完整路径。否则,cron 将找不到它。

你可以在 rkhunter 的手册页中找到更多可能感兴趣的选项,但这应该足以让你开始使用它了。

刚才我告诉过你,这些 rootkit 扫描程序并不是很有效,因为有许多 rootkit 它们无法检测到。这就是为什么处理 rootkit 的最佳方法是防止它们首次安装。因此,请确保保持系统锁定,以防止恶意行为者获取 root 权限。

接下来,让我们看看几种快速分析恶意软件的技术。

使用字符串和 VirusTotal 进行快速恶意软件分析

恶意软件分析是我这里无法详细讨论的高级话题之一。不过,我可以向你展示几种快速分析可疑文件的方法。

使用字符串分析文件

可执行文件通常内嵌有一系列文本字符串。你可以使用strings工具查看这些字符串。(是的,这很有道理,对吧?)根据你的发行版,strings可能已经安装了,也可能没有。它已经在 CentOS 和 AlmaLinux 上,但在 Ubuntu 上,你需要安装binutils包,像这样:

sudo apt install binutils

举个例子,让我们看看从一个加密币水龙头网站自动下载的Your File Is Ready To Download_2285169994.exe文件。为了检查这个文件,我会这样做:

strings "Your File Is Ready To Download_2285169994.exe" > output.txt
vim output.txt

我将输出保存到一个文本文件中,可以在 vim 中打开以查看行号。为了查看行号,我在 vim 屏幕底部输入了 :set number。(在 vim 的术语中,我们使用最后一行模式。)

很难准确地说要搜索什么,所以你只需浏览,直到看到一些有趣的内容为止。在这种情况下,看看我从第386行开始找到的内容:

386 The Setup program accepts optional command line parameters.
387 /HELP, /?
388 Shows this information.
389 /SP-
390 Disables the This will install... Do you wish to continue? prompt at the beginning of Setup.
391 /SILENT, /VERYSILENT
392 Instructs Setup to be silent or very silent.
393 /SUPPRESSMSGBOXES
394 Instructs Setup to suppress message boxes.
. . .
399 /NOCANCEL
400 Prevents the user from cancelling during the installation process.
. . .

有人说,可以将该程序的安装过程设置为以SILENT模式运行,而不弹出任何对话框。它还可以被设置为以一种用户无法取消安装的方式运行。当然,顶部的那行说这些是可选的命令行参数。但是,它们真的是可选的吗,还是默认硬编码了?这一点不太清楚,但在我看来,任何可以设置为以SILENT模式运行且不能被取消的安装程序都有些可疑,即使我们谈论的是可选参数。

好的,你可能会想,什么是加密货币水龙头? 它是一个网站,你可以通过浏览广告并解决某种 CAPTCHA 问题来领取少量的加密货币,比如比特币、以太坊或门罗币。大多数水龙头运营商是诚实的,但他们允许的网站广告往往并不安全,通常充斥着恶意软件、骗局和不适宜工作的图片。

这个小技巧有时候能很好地工作,但并非总是如此。更复杂的恶意软件可能不包含任何能够提供线索的文本字符串。那么,让我们看一下另一个用于恶意软件分析的快速小技巧。

使用 VirusTotal 扫描恶意软件

VirusTotal 是一个可以上传可疑文件进行分析的网站。它使用多种病毒扫描器,因此如果某个扫描器漏掉了什么,另一个扫描器可能会发现它。以下是扫描 Your File Is Ready To Download_2285169994.exe 文件的结果:

图 12.1:VirusTotal 扫描器

图 12.1:VirusTotal 扫描器

在这里,我们看到不同的病毒扫描器对这个文件有不同的分类。但无论它被归类为 Win.Malware.InstallcoreTrojan.InstallCore 还是其他什么名字,它依然是有害的。

虽然 VirusTotal 很好用,但你需要小心使用它。不要上传任何包含敏感或机密信息的文件,因为这些信息会被分享给其他人。

那么,这个特定的恶意软件到底是怎么回事呢?实际上,它是一个伪装成 Adobe Flash 安装程序的恶意软件。当然,你不想在生产环境的 Windows 机器上安装它进行测试。但是,如果你有一个 Windows 虚拟机,可以在其上测试这个恶意软件。(在开始之前最好先做一个虚拟机快照,或者准备好之后丢弃虚拟机。)

正如我一开始所说,恶意软件分析是一个相当深入的话题,有许多更复杂的工具可以用来分析它。然而,如果你对某个文件有疑虑并且只需要做一个快速检查,这两种技巧可能就足够了。

接下来,让我们看看如何自动审计系统中的各种事件。

理解 auditd 守护进程

假设你有一个目录,里面充满了只有极少数人需要查看的超级机密文件,你想知道何时有人未经授权尝试查看它们。或者,你也许想知道某个文件何时被修改,或者想知道系统有人登录后做了什么事情。为了满足这些需求,你可以使用 auditd 系统。它是一个非常酷的系统,我相信你会喜欢它的。

auditd 的一个优点是它工作在 Linux 内核层级,而不是用户模式层级。这使得攻击者更难以破坏它。

在 Red Hat 系列系统中,auditd 默认已经安装并启用。所以,你会在 CentOS 和 AlmaLinux 机器上找到它。而在 Ubuntu 系统中,它并不会默认安装,因此你需要手动安装:

sudo apt install auditd

在 Ubuntu 上,你可以通过正常的 systemctl 命令来控制 auditd 守护进程。所以,如果你需要重新启动 auditd 以读取新的配置文件,你可以使用以下命令:

sudo systemctl restart auditd

在 RHEL 类型的机器上,auditd 配置为不与普通的 systemctl 命令一起工作。(对于所有其他守护进程,它们是可以的。)所以,在你的 CentOS 和 AlmaLinux 机器上,你需要用传统的 service 命令来重启 auditd 守护进程,像这样:

sudo service auditd restart

除了这个小差异之外,从这里开始我告诉你关于 auditd 的所有内容都会适用于你所有的虚拟机。

创建审计规则

好的,让我们从一些简单的开始,一步步进入更高级的内容。首先,让我们检查一下是否有任何审计规则生效:

[donnie@localhost ~]$ sudo auditctl -l
[sudo] password for donnie:
No rules
[donnie@localhost ~]$

如你所见,auditctl 命令是我们用来管理审计规则的。-l 选项列出规则。

审计文件变更

现在,假设我们想查看当有人更改 /etc/passwd 文件时的情况。(我们将使用的命令看起来可能有点吓人,但我保证一旦我们分解它,就能理解。)下面是命令:

[donnie@localhost ~]$ sudo auditctl -w /etc/passwd -p wa -k passwd_changes
[sudo] password for donnie:
[donnie@localhost ~]$ sudo auditctl -l
-w /etc/passwd -p wa -k passwd_changes
[donnie@localhost ~]$

这里是详细说明:

  • -w:这代表“哪里”,它指向我们想要监控的对象。在这种情况下,它是 /etc/passwd

  • -p:这表示我们想要监控的对象权限。在这种情况下,我们监控的是,当任何人尝试(w)写入文件或尝试进行(a)属性更改时的情况。(我们可以审计的其他两个权限是(r)读取和(x)执行。)

  • -kk 代表键,它是 auditd 为规则分配名称的方式。所以,passwd_changes 是我们正在创建的规则的键或名称。

auditctl -l 命令向我们展示了规则确实存在。

现在,这个小问题是,这个规则是临时的,当我们重新启动机器时它会消失。为了使其永久生效,我们需要在 /etc/audit/rules.d/ 目录中创建一个自定义的 rules 文件。然后,当你重新启动 auditd 守护进程时,自定义规则将会被插入到 /etc/audit/audit.rules 文件中。由于 /etc/audit/ 目录只能被具有 root 权限的用户访问,我将通过列出文件的完整路径来打开文件,而不是尝试进入该目录:

sudo less /etc/audit/audit.rules

这个默认文件里没有很多内容:

## This file is automatically generated from /etc/audit/rules.d
-D
-b 8192
-f 1

这是这个文件的详细说明:

  • -D:这将导致当前生效的所有规则和监视被删除,以便我们从干净的状态开始。所以,如果我现在重新启动 auditd 守护进程,它会读取这个 audit.rules 文件,并删除我刚刚创建的规则。

  • -b 8192:这设置了我们可以同时进行的未处理审计缓冲区的数量。如果所有缓冲区都满了,系统将无法生成更多的审计信息。

  • -f 1:这设置了关键错误的失败模式,值可以是012-f 0将模式设置为静默,意味着auditd不会对关键错误做出任何处理。-f 1,正如我们所见,会告诉auditd只报告关键错误,而-f 2则会导致 Linux 内核进入恐慌模式。根据auditctl的手册页,任何处于高安全环境中的人都可能会将其更改为-f 2。不过就我们而言,-f 1是可行的。

你可以使用文本编辑器在/etc/audit/rules.d/目录中创建一个新的rules文件。或者,你也可以像这样将auditctl -l的输出重定向到一个新文件:

[donnie@localhost ~]$ sudo sh -c "auditctl -l > /etc/audit/rules.d/custom.rules"
[donnie@localhost ~]$ sudo service auditd restart

在 Ubuntu 上:

sudo systemctl restart auditd

由于 Bash shell 不允许我直接将信息重定向到/etc/目录中的文件,即使使用sudo,我必须使用sudo sh -c命令来执行auditctl命令。在重新启动auditd守护进程后,我们的audit.rules文件现在看起来是这样的:

## This file is automatically generated from /etc/audit/rules.d
-D
-b 8192
-f 1
-w /etc/passwd -p wa -k passwd_changes

现在,每次机器重启时,或者每次你手动重启auditd守护进程时,这个规则都会生效。

审计一个目录

Vicky 和 Cleopatra,我的纯灰色小猫和灰白色的虎斑猫,有一些超级敏感的秘密需要保护。所以,我创建了一个secretcats组并将它们添加进去。然后,我创建了secretcats共享目录并设置了访问控制,正如我在第九章,访问控制列表和共享目录管理中所示的那样:

[donnie@localhost ~]$ sudo groupadd secretcats
[sudo] password for donnie:
[donnie@localhost ~]$ sudo usermod -a -G secretcats vicky
[donnie@localhost ~]$ sudo usermod -a -G secretcats cleopatra
[donnie@localhost ~]$ sudo mkdir /secretcats
[donnie@localhost ~]$ sudo chown nobody:secretcats /secretcats/
[donnie@localhost ~]$ sudo chmod 3770 /secretcats/
[donnie@localhost ~]$ ls -ld /secretcats/
drwxrws--T. 2 nobody secretcats 6 Dec 11 14:47 /secretcats/
[donnie@localhost ~]$

Vicky 和 Cleopatra 想确保没有人能进入她们的东西,所以她们请求我为她们的目录设置一个审计规则:

[donnie@localhost ~]$ sudo auditctl -w /secretcats/ -k secretcats_watch
[sudo] password for donnie:
[donnie@localhost ~]$ sudo auditctl -l
-w /etc/passwd -p wa -k passwd_changes
-w /secretcats -p rwxa -k secretcats_watch
[donnie@localhost ~]$

如前所述,-w选项表示我们想要监控的内容,而-k选项表示审计规则的名称。这一次,我省略了-p选项,因为我想监控所有类型的访问。换句话说,我想监控任何读取、写入、属性更改或执行的操作。(因为这是一个目录,执行操作发生在有人尝试cd进入该目录时。)你可以在auditctl -l的输出中看到,省略-p选项后,我们将会监控所有操作。但是,假设我只想监控有人尝试cd进入该目录的情况,我可以将规则设置成这样:

sudo auditctl -w /secretcats/ -p x -k secretcats_watch

到现在为止还算简单吧?

在创建自己的自定义审计规则时要仔细规划。审计比实际需要更多的文件和目录可能会对性能产生一定影响,并且可能会淹没你在过多的信息中。只审计你真正需要审计的内容,按照情景要求或任何适用的管理机构的要求进行审计。

现在,让我们来看一些稍微复杂一点的内容。

审计系统调用

创建规则以监视某人执行特定操作并不难,但命令的语法比我们之前看到的要稍微复杂一些。通过这个规则,每当 Charlie 尝试打开文件或创建文件时,我们都会收到警报:

[donnie@localhost ~]$ sudo auditctl -a always,exit -F arch=b64 -S openat -F auid=1006
[sudo] password for donnie:
[donnie@localhost ~]$ sudo auditctl -l
-w /etc/passwd -p wa -k passwd_changes
-w /secretcats -p rwxa -k secretcats_watch
-a always,exit -F arch=b64 -S openat -F auid=1006
[donnie@localhost ~]$

这是详细说明:

  • -a always,exit:在这里,我们有动作和列表。exit部分意味着这个规则会被添加到系统调用exit列表中。每当操作系统退出一个系统调用时,exit列表将用于确定是否需要生成审计事件。always部分是动作,意味着每当从指定的系统调用退出时,都会为这个规则创建一个审计记录。请注意,动作和列表参数必须用逗号分隔。

  • -F arch=b64-F选项用于构建规则字段,我们可以在这个命令中看到两个规则字段。第一个规则字段指定了机器的 CPU 架构。b64意味着计算机正在使用 x86_64 CPU 运行。(不管是 Intel 还是 AMD 都不重要。)考虑到 32 位机器正在逐渐淘汰,而且 Sun SPARC 和 PowerPC 机器也并不常见,b64是你现在最常看到的。

  • -S openat-S选项指定了我们想要监视的系统调用。openat是一个系统调用,用来打开或创建文件。

  • -F auid=1006:这个第二个审计字段指定了我们想要监视的用户 ID 号码。(Charlie 的用户 ID 号码是1006。)

对系统调用(或称 syscalls)的完整解释对于我们当前的目的来说有点过于深奥。暂时说一下,系统调用会在用户发出请求 Linux 内核提供服务的命令时发生。如果你有兴趣,可以阅读更多关于系统调用的内容,网址是blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/

我在这里展示的只是你可以使用审计规则做的事情中的一小部分。想要查看更多示例,请查阅auditctl的手册页:

man auditctl

所以,现在你可能在想,既然我已经有了这些规则,我怎么知道别人什么时候会违反这些规则呢? 和往常一样,我很高兴你提出了这个问题。

使用 ausearch 和 aureport

auditd守护进程将事件记录到/var/log/audit/audit.log文件中。尽管你可以直接通过less等工具读取文件,但其实并不推荐这样做。ausearchaureport工具将帮助你将文件翻译成一种更易懂的语言。

搜索文件变更警报

让我们从查看我们创建的规则开始,这个规则会在/etc/passwd文件发生更改时向我们发出警报:

sudo auditctl -w /etc/passwd -p wa -k passwd_changes

现在,让我们对文件进行更改并查看警报信息。为了不再添加新用户,因为我已经用完了可以使用的猫咪名字,我将使用chfn工具将联系方式添加到克利奥帕特拉条目的注释字段:

[donnie@localhost etc]$ sudo chfn cleopatra
Changing finger information for cleopatra.
Name []: Cleopatra Tabby Cat
Office []: Donnie's back yard
Office Phone []: 555-5555
Home Phone []: 555-5556
Finger information changed.
[donnie@localhost etc]

现在,我将使用 ausearch 查找这个事件可能生成的任何审计消息:

[donnie@localhost ~]$ sudo ausearch -i -k passwd_changes
----
type=CONFIG_CHANGE msg=audit(12/11/2017 13:06:20.665:11393) : auid=donnie ses=842 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 op=add_rule key=passwd_changes li
st=exit res=yes
----
type=CONFIG_CHANGE msg=audit(12/11/2017 13:49:15.262:11511) : auid=donnie ses=842 op=updated_rules path=/etc/passwd key=passwd_changes list=exit res=yes
[donnie@localhost ~]$

这是细节分析:

  • -i:该选项将任何数字数据(尽可能)转换为文本。在这种情况下,它将用户 ID 号转换为实际用户名,在此显示为 auid=donnie。如果我省略 -i 选项,用户信息将显示为 auid=1000,这是我的用户 ID 号。

  • -k passwd_changes:这指定了审计规则的键或名称,我们希望查看该规则的审计消息。

这里,你会看到输出有两部分。第一部分仅显示我创建审计规则的时间,因此我们不关心这一部分。在第二部分,你可以看到我触发了该规则的时间,但它没有显示我如何触发它。所以,让我们使用 aureport 查看它是否能提供更多的细节:

[donnie@localhost ~]$ sudo aureport -i -k | grep 'passwd_changes'
1\. 12/11/2017 13:06:20 passwd_changes yes ? donnie 11393
2\. 12/11/2017 13:49:15 passwd_changes yes ? donnie 11511
3\. 12/11/2017 13:49:15 passwd_changes yes /usr/bin/chfn donnie 11512
4\. 12/11/2017 14:54:11 passwd_changes yes /usr/sbin/usermod donnie 11728
5\. 12/11/2017 14:54:25 passwd_changes yes /usr/sbin/usermod donnie 11736
[donnie@localhost ~]$

有趣的是,在使用 ausearch 时,你必须在 -k 选项后指定你感兴趣的审计规则的名称或键。而在 aureport 中,-k 选项意味着你想查看所有与所有审计规则键相关的日志条目。要查看特定键的日志条目,只需将输出通过管道传递到 grep-i 选项执行与 ausearch 相同的功能。

如你所见,aureportaudit.log 文件中晦涩的语言解析成更易理解的简单语言。我不确定自己做了什么操作以生成事件 1 和事件 2,于是我查看了 /var/log/secure 文件,看看能否找出原因。我在该时间点看到了以下两个条目:

Dec 11 13:06:20 localhost sudo: donnie : TTY=pts/1 ; PWD=/home/donnie ; USER=root ; COMMAND=/sbin/auditctl -w /etc/passwd -p wa -k passwd_changes
. . .
. . .
Dec 11 13:49:24 localhost sudo: donnie : TTY=pts/1 ; PWD=/home/donnie ; USER=root ; COMMAND=/sbin/ausearch -i -k passwd_changes

所以,事件 1 是我最初创建审计规则时发生的,事件 2 是我执行 ausearch 操作时发生的。

我必须坦白,第4行和第5行的事件有点神秘。它们都是在我调用 usermod 命令时创建的,而且它们都与我将 Vicky 和 Cleopatra 添加到 secretcats 组的安全日志条目相关:

Dec 11 14:54:11 localhost sudo:  donnie : TTY=pts/1 ; PWD=/home/donnie ; USER=root ; COMMAND=/sbin/usermod -a -G secretcats vicky
Dec 11 14:54:11 localhost usermod[14865]: add 'vicky' to group 'secretcats'
Dec 11 14:54:11 localhost usermod[14865]: add 'vicky' to shadow group 'secretcats'
Dec 11 14:54:25 localhost sudo:  donnie : TTY=pts/1 ; PWD=/home/donnie ; USER=root ; COMMAND=/sbin/usermod -a -G secretcats cleopatra
Dec 11 14:54:25 localhost usermod[14871]: add 'cleopatra' to group 'secretcats'
Dec 11 14:54:25 localhost usermod[14871]: add 'cleopatra' to shadow group 'secretcats'

奇怪的是,向用户添加副组并不会修改 passwd 文件。所以,我真不知道为什么这个规则会触发,并在第4行和第5行创建事件。

这就剩下第3行的事件了,在那里我使用 chfn 实际修改了 passwd 文件。以下是该条目的 secure 日志记录:

Dec 11 13:48:49 localhost sudo:  donnie : TTY=pts/1 ; PWD=/etc ; USER=root ; COMMAND=/bin/chfn cleopatra

所以,在所有这些事件中,只有第3行的事件是实际修改了 /etc/passwd 文件的。

我这里提到的 /var/log/secure 文件位于 Red Hat 类型的操作系统中,例如 CentOS 和 AlmaLinux。在你的 Ubuntu 机器上,你将看到 /var/log/auth.log 文件。

搜索目录访问规则违规

对于下一个场景,我们将为 Vicky 和 Cleopatra 创建一个共享目录,然后为其创建一个如下所示的审计规则:

sudo auditctl -w /secretcats/ -k secretcats_watch

因此,所有访问或尝试访问该目录的行为都应触发警报。首先,让我们让 Vicky 进入 /secretcats/ 目录并运行 ls -l 命令:

[vicky@localhost ~]$ cd /secretcats
[vicky@localhost secretcats]$ ls -l
total 4
-rw-rw-r--. 1 cleopatra secretcats 31 Dec 12 11:49 cleopatrafile.txt
[vicky@localhost secretcats]$

在这里,我们看到 Cleopatra 已经在这里创建了一个文件。(稍后我们会回来详细看看。)当事件触发 auditd 规则时,通常会在 /var/log/audit/audit.log 文件中生成多条记录。如果你查看每条记录,你会看到每条记录都涵盖了该事件的不同方面。当我执行 ausearch 命令时,我看到仅仅通过一次 ls -l 操作就有五条记录。为了节省空间,我这里只放第一条记录:

sudo ausearch -i -k secretcats_watch | less
type=PROCTITLE msg=audit(12/12/2017 12:15:35.447:14077) : proctitle=ls --color=auto -l
type=PATH msg=audit(12/12/2017 12:15:35.447:14077) : item=0 name=. inode=33583041 dev=fd:01 mode=dir,sgid,sticky,770 ouid=nobody ogid=secretcats rdev=00:00 obj=unconfined_u:object_r:default_t:s0 objtype=NORMAL
type=CWD msg=audit(12/12/2017 12:15:35.447:14077) :  cwd=/secretcats
type=SYSCALL msg=audit(12/12/2017 12:15:35.447:14077) : arch=x86_64 syscall=openat success=yes exit=3 a0=0xffffffffffffff9c a1=0x2300330 a2=O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC a3=0x0 items=1 ppid=10805 pid=10952 auid=vicky uid=vicky gid=vicky euid=vicky suid=vicky fsuid=vicky egid=vicky sgid=vicky fsgid=vicky tty=pts0 ses=1789 comm=ls exe=/usr/bin/ls subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=secretcats_watch

我将把最后一条记录放在这里:

type=PROCTITLE msg=audit(12/12/2017 12:15:35.447:14081) : proctitle=ls --color=auto -l
type=PATH msg=audit(12/12/2017 12:15:35.447:14081) : item=0 name=cleopatrafile.txt inode=33583071 dev=fd:01 mode=file,664 ouid=cleopatra ogid=secretcats rdev=00:00 obj=unconfined_u:object_r:default_t:s0 objtype=NORMAL
type=CWD msg=audit(12/12/2017 12:15:35.447:14081) :  cwd=/secretcats
type=SYSCALL msg=audit(12/12/2017 12:15:35.447:14081) : arch=x86_64 syscall=getxattr success=no exit=ENODATA(No data available) a0=0x7fff7c266e60 a1=0x7f0a61cb9db0 a2=0x0 a3=0x0 items=1 ppid=10805 pid=10952 auid=vicky uid=vicky gid=vicky euid=vicky suid=vicky fsuid=vicky egid=vicky sgid=vicky fsgid=vicky tty=pts0 ses=1789 comm=ls exe=/usr/bin/ls subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=secretcats_watch

在这两条记录中,你可以看到执行的操作(ls -l)和执行操作的人员——在这种情况下是猫——的信息。由于这是一个 RHEL 类型的机器,你还会看到 SELinux 上下文信息。在第二条记录中,你还可以看到 Vicky 在执行 ls 命令时看到的文件名。

接下来,假设那个狡猾的 Charlie 进入系统并尝试进入 /secretcats/ 目录:

[charlie@localhost ~]$ cd /secretcats
-bash: cd: /secretcats: Permission denied
[charlie@localhost ~]$ ls -l /secretcats
ls: cannot open directory /secretcats: Permission denied
[charlie@localhost ~]$

Charlie 不是 secretcats 组的成员,也没有权限进入 secretcats 目录。所以,他应该触发警报信息。实际上,他触发了一个包含四条记录的警报,我会再次列出第一条和最后一条。下面是第一条记录:

sudo ausearch -i -k secretcats_watch | less
type=PROCTITLE msg=audit(12/12/2017 12:32:04.341:14152) : proctitle=ls --color=auto -l /secretcats
type=PATH msg=audit(12/12/2017 12:32:04.341:14152) : item=0 name=/secretcats inode=33583041 dev=fd:01 mode=dir,sgid,sticky,770 ouid=nobody ogid=secretcats rdev=00:00 obj=unconfined_u:object_r:default_t:s0 objtype=NORMAL
type=CWD msg=audit(12/12/2017 12:32:04.341:14152) :  cwd=/home/charlie
type=SYSCALL msg=audit(12/12/2017 12:32:04.341:14152) : arch=x86_64 syscall=lgetxattr success=yes exit=35 a0=0x7ffd8d18f7dd a1=0x7f2496858f8a a2=0x12bca30 a3=0xff items=1 ppid=11637 pid=11663 auid=charlie uid=charlie gid=charlie euid=charlie suid=charlie fsuid=charlie egid=charlie sgid=charlie fsgid=charlie tty=pts0 ses=1794 comm=ls exe=/usr/bin/ls subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=secretcats_watch

这是最后一条记录:

type=PROCTITLE msg=audit(12/12/2017 12:32:04.341:14155) : proctitle=ls --color=auto -l /secretcats
type=PATH msg=audit(12/12/2017 12:32:04.341:14155) : item=0 name=/secretcats inode=33583041 dev=fd:01 mode=dir,sgid,sticky,770 ouid=nobody ogid=secretcats rdev=00:00 obj=unconfined_u:object_r:default_t:s0 objtype=NORMAL
type=CWD msg=audit(12/12/2017 12:32:04.341:14155) :  cwd=/home/charlie
type=SYSCALL msg=audit(12/12/2017 12:32:04.341:14155) : arch=x86_64 syscall=openat success=no exit=EACCES(Permission denied) a0=0xffffffffffffff9c a1=0x12be300 a2=O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC a3=0x0 items=1 ppid=11637 pid=11663 auid=charlie uid=charlie gid=charlie euid=charlie suid=charlie fsuid=charlie egid=charlie sgid=charlie fsgid=charlie tty=pts0 ses=1794 comm=ls exe=/usr/bin/ls subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=secretcats_watch

这里有两点需要注意。首先,仅仅尝试 cd 进入目录并不会触发警报。然而,使用 ls 尝试读取目录内容时,会触发警报。其次,注意第二条记录中出现的 Permission denied 消息。

我们将查看的最后一组警报是在 Cleopatra 创建她的 cleopatrafile.txt 文件时触发的。这个事件触发了一个包含 30 条记录的警报。我这里只展示其中的两条,第一条如下:

type=PROCTITLE msg=audit(12/12/2017 11:49:37.536:13856) : proctitle=vim cleopatrafile.txt
type=PATH msg=audit(12/12/2017 11:49:37.536:13856) : item=0 name=. inode=33583041 dev=fd:01 mode=dir,sgid,sticky,770 ouid=nobody ogid=secretcats rdev=00:00 obj=unconfined_u:o
bject_r:default_t:s0 objtype=NORMAL
type=CWD msg=audit(12/12/2017 11:49:37.536:13856) :  cwd=/secretcats
type=SYSCALL msg=audit(12/12/2017 11:49:37.536:13856) : arch=x86_64 syscall=open success=yes exit=4 a0=0x5ab983 a1=O_RDONLY a2=0x0 a3=0x63 items=1 ppid=9572 pid=9593 auid=cle
opatra uid=cleopatra gid=cleopatra euid=cleopatra suid=cleopatra fsuid=cleopatra egid=cleopatra sgid=cleopatra fsgid=cleopatra tty=pts0 ses=1779 comm=vim exe=/usr/bin/vim sub
j=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=secretcats_watch

这是第二条记录:

type=PROCTITLE msg=audit(12/12/2017 11:49:56.001:13858) : proctitle=vim cleopatrafile.txt
type=PATH msg=audit(12/12/2017 11:49:56.001:13858) : item=1 name=/secretcats/.cleopatrafile.txt.swp inode=33583065 dev=fd:01 mode=file,600 ouid=cleopatra ogid=secretcats rdev
=00:00 obj=unconfined_u:object_r:default_t:s0 objtype=DELETE
type=PATH msg=audit(12/12/2017 11:49:56.001:13858) : item=0 name=/secretcats/ inode=33583041 dev=fd:01 mode=dir,sgid,sticky,770 ouid=nobody ogid=secretcats rdev=00:00 obj=unc
onfined_u:object_r:default_t:s0 objtype=PARENT
type=CWD msg=audit(12/12/2017 11:49:56.001:13858) :  cwd=/secretcats
type=SYSCALL msg=audit(12/12/2017 11:49:56.001:13858) : arch=x86_64 syscall=unlink success=yes exit=0 a0=0x15ee7a0 a1=0x1 a2=0x1 a3=0x7ffc2c82e6b0 items=2 ppid=9572 pid=9593
auid=cleopatra uid=cleopatra gid=cleopatra euid=cleopatra suid=cleopatra fsuid=cleopatra egid=cleopatra sgid=cleopatra fsgid=cleopatra tty=pts0 ses=1779 comm=vim exe=/usr/bin
/vim subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=secretcats_watch

你可以看出,这两条消息中的第一条发生在 Cleopatra 保存文件并退出 vim 时,因为第二条消息显示了 objtype=DELETE,她的临时 vim 交换文件被删除了。

好的,这些都没问题,但如果这些信息太多了怎么办?如果你只是想要一份简洁且稀疏的安全事件列表,该怎么办呢?为此,我们将使用 aureport。我们将像之前那样使用它。

首先,我们将 aureport 的输出通过管道传递给 less,而不是传递给 grep,这样我们可以看到列标题:

[donnie@localhost ~]$ sudo aureport -i -k | less
Key Report
===============================================
# date time key success exe auid event
===============================================
1\. 12/11/2017 13:06:20 passwd_changes yes ? donnie 11393
2\. 12/11/2017 13:49:15 passwd_changes yes ? donnie 11511
3\. 12/11/2017 13:49:15 passwd_changes yes /usr/bin/chfn donnie 11512
4\. 12/11/2017 14:54:11 passwd_changes yes /usr/sbin/usermod donnie 11728
5\. 12/11/2017 14:54:25 passwd_changes yes /usr/sbin/usermod donnie 11736
. . .
. . .

success 列中的状态将是 yesno,取决于用户是否成功执行了违反规则的操作。或者,如果事件不是由规则触发的,状态可能是问号。

对于 Charlie,我们可以看到在第 48 行发生了一个 yes 事件,而第 4951 行的事件都显示为 no 状态。我们还可以看到,所有这些记录都是由于 Charlie 使用了 ls 命令而触发的:

[donnie@localhost ~]$ sudo aureport -i -k | grep 'secretcats_watch'

6. 12/11/2017 15:01:25 secretcats_watch yes ? donnie 11772

8. 12/12/2017 11:49:29 secretcats_watch yes /usr/bin/ls cleopatra 13828

9. 12/12/2017 11:49:37 secretcats_watch yes /usr/bin/vim cleopatra 13830

10. 12/12/2017 11:49:37 secretcats_watch yes /usr/bin/vim cleopatra 13829

48. 12/12/2017 12:32:04 secretcats_watch yes /usr/bin/ls charlie 14152

49. 12/12/2017 12:32:04 secretcats_watch no /usr/bin/ls charlie 14153

50. 12/12/2017 12:32:04 secretcats_watch no /usr/bin/ls charlie 14154

51. 12/12/2017 12:32:04 secretcats_watch no /usr/bin/ls charlie 14155

[donnie@localhost ~]$

你可能会觉得,第48行中的yes事件表示 Charlie 成功读取了secretcats目录的内容。为了进一步分析,我们来看一下每行末尾的事件编号,并将它们与我们之前运行的ausearch命令的输出对应起来。你会看到,事件编号1415214155属于一组记录,它们的时间戳相同。我们可以从每条记录的第一行看到这一点:

[donnie@localhost ~]$ sudo ausearch -i -k secretcats_watch | less
type=PROCTITLE msg=audit(12/12/2017 12:32:04.341:14152) : proctitle=ls --color=auto -l /secretcats
type=PROCTITLE msg=audit(12/12/2017 12:32:04.341:14153) : proctitle=ls --color=auto -l /secretcats
type=PROCTITLE msg=audit(12/12/2017 12:32:04.341:14154) : proctitle=ls --color=auto -l /secretcats
type=PROCTITLE msg=audit(12/12/2017 12:32:04.341:14155) : proctitle=ls --color=auto -l /secretcats

正如我们之前所提到的,这一系列的最后一条记录显示 Charlie 的Permission denied,这才是最重要的。

空间有限,我无法对审计日志记录中的每一项逐个进行详细解释。然而,你可以在这里阅读官方的 Red Hat 文档:access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/security_hardening/auditing-the-system_security-hardening#understanding-audit-log-files_auditing-the-system

搜索系统调用规则违规

我们创建的第三条规则是用来监控那个狡猾的 Charlie。这条规则会在 Charlie 尝试打开或创建文件时触发警报。(正如我们之前所提到的,1006是 Charlie 的用户 ID。)

sudo auditctl -a always,exit -F arch=b64 -S openat -F auid=1006

尽管 Charlie 在这个系统上做的事情不多,这个规则却给我们带来了比预期更多的日志条目。我们来看看其中的几条:

time->Tue Dec 12 11:49:29 2017
type=PROCTITLE msg=audit(1513097369.952:13828): proctitle=6C73002D2D636F6C6F723D6175746F
type=PATH msg=audit(1513097369.952:13828): item=0 name="." inode=33583041 dev=fd:01 mode=043770 ouid=99 ogid=1009 rdev=00:00 obj=unconfined_u:object_r:default_t:s0 objtype=NO
RMAL
type=CWD msg=audit(1513097369.952:13828):  cwd="/secretcats"
type=SYSCALL msg=audit(1513097369.952:13828): arch=c000003e syscall=257 success=yes exit=3 a0=ffffffffffffff9c a1=10d1560 a2=90800 a3=0 items=1 ppid=9572 pid=9592 auid=1004 u
id=1004 gid=1006 euid=1004 suid=1004 fsuid=1004 egid=1006 sgid=1006 fsgid=1006 tty=pts0 ses=1779 comm="ls" exe="/usr/bin/ls" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0
:c0.c1023 key="secretcats_watch"

这条记录是在 Charlie 尝试访问/secretcats/目录时生成的。所以我们可以预期会看到这一条。但我们没有预料到的是,Charlie 通过安全外壳SSH)登录系统时,间接访问的文件记录清单异常冗长。这里有一条:

time->Tue Dec 12 11:50:28 2017
type=PROCTITLE msg=audit(1513097428.662:13898): proctitle=737368643A20636861726C6965407074732F30
type=PATH msg=audit(1513097428.662:13898): item=0 name="/proc/9726/fd" inode=1308504 dev=00:03 mode=040500 ouid=0 ogid=0 rdev=00:00 obj=unconfined_u:unconfined_r:unconfined_t
:s0-s0:c0.c1023 objtype=NORMAL
type=CWD msg=audit(1513097428.662:13898):  cwd="/home/charlie"
type=SYSCALL msg=audit(1513097428.662:13898): arch=c000003e syscall=257 success=yes exit=3 a0=ffffffffffffff9c a1=7ffc7ca1d840 a2=90800 a3=0 items=1 ppid=9725 pid=9726 auid=1
006 uid=1006 gid=1008 euid=1006 suid=1006 fsuid=1006 egid=1008 sgid=1008 fsgid=1008 tty=pts0 ses=1781 comm="sshd" exe="/usr/sbin/sshd" subj=unconfined_u:unconfined_r:unconfin
ed_t:s0-s0:c0.c1023 key=(null)

这里有另一条:

time->Tue Dec 12 11:50:28 2017
type=PROCTITLE msg=audit(1513097428.713:13900): proctitle=737368643A20636861726C6965407074732F30
type=PATH msg=audit(1513097428.713:13900): item=0 name="/etc/profile.d/" inode=33593031 dev=fd:01 mode=040755 ouid=0 ogid=0 rdev=00:00 obj=system_u:object_r:bin_t:s0 objtype=
NORMAL
type=CWD msg=audit(1513097428.713:13900):  cwd="/home/charlie"
type=SYSCALL msg=audit(1513097428.713:13900): arch=c000003e syscall=257 success=yes exit=3 a0=ffffffffffffff9c a1=1b27930 a2=90800 a3=0 items=1 ppid=9725 pid=9726 auid=1006 u
id=1006 gid=1008 euid=1006 suid=1006 fsuid=1006 egid=1008 sgid=1008 fsgid=1008 tty=pts0 ses=1781 comm="bash" exe="/usr/bin/bash" subj=unconfined_u:unconfined_r:unconfined_t:s
0-s0:c0.c1023 key=(null)

在第一条记录中,我们看到 Charlie 访问了/usr/sbin/sshd文件。在第二条记录中,我们看到他访问了/usr/bin/bash文件。并不是 Charlie 主动选择访问这些文件。操作系统在正常的登录过程中为他访问了这些文件。所以如你所见,当你创建审计规则时,必须小心你所希望的结果,因为有可能你会得到你并不完全想要的。如果你确实需要监控某人,你应该创建一个不会给你提供如此多信息的规则。

既然我们已经开始了,不如看看aureport的输出是什么样的:

[donnie@localhost ~]$ sudo aureport -s -i | grep 'openat'
1068\. 12/12/2017 11:49:29 openat 9592 ls cleopatra 13828
1099\. 12/12/2017 11:50:28 openat 9665 sshd charlie 13887
1100\. 12/12/2017 11:50:28 openat 9665 sshd charlie 13889
1101\. 12/12/2017 11:50:28 openat 9665 sshd charlie 13890
1102\. 12/12/2017 11:50:28 openat 9726 sshd charlie 13898
1103\. 12/12/2017 11:50:28 openat 9726 bash charlie 13900
1104\. 12/12/2017 11:50:28 openat 9736 grep charlie 13901
1105\. 12/12/2017 11:50:28 openat 9742 grep charlie 13902
1108\. 12/12/2017 11:50:51 openat 9766 ls charlie 13906
1110\. 12/12/2017 12:15:35 openat 10952 ls vicky 14077
1115\. 12/12/2017 12:30:54 openat 11632 sshd charlie 14129
. . .
. . .

除了 Charlie 的行为外,我们还可以看到 Vicky 和 Cleopatra 的行为。这是因为我们为/secretcats/目录设置的规则,在 Vicky 和 Cleopatra 访问、查看或创建该目录中的文件时生成了openat事件。

生成身份验证报告

你可以在不定义任何审计规则的情况下生成用户身份验证报告。只需使用aureport加上-au选项切换。(记住auauthentication(认证)前两个字母。)

[donnie@localhost ~]$ sudo aureport -au
[sudo] password for donnie:
Authentication Report
============================================
# date time acct host term exe success event
============================================
1\. 10/28/2017 13:38:52 donnie localhost.localdomain tty1 /usr/bin/login yes 94
2\. 10/28/2017 13:39:03 donnie localhost.localdomain /dev/tty1 /usr/bin/sudo yes 102
3\. 10/28/2017 14:04:51 donnie localhost.localdomain /dev/tty1 /usr/bin/sudo yes 147
. . .
. . .
239\. 12/12/2017 11:50:20 charlie 192.168.0.222 ssh /usr/sbin/sshd no 13880
244\. 12/12/2017 12:10:06 cleopatra 192.168.0.222 ssh /usr/sbin/sshd no 13992
. . .

对于登录事件,这告诉我们用户是通过本地终端还是远程 SSH 登录的。要查看任何事件的详细信息,使用ausearch并加上-a选项,后面跟上你在行尾看到的事件号。(奇怪的是,-a选项代表一个事件。)

我们来看一下 Charlie 的事件号14122

[donnie@localhost ~]$ sudo ausearch -a 14122
----
time->Tue Dec 12 12:30:49 2017
type=USER_AUTH msg=audit(1513099849.322:14122): pid=11632 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=pubkey acct="charlie" exe="/usr/sbin/sshd" hostname=? addr=192.168.0.222 terminal=ssh res=failed'

这个问题在于它真的没有任何意义。我是为 Charlie 执行登录操作的人,而且我可以肯定地说 Charlie 从未有过登录失败的情况。事实上,我们可以将这与/var/log/secure文件中的匹配条目进行关联:

Dec 12 12:30:53 localhost sshd[11632]: Accepted password for charlie from 192.168.0.222 port 34980 ssh2
Dec 12 12:30:54 localhost sshd[11632]: pam_unix(sshd:session): session opened for user charlie by (uid=0)

这两个条目的时间戳比ausearch输出的时间戳晚了几秒钟,但这没问题。这个日志文件中没有任何迹象表明 Charlie 曾经登录失败,而且这两个条目清楚地显示了 Charlie 的登录确实是成功的。这里的教训是,当你在ausearchaureport的输出中看到奇怪的内容时,一定要与正确的身份验证日志文件中的匹配条目进行关联,以更好地了解发生了什么。(这里提到的身份验证日志文件是指 Red Hat 类型系统的/var/log/secure和 Ubuntu 系统的/var/log/auth.log。其他 Linux 发行版的文件名可能会有所不同。)

使用预定义的规则集

在你的 CentOS 7 机器的/usr/share/doc/audit-version_number/rules/目录和 AlmaLinux 机器的/usr/share/audit/sample-rules/目录中,你会看到一些针对不同场景的预设规则集。一旦你在 Ubuntu 上安装了auditd,你将在/usr/share/doc/auditd/examples/rules/目录下找到审计规则。无论如何,这三个发行版中有些规则集是通用的。我们来看一下 AlmaLinux 9 机器,看看那里的情况:

[donnie@localhost ~]$ cd /usr/share/audit/sample-rules/
[donnie@localhost sample-rules]$ ls -l
total 160
. . .
-rw-r--r--. 1 root root 4943 Oct 27 07:15 30-nispom.rules
. . .
-rw-r--r--. 1 root root 6179 Oct 27 07:15 30-pci-dss-v31.rules
-rw-r--r--. 1 root root 6624 Oct 27 07:15 30-stig.rules
-rw-r--r--. 1 root root 1458 Oct 27 07:15 31-privileged.rules
-rw-r--r--. 1 root root  213 Oct 27 07:15 32-power-abuse.rules
. . .
[donnie@localhost sample-rules]$

我想关注的三个文件是nispompci-dssstig文件。每个规则集都旨在满足特定认证机构的审计标准。按顺序,这些规则集是:

  • nispom:国家工业安全计划——你会看到这个规则集在美国国防部或其承包商那里使用。

  • pci-dss:支付卡行业数据安全标准——如果你从事银行或金融行业的工作,甚至如果你只是在经营一个接受信用卡支付的在线业务,你很可能会非常熟悉这个标准。

  • stig: 安全技术实施指南 - 如果你在美国政府工作,或可能在其他政府工作,你将会接触到这个。

要使用这些规则集中的一个,只需将相应的文件复制到/etc/audit/rules.d/目录:

[donnie@localhost rules]$ sudo cp 30-pci-dss-v31.rules /etc/audit/rules.d
[donnie@localhost rules]$

在复制规则文件之后,重新启动auditd守护进程以读取新规则。

对于 Red Hat、CentOS 或 AlmaLinux,执行以下操作:

sudo service auditd restart

对于 Ubuntu,执行以下操作:

sudo systemctl restart auditd

当然,某些规则可能对你不起作用,或者你可能需要启用一个当前被禁用的规则。如果是这样,只需在文本编辑器中打开相应的规则文件,注释掉不生效的部分或取消注释需要启用的部分。

即使auditd非常酷,也要记住它只是提醒你潜在的安全漏洞,并不会增强系统的安全性来防止这些漏洞。

实操实验 - 使用auditd

在本实验中,你将练习使用auditd的各种功能。让我们开始吧:

  1. 仅在 Ubuntu 中,安装auditd
sudo apt update
sudo apt install auditd
  1. 查看当前生效的规则:
sudo auditctl -l
  1. 在命令行中,创建一个临时规则,审计/etc/passwd文件的变化。验证规则是否生效:
sudo auditctl -w /etc/passwd -p wa -k passwd_changes
sudo auditctl -l
  1. 为 Lionel 创建一个用户账户。在 Ubuntu 中,执行以下操作:
sudo adduser lionel
  1. 在 CentOS 或 AlmaLinux 中,执行以下操作:
sudo useradd lionel
sudo passwd lionel
  1. 搜索有关passwd文件变动的审计信息:
sudo ausearch -i -k passwd_changes
sudo aureport -i -k | grep 'passwd_changes'
  1. 注销你的账户并以 Lionel 的身份登录。然后,再次注销 Lionel 的账户,重新登录到你的账户。

  2. 进行身份验证报告:

sudo aureport -au
  1. 创建/secrets目录并设置权限,确保只有 root 用户能够访问:
sudo mkdir /secrets
sudo chmod 700 /secrets
  1. 创建一个监视/secrets目录的规则:
sudo auditctl -w /secrets -k secrets_watch
sudo auditctl -l
  1. 注销你的账户,然后以 Lionel 的身份登录。让他尝试查看/secrets目录中的内容:
ls -l /secrets
  1. 注销 Lionel 的账户并登录到你的账户。查看 Lionel 创建的警报:
sudo ausearch -i -k secrets_watch | less
  1. 你现在有了两个临时规则,它们将在重启机器时消失。通过创建一个custom.rules文件,使其变为永久规则:
sudo sh -c "auditctl -l > /etc/audit/rules.d/custom.rules"
  1. 重启机器并验证规则是否仍然生效:
sudo auditctl -l

你已完成实验 - 恭喜!

实操实验 - 使用预配置规则与auditd

在本实验中,我们将模拟美国政府是我们的客户,我们需要设置一个服务器来符合他们的安全技术实施指南STIG)审计标准。为此,我们将使用在安装auditd时自动安装的多个预配置规则集。请注意,此实验可以在你任何一台虚拟机上运行:

  1. 删除你在上一个实验中创建的custom.rules文件,然后重启auditd服务。

  2. 10-base-config.rules30-stig.rules31-privileged.rules99-finalize.rules文件复制到/etc/audit/rules.d/目录。(这些规则文件在 Ubuntu 的/usr/share/doc/auditd/examples/rules/目录中,在 AlmaLinux 的/usr/share/audit/sample-rules/目录中。)

[donnie@almalinux9 sample-rules]$ pwd
/usr/share/audit/sample-rules
[donnie@almalinux9 sample-rules]$ sudo cp 10-base-config.rules 30-stig.rules 31-privileged.rules 99-finalize.rules /etc/audit/rules.d
[donnie@almalinux9 sample-rules]$
  1. 重启auditd服务,然后使用sudo auditctl -l查看新的活动规则集。

  2. 实验结束。

你刚刚看到,我们有时可以同时使用多个不同的预配置规则集,只要它们互补。但要理解的是,你永远不会同时使用所有预配置的规则集。

在这一部分中,你了解了一些如何使用auditd的示例。接下来,让我们看看一种更简单的文件和目录审计方法。

使用 inotifywait 审计文件和目录

有时候,你可能只需要一个快速简便的方法来实时监控文件或目录。与其将审计消息发送到audit.log文件,不如使用inotifywait,它可以在有人访问指定文件或目录时,立即在终端中弹出消息。这个工具是inotify-tools包的一部分,适用于 Ubuntu 和 AlmaLinux。它不是默认安装的,如果未安装,您可以自行安装。

要监控单个文件,只需执行:

donnie@donnie-ca:~$ sudo inotifywait -m /secrets/donnie_file.txt
[sudo] password for donnie: 
Setting up watches.
Watches established.
/secrets/donnie_file.txt OPEN 
/secrets/donnie_file.txt CLOSE_NOWRITE,CLOSE

/secrets/目录设置为只有具有 root 权限的人才能访问,因此我必须使用sudo才能使其生效。-m选项使inotifywait进行持续监控,而不是在发生某些事件后立即退出。当我使用less打开文件时,出现了OPEN消息,当我退出less时,出现了CLOSE_NOWRITE,CLOSE消息。现在,让我们关闭这个,并监控整个目录。我们只需要添加-r选项,省略文件名,如下所示:

donnie@donnie-ca:~$ sudo inotifywait -m -r /secrets/
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
/secrets/ OPEN donnie_file.txt
/secrets/ CREATE .donnie_file.txt.swp
/secrets/ OPEN .donnie_file.txt.swp
/secrets/ CREATE .donnie_file.txt.swx
. . .
. . .
/secrets/ CLOSE_NOWRITE,CLOSE donnie_file.txt
/secrets/ OPEN donnie_file.txt
/secrets/ CLOSE_NOWRITE,CLOSE donnie_file.txt
/secrets/ MODIFY .donnie_file.txt.swp
/secrets/ MODIFY .donnie_file.txt.swp

这次,我在vim中打开了donnie_file.txt文件,这导致出现了一堆消息。因为当你在vim中打开一个文件时,它会创建一些临时文件,这些临时文件在退出vim时会被清除。(注意,我实际上还没有编辑文件,而且当我编辑文件时,更多的消息会被创建。)

尽管inotifywait看起来很有用,但它也有一个缺点。那就是,为了使用它,你需要始终盯着工作站,保持运行inotifywait的终端窗口打开,并等待消息弹出。它没有日志机制,也没有守护进程模式。但是,如果你需要实时监控某些东西,它可能会很有用。

这就是inotifywait的全部内容。接下来,我们将介绍 OpenSCAP,它实际上可以修复不太安全的系统。

使用 oscap 应用 OpenSCAP 策略

安全内容自动化协议SCAP)是由美国国家标准与技术研究院NIST)创建的。它包括强化指南、强化模板和用于设置安全系统的基准配置指南。OpenSCAP 是一套开源工具,可用于实施 SCAP。它包括以下内容:

  • 可以应用于系统的安全配置文件。不同的配置文件满足多个不同认证机构的要求。

  • 帮助进行系统初步设置的安全指南。

  • oscap 命令行工具用于应用安全模板。

  • 在有桌面界面的系统上,你可以使用 SCAP Workbench,一个图形界面工具。

你可以在 Red Hat 或 Ubuntu 发行版上安装 OpenSCAP,但在 Red Hat 发行版上实现得更好。首先,当你安装 Red Hat 类型的操作系统时,你可以选择在安装过程中应用 SCAP 配置文件。而 Ubuntu 则做不到这一点。所有 Red Hat 类型的发行版都配备了一套相当完整的、可直接使用的配置文件。而 Ubuntu 22.04 只包含 Fedora 14 和 RHEL 6 的过时配置文件,并没有适用于 Ubuntu 22.04 的配置文件,这让我觉得非常奇怪。不过不用担心,我稍后会告诉你如何获得一些不错的 Ubuntu 配置文件。

在进行初始系统构建时,最好从一个适合你场景的安全检查单开始,因为 OpenSCAP 无法为你自动完成某些任务。然后,使用 OpenSCAP 完成剩余的工作。我将在 第十六章繁忙蜜蜂的安全小贴士和技巧 的最后告诉你更多关于安全检查单的信息。

好的,让我们学习如何安装 OpenSCAP 以及如何使用所有发行版中通用的命令行工具。

安装 OpenSCAP

在你的 CentOS 7 机器上,如果你没有在操作系统安装过程中安装 OpenSCAP,请执行以下操作:

sudo yum install openscap-scanner scap-security-guide

对于 AlmaLinux 8 或 AlmaLinux 9,请执行以下操作:

sudo dnf install openscap-scanner scap-security-guide

在 Ubuntu 22.04 机器上执行以下操作:

sudo apt install python-openscap

查看配置文件

在 CentOS 7 机器和 AlmaLinux 机器上,你会在 /usr/share/xml/scap/ssg/content/ 目录下看到配置文件。

正如我之前提到的,Ubuntu 只在 /usr/share/openscap/ 目录下提供了一些过时的 Fedora 14 和 RHEL 6 配置文件,而没有任何 Ubuntu 版本的配置文件。(为什么会这样,我也不知道。)这些配置文件是 .xml 格式的,每个文件包含一个或多个可以应用于系统的配置文件。例如,这里是来自 CentOS 7 机器的一些配置文件:

[donnie@localhost content]$ pwd
/usr/share/xml/scap/ssg/content
[donnie@localhost content]$ ls -l
total 50596
-rw-r--r--. 1 root root  6734643 Oct 19 19:40 ssg-centos6-ds.xml
-rw-r--r--. 1 root root  1596043 Oct 19 19:40 ssg-centos6-xccdf.xml
-rw-r--r--. 1 root root 11839886 Oct 19 19:41 ssg-centos7-ds.xml
-rw-r--r--. 1 root root  2636971 Oct 19 19:40 ssg-centos7-xccdf.xml
-rw-r--r--. 1 root root      642 Oct 19 19:40 ssg-firefox-cpe-dictionary.xml
. . .
-rw-r--r--. 1 root root 11961196 Oct 19 19:41 ssg-rhel7-ds.xml
-rw-r--r--. 1 root root   851069 Oct 19 19:40 ssg-rhel7-ocil.xml
-rw-r--r--. 1 root root  2096046 Oct 19 19:40 ssg-rhel7-oval.xml
-rw-r--r--. 1 root root  2863621 Oct 19 19:40 ssg-rhel7-xccdf.xml
[donnie@localhost content]$

在 AlmaLinux 8 机器上,你会看到一个类似的列表,只不过它们是针对 AlmaLinux 8 的。而在 AlmaLinux 9 上,情况有点不同。写这篇文章时,我们只有一个配置文件。这是因为 RHEL 9 发行版相当新,所以针对它们的 SCAP 配置文件的开发还未完成。无论如何,这是 AlmaLinux 9 的文件:

[donnie@almalinux9 content]$ pwd
/usr/share/xml/scap/ssg/content
[donnie@almalinux9 content]$ ls -l
total 21524
-rw-r--r--. 1 root root 22040119 Oct 27 08:37 ssg-almalinux9-ds.xml
[donnie@almalinux9 content]$

用于处理 OpenSCAP 的命令行工具是 oscap。在我们的 AlmaLinux 9 机器上,让我们使用它并加上 info 参数来查看任何配置文件的信息。我们来看一下 ssg-almalinux9-ds.xml 文件:

[donnie@almalinux9 content]$ pwd
/usr/share/xml/scap/ssg/content
[donnie@almalinux9 content]$ sudo oscap info ssg-almalinux9-ds.xml 
. . .
. . .
    Profiles:
        Title: ANSSI-BP-028 (enhanced)
        Id: xccdf_org.ssgproject.content_profile_anssi_bp28_enhanced
        Title: ANSSI-BP-028 (high)
        Id: xccdf_org.ssgproject.content_profile_anssi_bp28_high
        Title: ANSSI-BP-028 (intermediary)
        Id: xccdf_org.ssgproject.content_profile_anssi_bp28_intermediary
        Title: ANSSI-BP-028 (minimal)
        Id: xccdf_org.ssgproject.content_profile_anssi_bp28_minimal
        . . .

由于格式限制,我无法向您展示完整的配置文件列表。因此,请您自行操作,向下滚动列表。您将看到针对STIGPCI-DSS的配置文件,就像我们在审核规则中看到的那样。这里还提供了适用于美国医疗机构的HIPAA配置文件,互联网安全中心CIS)的一些基准配置文件,以及适用于某些非美国国家的多个配置文件,等等。

获取 Ubuntu 缺失的配置文件

我们已经看到,Ubuntu 的 Ubuntu 仓库中没有 SCAP 配置文件。那么,Ubuntu 用户是否就此失去了希望?绝对没有。幸运的是,您可以在 Fedora Server 虚拟机上安装的scap-security-guide包附带了多种其他 Linux 发行版的 SCAP 配置文件,包括最新版本的 Ubuntu。因此,您在 Ubuntu 上设置 OpenSCAP 的最佳方法是创建一个 Fedora Server 虚拟机,按照您刚刚在 AlmaLinux 上所做的方式安装scap-security-guide包,然后将 Fedora 的/usr/share/xml/scap/ssg/content/目录中的相应配置文件复制到您的 Ubuntu 机器上。之后,您就可以继续了。

扫描系统

在这一部分,我们将处理我们的 AlmaLinux 9 虚拟机。

这个过程适用于大多数 Linux 发行版,唯一不同的是配置文件的名称会有所不同。

现在,假设我们需要确保系统符合支付卡行业标准。首先,我们将扫描 AlmaLinux 9 机器,看看需要修复什么。(请注意,以下命令非常长,并在打印页面上换行。)

sudo oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_pci-dss --fetch-remote --results scan-xccdf-results.xml /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml

正如我们喜欢做的那样,让我们来逐步分析:

  • xccdf eval可扩展配置检查描述格式(Extensible Configuration Checklist Description Format)是我们可以用来编写安全配置规则的语言之一。我们将使用以这种语言编写的配置文件来执行系统评估。

  • --profile xccdf_org.ssgproject.content_profile_pci-dss:在这里,我指定了要使用支付卡行业数据安全标准(PCI-DSS)配置文件来评估系统。(配置文件名称来自配置文件中的Id行。)

  • --fetch-remote:使用此选项来获取附加规则。(请注意,如果您的系统加密策略设置为FUTURE模式,则此选项将无法使用。)

  • --results scan-xccdf-results.xml:我将扫描结果保存到这个.xml格式的文件中。扫描完成后,我将从这个文件生成报告。

  • /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml:这是包含xccdf_org.ssgproject.content_profile_pci-dss配置文件的文件。

随着扫描的进行,输出将被发送到屏幕以及指定的输出文件。这是一个很长的项目列表,因此我只展示其中的一部分。以下是几个看起来还可以的项目:

Title   Ensure Software Patches Installed
Rule    xccdf_org.ssgproject.content_rule_security_patches_up_to_date
OVAL Definition ID  oval:org.almalinux.alsa:def:20227967
OVAL Definition Title   ALSA-2022:7967: qemu-kvm security, bug fix, and enhancement update (Moderate)
Result  pass
Title   Ensure Software Patches Installed
Rule    xccdf_org.ssgproject.content_rule_security_patches_up_to_date
OVAL Definition ID  oval:org.almalinux.alsa:def:20227959
OVAL Definition Title   ALSA-2022:7959: guestfs-tools security, bug fix, and enhancement update (Low)
Result  pass

以下是几个需要修复的项目:

Title   Ensure PAM Displays Last Logon/Access Notification
Rule    xccdf_org.ssgproject.content_rule_display_login_attempts
Result  fail
Title   Limit Password Reuse
Rule    xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember
Result  fail
Title   Lock Accounts After Failed Password Attempts
Rule    xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny
Result  fail
Title   Set Lockout Time for Failed Password Attempts
Rule    xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time
Result  fail

所以,我们安装了一些针对特定安全漏洞的修补程序,这很好。不过,似乎我们的密码策略还有些问题。

现在,我已经运行了扫描并创建了包含结果的输出文件,我可以开始构建我的报告:

sudo oscap xccdf generate report scan-xccdf-results.xml > scan-xccdf-results.html

这从 .xml 格式的文件中提取信息,这些文件不适合人类阅读,并将其转换为 .html 文件,你可以在网页浏览器中打开它。(顺便说一下,报告上说有 49 个问题需要修复。)

修复系统

所以,我们有 49 个问题需要修复,才能使我们的系统符合支付卡行业标准。让我们看看 oscap 能为我们修复其中多少个问题:

sudo oscap xccdf eval --remediate --profile xccdf_org.ssgproject.content_profile_pci-dss --fetch-remote --results scan-xccdf-results.xml /usr/share/xml/scap/ssg/content/ssg-almalinux9-ds.xml

这是我用来执行初步扫描的相同命令,只是我添加了 --remediate 选项,并将结果保存到不同的文件中。当你运行这个命令时,可能需要一点耐心,因为修复某些问题需要下载和安装软件包。事实上,甚至在我输入这些文字时,oscap 正在忙着下载和安装缺失的 AIDE 入侵检测系统软件包。

好的,以下是一些已经修复的问题:

Title   Verify and Correct File Permissions with RPM
Rule    xccdf_org.ssgproject.content_rule_rpm_verify_permissions
Result  fixed
Title   Install AIDE
Rule    xccdf_org.ssgproject.content_rule_package_aide_installed
Result  fixed
Title   Build and Test AIDE Database
Rule    xccdf_org.ssgproject.content_rule_aide_build_database
Result  fixed
Title   Configure Periodic Execution of AIDE
Rule    xccdf_org.ssgproject.content_rule_aide_periodic_cron_checking

结果已修复

有几个错误是因为 oscap 无法修复的,但这是正常的。至少你知道这些问题,这样你可以尝试自己修复它们。

现在,看看这个。你还记得在 第三章,保护用户账户 中,我让你费尽心思确保用户拥有强密码并定期过期吗?好吧,通过应用这个 OpenSCAP 配置文件,你可以自动修复这些问题。以下是一些已修复的项目:

Title   Lock Accounts After Failed Password Attempts
Rule    xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny
Result  fixed
Title   Set Lockout Time for Failed Password Attempts
Rule    xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time
Result  fixed
Title   Ensure PAM Enforces Password Requirements - Minimum Digit Characters
Rule    xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit
Result  fixed
Title   Ensure PAM Enforces Password Requirements - Minimum Lowercase Characters
Rule    xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit
Result  fixed

所以,是的,OpenSCAP 非常棒,甚至命令行工具也不难使用。不过,如果你需要使用图形界面,我们也有一个工具可以使用,接下来我们会看一下。

使用 SCAP Workbench

对于安装了桌面环境的机器,我们有 SCAP Workbench。不过,如果你曾经使用过该工具的早期版本,你可能会感到非常失望。事实上,早期版本的 Workbench 糟糕到根本无法使用。幸运的是,情况现在已经有所改善。现在,Workbench 变成了一个相当不错的小工具。

要获取 SCAP Workbench,只需使用适当的安装命令。在 CentOS 7 上,执行以下操作:

sudo yum install scap-workbench

在 AlmaLinux 8 或 AlmaLinux 9 上,执行以下操作:

sudo dnf install scap-workbench

在 Ubuntu 上,执行以下操作:

sudo apt install scap-workbench

是的,软件包名称是 scap-workbench,而不是 openscap-workbench。我不知道为什么,但我知道如果你搜索 openscap 软件包,是永远找不到它的。

安装完成后,你将在 活动 页面上的 显示应用程序 部分看到它的菜单项:

图 12.2:Gnome 3 桌面上的 SCAP Workbench

图 12.2:Gnome 3 桌面上的 SCAP Workbench

当你第一次打开程序时,你会认为系统会要求你输入 root 或 sudo 密码。但实际上并没有。我们稍后看看这是否会影响我们。

在打开的屏幕上,你首先会看到一个下拉列表,供你选择要加载的内容类型。我将选择AlmaLinux9,然后点击加载内容按钮:

图 12.3:选择要加载的内容

图 12.3:选择要加载的内容

接下来,你会看到顶部面板,你可以选择所需的配置文件。你还可以选择自定义配置文件,并决定是否在本地机器或远程机器上运行扫描。在底部面板中,你会看到该配置文件的规则列表。你可以展开每一项规则,以查看该规则的描述:

图 12.4:查看规则,并生成修复角色

图 12.4:查看规则,并生成修复角色

在这个屏幕的底部,你会看到一些很酷的选项。点击生成修复角色按钮,你可以选择创建一个 Puppet 清单、一个 Ansible 剧本,或者一个 Bash shell 脚本,然后将它分发并应用到你网络中的其他 AlmaLinux 9 机器上。你还可以选择获取远程资源修复

现在,点击那个扫描按钮,看看会发生什么:

图 12.5:输入你的密码

图 12.5:输入你的密码

很棒!正如我所希望的,它会提示你输入 sudo 密码。除此之外,剩下的交给你去尝试。这只是另一个图形界面工具,应该比较容易上手。

接下来,我们将看看如何选择一个合适的 OpenSCAP 配置文件。

选择一个 OpenSCAP 配置文件

所以,现在你可能在想,好的,这些都不错,但我怎么知道这些配置文件里有什么,以及我需要哪个? 其实,有几种方式可以解决这个问题。

我刚刚展示的第一种方式,是在拥有桌面界面的机器上安装 SCAP Workbench,并阅读每个配置文件中所有规则的描述。

第二种方式,可能稍微简单一点,就是访问 OpenSCAP 网站,查看他们那里的文档。

你可以在www.open-scap.org/security-policies/choosing-policy/上找到关于可用 OpenSCAP 配置文件的信息。

至于如何知道选择哪个配置文件,有几个方面需要考虑:

  • 如果你在金融行业工作,或者在从事在线金融交易的公司工作,那么可以选择 pci-dss 配置文件。

  • 如果你在政府机构工作,特别是美国政府,那么可以根据具体机构的要求选择 stig 配置文件或 nispom 配置文件。

  • 如果这两种情况都不适用于你,那么你需要做一些研究和规划,找出哪些内容确实需要锁定。浏览每个配置文件中的规则,并阅读 OpenSCAP 网站上的文档,帮助你决定需要什么。

使用 Red Hat 及其衍生版本,你甚至可以在安装操作系统时应用一个策略。接下来我们将介绍这个内容。

在系统安装过程中应用 OpenSCAP 配置文件

我非常喜欢 Red Hat 团队的一点是,他们完全理解安全问题。是的,我们可以锁定其他发行版并使其更加安全,正如我们已经看到的那样。但是使用 Red Hat 发行版时,这就简单多了。很多情况下,Red Hat 类型的发行版的维护者已经设置了安全的默认选项,而其他发行版的默认设置并不安全。(例如,在 Ubuntu 22.04 发布之前,Red Hat 发行版是唯一一个默认锁定用户主目录的发行版。)对于其他一些事情,Red Hat 类型的发行版带有工具和安装选项,帮助忙碌的安全管理员更加轻松地工作。

当你安装一个 Red Hat 类型的发行版时,你将有机会在操作系统安装过程中应用 OpenSCAP 配置文件。在这个 AlmaLinux 9 安装器的界面上,你可以看到在屏幕右下角有一个选择安全配置文件的选项:

图 12.6:在安装过程中选择 SCAP 配置文件

图 12.6:在安装过程中选择 SCAP 配置文件

你只需要点击它,然后选择你的配置文件:

图:12.7:选择 PCI-DSS 配置文件

图:12.7:选择 PCI-DSS 配置文件

好的,这差不多就是我们关于 OpenSCAP 讨论的总结了。唯一需要补充的是,尽管 OpenSCAP 非常强大,但它并不能做所有事情。例如,一些安全标准要求你将某些目录(如 /home//var/)放在独立的分区上。OpenSCAP 扫描会提醒你如果情况不是这样,但它无法更改你现有的分区方案。因此,对于这类问题,你需要从制定安全要求的管理机构那里获取检查清单,并在使用 OpenSCAP 之前做一些高级操作。

总结

在这一章中,我们涵盖了很多内容,也看到了一些非常酷的东西。我们从查看几个杀毒扫描器开始,以便防止任何访问我们 Linux 服务器的 Windows 机器被感染。在使用 Rootkit Hunter 扫描 rootkit 部分,我们学习了如何扫描那些讨厌的 rootkit。我们还看到了一些快速技术来检查潜在的恶意文件。了解如何审计系统非常重要,尤其是在高安全性环境中,我们也看到了如何做到这一点。最后,我们以讨论如何使用 OpenSCAP 加固系统来结束。

在下一章,我们将讨论日志记录和日志文件安全。我在那里见!

问题

  1. 以下关于 rootkit 哪项是正确的?

    1. 它们只会感染 Windows 操作系统。

    2. 植入 rootkit 的目的是获得系统的 root 权限。

    3. 入侵者必须已经获得 root 权限,才能植入 rootkit。

    4. Rootkit 不会造成很大的危害。

  2. 你会使用以下哪种方法来保持 maldet 更新?

    1. 手动创建一个每天运行的 cron 任务。

    2. 什么都不做,因为 maldet 会自动更新自己。

    3. 每天运行操作系统的常规更新命令。

    4. 从命令行运行 maldet update 工具。

  3. 关于 auditd 服务,以下哪项是正确的?

    1. 在 Ubuntu 系统上,你需要使用 service 命令停止或重启它。

    2. 在 Red Hat 类型的系统上,你需要使用 service 命令停止或重启它。

    3. 在 Ubuntu 系统上,它已经预先安装了。

    4. 在 Red Hat 类型的系统上,你需要自己安装它。

  4. 你需要创建一个审计规则,每次某个特定人读取或创建文件时都会发出警告。你会在该规则中使用哪个 syscalls?

    1. openfile

    2. fileread

    3. openat

    4. fileopen

  5. auditd 服务使用哪个文件来记录审计事件?

    1. /var/log/messages

    2. /var/log/syslog

    3. /var/log/auditd/audit

    4. /var/log/audit/audit.log

  6. 你需要为 auditd 创建自定义审计规则。你会将新规则放在哪里?

    1. /usr/share/audit-version_number/

    2. /etc/audit/

    3. /etc/audit.d/rules/

    4. /etc/audit/rules.d/

  7. 你正在为银行的客户门户设置 Web 服务器。你可能应用以下哪个 SCAP 配置文件?

    1. STIG

    2. NISPOM

    3. PCI-DSS

    4. Sarbanes-Oxley

  8. 关于 OpenSCAP,以下哪项是正确的?

    1. 它不能自动修复所有问题,所以在设置服务器之前,你需要做一些预先规划并使用检查清单。

    2. 它可以自动修复系统中的每个问题。

    3. 它仅适用于 Red Hat 类型的发行版。

    4. Ubuntu 提供了更好的 SCAP 配置文件选择。

  9. 以下哪个命令可用于生成用户认证报告?

    1. sudo ausearch -au

    2. sudo aureport -au

    3. 定义审计规则,然后执行 sudo ausearch -au

    4. 定义审计规则,然后执行 sudo aureport -au

  10. 以下哪组 Rootkit Hunter 选项可以使 rootkit 扫描每天自动运行?

    1. -c

    2. -c --rwo

    3. --rwo

    4. -c --cronjob --rwo

进一步阅读

答案

  1. c

  2. b

  3. b

  4. c

  5. d

  6. d

  7. c

  8. a

  9. b

  10. d

第十二章:13 日志记录与日志安全

加入我们的书籍社区,参与 Discord 讨论

packt.link/SecNet

系统日志是每个 IT 管理员生活中的重要部分。它们可以告诉你系统的性能如何,如何排除故障,以及用户(无论是授权用户还是未授权用户)在系统上做了什么。

在本章中,我将带你简要了解 Linux 日志系统,然后展示一个很酷的技巧,帮助你更轻松地审查日志。接下来,我将向你展示如何设置一个远程日志服务器,并为客户端提供 传输层安全性 (TLS)-加密连接。

我们将要讨论的主题包括:

  • 理解 Linux 系统日志文件

  • 理解 rsyslog

  • 理解 journald

  • 使用 Logwatch 让事情变得更简单

  • 设置远程日志服务器

本章的重点是讨论那些已经内置在你的 Linux 发行版中或可以从发行版仓库中获得的日志工具。其他 Packt Publishing 书籍,例如 Adam K. Dean 的 Linux 管理食谱,会向你展示一些更高级、更复杂的第三方日志聚合和分析工具。

所以,如果你已经准备好并充满干劲,让我们来看看那些 Linux 日志文件。

理解 Linux 系统日志文件

你可以在 /var/log/ 目录中找到 Linux 日志文件。Linux 日志文件的结构在所有 Linux 发行版中基本相同。但为了保持 Linux 传统的困惑,各个发行版的主要日志文件名称不同。在 Red Hat 系统中,主要日志文件是 messages 文件,认证相关事件的日志是 secure 文件。在 Debian/Ubuntu 系统中,主要日志文件是 syslog 文件,认证日志是 auth.log 文件。你还会看到其他一些日志文件,包括:

  • /var/log/kern.log:在 Debian/Ubuntu 系统中,这个日志包含有关 Linux 内核运行情况的消息。正如我们在 第四章:用防火墙保护你的服务器 - 第一部分第五章:用防火墙保护你的服务器 - 第二部分 中所看到的,这包括有关 Linux 防火墙运行情况的消息。所以,如果你想查看是否有任何可疑的网络数据包被阻止,这就是你需要查看的地方。Red Hat 系统没有这个文件。相反,Red Hat 系统会将其内核消息发送到 messages 文件中。

  • /var/log/wtmp/var/run/utmp:这两个文件本质上执行相同的功能。它们都记录登录到系统的用户信息。主要区别在于,wtmp 保存来自 utmp 的历史数据。与大多数 Linux 日志文件不同,这些文件采用二进制格式,而非普通的文本模式格式。utmp 文件是我们将要查看的唯一不在 /var/log/ 目录下的文件。

  • /var/log/btmp:这个二进制文件包含失败登录尝试的信息。我们在第三章 安全保护普通用户账户中看过的pam_tally2模块使用了该文件中的信息。

  • /var/log/lastlog:这个二进制文件包含用户上次登录系统的时间信息。

  • /var/log/audit/audit.log:这个文本模式文件记录了来自 auditd 守护进程的信息。我们在第十二章 扫描、加固与审计中已经讨论过它,所以在这里就不再详细讲解。

还有许多其他日志文件包含关于应用程序和系统启动的信息。但我在这里列出的日志文件是我们在查看系统安全时最关心的主要文件。

现在我们已经看过了有哪些日志文件,让我们更详细地了解它们。

系统日志和认证日志

无论你是在讨论 Debian/Ubuntu 上的syslogauth.log文件,还是在 RHEL/CentOS/AlmaLinux 上的messagessecure文件,这些文件在不同系统中是相同的,只是名字不同。系统日志文件和认证日志文件有相同的基本结构,都是纯文本文件。这使得我们可以使用 Linux 内置的工具方便地搜索特定信息。其实我们使用哪个虚拟机并不重要,主要是确保文件名的一致性。

首先,让我们来看一下系统日志中的一条简单信息:

Jul  1 18:16:12 localhost systemd[1]: Started Postfix Mail Transport Agent.

下面是具体内容:

  • Jul 1 18:16:12:这是消息生成的日期和时间。

  • localhost:这是生成信息的机器的主机名。这很重要,因为一台 Linux 机器可以作为其他 Linux 机器的中央日志存储库。默认情况下,其他机器的消息会被直接丢到本地机器使用的同一个日志文件中。因此,我们需要这个字段来了解每台机器上发生了什么。

  • systemd[1]:这是生成该消息的服务。在这个例子中,是systemd守护进程。

  • 这一行的其余部分是具体的消息内容。

提取文本模式日志文件信息有多种方式。目前,我们仅仅在less中打开文件,如这个示例所示:

sudo less syslog

然后,要搜索特定的文本字符串,按下/键,输入你想查找的字符串,然后按回车。

那么,我们可以在这些文件中找到哪些与安全相关的信息呢?首先,让我们看一下服务器私钥的权限:

donnie@orangepione:/etc/ssh$ ls -l
total 580
. . .
-rw-------+ 1 root root   1679 Feb 10  2019 ssh_host_rsa_key
-rw-r--r--  1 root root    398 Feb 10  2019 ssh_host_rsa_key.pub
donnie@orangepione:/etc/ssh$

这个私钥,ssh_host_rsa_key文件,必须仅对 root 用户设置权限。但是,权限设置末尾的+符号表示该文件上设置了访问控制列表ACL)。getfacl命令会显示具体的情况:

donnie@orangepione:/etc/ssh$ getfacl ssh_host_rsa_key
# file: ssh_host_rsa_key
# owner: root
# group: root
user::rw-
user:sshdnoroot:r--
group::---
mask::r--
other::---
donnie@orangepione:/etc/ssh$

所以,有人创建了 sshdnoroot 用户,并为其分配了对服务器私有 SSH 密钥的读取权限。现在,如果我尝试重启 OpenSSH 守护进程,它将失败。查看系统日志——在此案例中是 syslog 文件——将告诉我原因:

Mar 13 12:47:46 localhost sshd[1952]: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Mar 13 12:47:46 localhost sshd[1952]: @ WARNING: UNPROTECTED PRIVATE KEY FILE! @
Mar 13 12:47:46 localhost sshd[1952]: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Mar 13 12:47:46 localhost sshd[1952]: Permissions 0640 for '/etc/ssh/ssh_host_rsa_key' are too open.
Mar 13 12:47:46 localhost sshd[1952]: It is required that your private key files are NOT accessible by others.
Mar 13 12:47:46 localhost sshd[1952]: This private key will be ignored.
Mar 13 12:47:46 localhost sshd[1952]: key_load_private: bad permissions
Mar 13 12:47:46 localhost sshd[1952]: Could not load host key: /etc/ssh/ssh_host_rsa_key

所以,如果除了 root 用户之外的其他人对服务器的私钥有任何访问权限,SSH 守护进程就无法启动。但这是怎么发生的呢?让我们在认证文件中搜索——在这个例子中是 auth.log——看看是否能找到线索:

Mar 13 12:42:54 localhost sudo:   donnie : TTY=tty1 ; PWD=/etc/ssh ; USER=root ; COMMAND=/usr/bin/setfacl -m u:sshdnoroot:r ssh_host_ecdsa_key ssh_host_ed25519_key ssh_host_rsa_key

啊,原来是那个 donnie 做的这件事。什么?这太离谱了!立刻解雇那家伙!等等,那是我自己。再想想,还是不要解雇他了。不过说真的,这显示了强制用户使用 sudo 而不是允许他们从 root shell 做任何事的价值。如果我从 root shell 执行这些操作,认证日志只会显示我作为 root 用户登录的地方,但不会显示我以 root 用户身份做了什么。而使用 sudo,每个 root 权限的操作都会被记录下来,且会显示执行操作的人。

有几种方法可以从日志文件中获取特定信息,包括:

  • 使用前面提到的 less 工具的搜索功能

  • 使用 grep 来搜索一个或多个文件中的文本字符串

  • 用诸如 bash、Python 或 awk 等语言编写脚本

这是使用 grep 的一个示例:

sudo grep 'fail' syslog

在这种情况下,我正在搜索 syslog 文件中包含文本字符串 fail 的所有行。默认情况下,grep 是区分大小写的,因此此命令不会找到任何包含大写字母的 fail。此外,默认情况下,grep 会找到嵌入在其他文本字符串中的文本。因此,除了找到 fail,这个命令还会找到 failedfailure 或任何包含 fail 的其他文本字符串。

若要使搜索不区分大小写,请添加 -i 选项,如下所示:

sudo grep -i 'fail' syslog

这将找到所有形式的 fail,无论是大写还是小写字母。若只搜索 fail 这个文本字符串,并排除它嵌入在其他文本字符串中的情况,请使用 -w 选项,如下所示:

sudo grep -w 'fail' syslog

你可以像这样结合两个选项:

sudo grep -iw 'fail' syslog

通常,如果你不确定自己在寻找什么,可以先从一个更通用的搜索开始,这可能会显示过多信息。然后逐步缩小范围,直到找到你想要的内容。

现在,当你只想在日志文件中搜索特定信息时,以上方法是有效的。但如果你需要进行日常日志审查,这就有些繁琐了。稍后我将向你展示一款可以简化这项工作的工具。现在,让我们来看看二进制日志文件。

utmpwtmpbtmplastlog 文件

与系统日志文件和认证日志文件不同,所有这些文件都是二进制文件。所以,我们不能使用常规的文本工具,如 lessgrep,来读取它们或从中提取信息。相反,我们将使用一些特殊的工具来读取这些二进制文件。

wwho命令从/var/run/utmp文件中提取有关已登录用户及其活动的信息。这两个命令都有自己的选项切换,但您可能永远不会需要它们。如果只想查看当前已登录用户的列表,请像这样使用who

donnie@orangepione:/var/log$ who
donnie   tty7         2019-08-02 18:18 (:0)
donnie   pts/1        2019-11-21 16:21 (192.168.0.251)
donnie   pts/2        2019-11-21 17:01 (192.168.0.251)
katelyn  pts/3        2019-11-21 18:15 (192.168.0.11)
lionel   pts/4        2019-11-21 18:21 (192.168.0.15)
donnie@orangepione:/var/log$

它显示我有三个不同的登录。tty7行是我的本地终端会话,pts/1pts/2行是我从192.168.0.251机器的两个远程 SSH 会话登录的。凯特琳和莱昂纳德从另外两台机器上远程登录。

w命令不仅显示谁登录了,还显示他们在做什么:

donnie@orangepione:/var/log$ w
 18:29:42 up 2:09, 5 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
donnie tty7 :0 02Aug19 111days 6.28s 0.05s /bin/sh /etc/xdg/xfce4/xinitrc -- /etc/X11/xinit/xserverrc
donnie pts/1 192.168.0.251 16:21 4.00s 2.88s 0.05s w
donnie pts/2 192.168.0.251 17:01 7:10 0.81s 0.81s -bash
katelyn pts/3 192.168.0.11 18:15 7:41 0.64s 0.30s vim somefile.txt
lionel pts/4 192.168.0.15 18:21 8:06 0.76s 0.30s sshd: lionel [priv] 
donnie@orangepione:/var/log$

这显示了五个用户,但实际上只有三个,因为它将我每个登录会话都视为一个单独的用户。在我的第一个登录的FROM列下的:0表示这个登录在机器的本地控制台。/bin/sh部分显示我有一个终端窗口打开,/etc/xdg/xfce4/xinitrc -- /etc/X11/xinit/xserverrc部分表示机器处于图形模式,使用 XFCE 桌面。pts/1行显示我在那个窗口中运行了w命令,而pts/2行显示我在那个窗口中什么都没做,除了打开了 bash shell。

下一步,我们看到凯特琳正在编辑一个文件。所以,我认为她一切都好。但是看看莱昂纳德。他行中的[priv]表明他正在执行某种特权操作。要查看这个操作是什么,我们将窥探认证文件,我们在那里看到了这个:

Nov 21 18:21:42 localhost sudo:   lionel : TTY=pts/4 ; PWD=/home/lionel ; USER=root ; COMMAND=/usr/sbin/visudo

哦,来吧。什么傻瓜给了莱昂纳德使用visudo的特权?我是说,我们知道莱昂纳德不应该有那个特权。好吧,我们可以调查一下。在认证文件的更上方,我们看到了这个:

Nov 21 18:17:53 localhost sudo:   donnie : TTY=pts/2 ; PWD=/home/donnie ; USER=root ; COMMAND=/usr/sbin/visudo

这显示了那个donnie角色打开了visudo,但没有显示他对其进行了什么编辑。但由于这一行紧随donnie创建莱昂纳德账户的行之后,并且没有其他用户使用visudo,可以有把握地说donnie是给了莱昂纳德那个visudo特权的人。因此,我们可以推断,那个donnie角色是一个真正该被解雇的失败者。哦,等等。那不是我吗?好吧,不管了。

在正常使用中,last命令从/var/log/wtmp文件中提取信息,该文件归档了来自/var/run/utmp文件的历史数据。如果没有任何选项切换,last会显示每个用户的登录或注销时间,以及机器的启动时间:

donnie@orangepione:/var/log$ last
lionel   pts/4        192.168.0.15     Thu Nov 21 18:21   still logged in
lionel   pts/4        192.168.0.15     Thu Nov 21 18:17 - 18:17  (00:00)
katelyn  pts/3        192.168.0.11     Thu Nov 21 18:15   still logged in
katelyn  pts/3        192.168.0.251    Thu Nov 21 18:02 - 18:15  (00:12)
donnie   pts/2        192.168.0.251    Thu Nov 21 17:01   still logged in
donnie   pts/1        192.168.0.251    Thu Nov 21 16:21   still logged in
donnie   tty7         :0               Fri Aug  2 18:18    gone - no logout
reboot   system boot  4.19.57-sunxi    Wed Dec 31 19:00   still running
. . .
wtmp begins Wed Dec 31 19:00:03 1969
donnie@orangepione:/var/log$

要显示失败的登录尝试列表,请使用-f选项读取/var/log/btmp文件。关键在于,这需要sudo权限,因为我们通常希望保持有关失败登录的信息机密:

donnie@orangepione:/var/log$ sudo last -f /var/log/btmp
[sudo] password for donnie: 
katelyn ssh:notty 192.168.0.251 Thu Nov 21 17:57 gone - no logout
katelyn ssh:notty 192.168.0.251 Thu Nov 21 17:57 - 17:57 (00:00)
katelyn ssh:notty 192.168.0.251 Thu Nov 21 17:57 - 17:57 (00:00)
btmp begins Thu Nov 21 17:57:35 2019
donnie@orangepione:/var/log$

当然,我们可以查看凯特琳在auth.logsecure文件中的三次登录失败,但在这里查看它们更方便和更快。

最后是lastlog命令,它从—你猜到了—/var/log/lastlog文件中提取信息。该命令显示了机器上所有用户(甚至系统用户)的登录记录,以及他们最后一次登录的时间:

donnie@orangepione:/var/log$ lastlog
Username         Port     From             Latest
root             tty1                      Tue Mar 12 15:29:09 -0400 2019
. . .
messagebus                                 **Never logged in**
sshd                                       **Never logged in**
donnie           pts/2    192.168.0.251    Thu Nov 21 17:01:03 -0500 2019
sshdnoroot                                 **Never logged in**
. . .
katelyn          pts/3    192.168.0.11     Thu Nov 21 18:15:44 -0500 2019
lionel           pts/4    192.168.0.15     Thu Nov 21 18:21:33 -0500 2019
donnie@orangepione:/var/log$

/var/log/目录下有很多其他日志,但我这里只给你简要介绍与系统安全相关的日志。接下来,我们将看看大多数 Linux 发行版中内置的两个主要日志系统,从rsyslog系统开始。

了解 rsyslog

旧的syslog日志系统创建于 1980 年代,用于 Unix 及其他类 Unix 系统。直到几年前,它才在 Linux 世界中走向终结。如今,我们使用rsyslog,它更加健壮,功能也多了一些。它在基于 Debian/Ubuntu 和基于 Red Hat 的发行版上主要是相同的,只是在配置文件的设置上有所不同。但在看差异之前,让我们先看看相同之处。

了解 rsyslog 日志规则

日志规则定义了将每个特定系统服务的消息记录到哪里:

  • 在 Red Hat/CentOS/AlmaLinux 系统上,规则存储在/etc/rsyslog.conf文件中。只需向下滚动,直到看到#### RULES ####部分。

  • 在 Debian/Ubuntu 系统上,规则存储在/etc/rsyslog.d/目录中的不同文件中。目前我们关心的主要文件是50-default.conf,它包含了主要的日志规则。

为了说明rsyslog规则的结构,我们来看一个来自 AlmaLinux 机器的例子:

authpriv.*           /var/log/secure

以下是详细分类:

  • authpriv:这是设施,定义了消息的类型。

  • .:点号将设施与级别分开,后者是下一个字段。

  • *:这是级别,表示消息的重要性。在这种情况下,我们使用通配符,意味着authpriv设施的所有级别都会被记录。

  • /var/log/secure:这是动作,实际上是该消息的目标位置。(我不知道为什么有人决定称其为动作。)

当我们将这些内容汇总时,可以看到所有级别的authpriv消息会被发送到/var/log/secure文件中。

以下是预定义的rsyslog设施的简要列表:

  • auth:由授权系统生成的消息(如loginsusudo等)

  • authpriv:由授权系统生成的消息,但仅供选定用户读取

  • cron:由cron守护进程生成的消息

  • daemon:所有系统守护进程生成的消息(例如,sshdftpd等)

  • ftpftp生成的消息

  • kern:由 Linux 内核生成的消息

  • lpr:由行打印机排队生成的消息

  • mail:由邮件系统生成的消息

  • mark:系统日志中的定期时间戳消息

  • news:由网络新闻系统生成的消息

  • rsyslogrsyslog内部生成的消息

  • user:由用户生成的消息

  • local0-7:用于编写自定义脚本的自定义消息

以下是不同日志级别的列表:

  • none:禁用某个设施的日志记录

  • debug:仅调试

  • info:信息

  • notice:需要检查的问题

  • warning:警告消息

  • err:错误条件

  • crit:严重条件

  • alert:紧急消息

  • emerg:紧急情况

除了debug级别外,你为某个设施设置的任何级别都会导致该级别及以上(直到emerg)的消息被记录。例如,当你设置info级别时,所有info级别及以上的消息都会被记录。考虑到这一点,让我们看一个更复杂的日志规则示例,这也是来自 AlmaLinux 机器的:

*.info;mail.none;authpriv.none;cron.none   /var/log/messages

这是详细的步骤:

  • *.info:这指的是来自所有设施的info级别及以上的消息。

  • ;:这是一个复合规则。分号将该规则的不同部分彼此分开。

  • mail.none;authpriv.none;cron.none:这些是这个规则的三个例外。来自mailauthprivcron设施的消息不会被发送到/var/log/messages文件中。这三个设施有自己的日志文件规则。(我们之前看到的authpriv规则就是其中之一。)

Ubuntu 机器上的规则与 AlmaLinux 机器上的规则不完全相同。但如果你理解了这些示例,就不会有什么问题去弄懂 Ubuntu 的规则。

如果你对rsyslog.conf文件进行更改或向/etc/rsyslog.d/目录添加任何规则文件,你需要重新启动rsyslog守护进程以读取新的配置。你可以按如下方式进行操作:

[donnie@localhost ~]$ sudo systemctl restart rsyslog
[sudo] password for donnie: 
[donnie@localhost ~]$

现在你已经对rsyslog有了基本了解,接下来我们来看一下journald,它是新来的“新手”。

了解 journald

你会在任何使用systemd生态系统的 Linux 发行版上找到journald日志系统。journald不再将消息发送到文本文件,而是发送到二进制文件。你必须使用journalctl工具来提取信息,而不是使用普通的 Linux 文本文件工具。截止到本文编写时,我还不知道有哪个 Linux 发行版已经完全转向使用journald。目前使用systemd的 Linux 发行版同时运行journaldrsyslog。目前,RHEL 类型系统的默认设置是journald日志文件为临时文件,每次重启机器时都会被删除。(你可以配置journald使其日志文件持久化,但只要我们仍需要保留旧的rsyslog文件,这可能没有太大意义。)在 Ubuntu 上,默认设置是journaldrsyslogd都保持持久化日志文件。

在 RHEL 8/9 类型的发行版中,journald代替了rsyslog,现在实际收集操作系统其他部分的日志消息。但rsyslog仍然存在,收集来自journald的消息并将其发送到传统的rsyslog文本文件中。因此,日志文件管理的方式并没有真正改变。

完全从rsyslog过渡可能还需要几年时间。一个原因是,像 LogStash、Splunk 和 Nagios 这样的第三方日志聚合和分析工具,仍然被设置为读取文本文件,而不是二进制文件。另一个原因是,目前使用journald作为远程中央日志服务器仍处于概念验证阶段,尚未准备好投入生产使用。因此,目前journald还不能替代rsyslog

几年前,Fedora 团队发布了一个只使用journald的版本,省略了rsyslog。太多人对此提出投诉,因此他们不得不在下一个版本的 Fedora 中重新引入rsyslog

要查看完整的journald日志文件,请使用journalctl命令。在 Ubuntu 中,安装操作系统的人已经被添加到adm组,这使得该用户可以在没有 sudo 或 root 权限的情况下使用journalctl。任何后期添加的用户只能查看自己的消息。实际上,Frank 就是这样:

frank@ubuntu4:~$ journalctl
Hint: You are currently not seeing messages from other users and the system.
      Users in groups 'adm', 'systemd-journal' can see all messages.
      Pass -q to turn off this notice.
-- Logs begin at Tue 2019-11-26 17:43:28 UTC, end at Tue 2019-11-26 17:43:28 UTC. --
Nov 26 17:43:28 ubuntu4 systemd[10306]: Listening on GnuPG cryptographic agent and passphrase cache.
Nov 26 17:43:28 ubuntu4 systemd[10306]: Reached target Timers.
Nov 26 17:43:28 ubuntu4 systemd[10306]: Listening on GnuPG cryptographic agent and passphrase cache (restricted).
. . .
. . .
Nov 26 17:43:28 ubuntu4 systemd[10306]: Reached target Basic System.
Nov 26 17:43:28 ubuntu4 systemd[10306]: Reached target Default.
Nov 26 17:43:28 ubuntu4 systemd[10306]: Startup finished in 143ms.
frank@ubuntu4:~$

要查看来自系统或其他用户的消息,这些新用户必须被添加到admsystemd-journal组,或者被授予适当的 sudo 权限。在 RHEL/CentOS/AlmaLinux 中,没有用户会自动加入admsystemd-journal组。因此,最初,只有具有 sudo 权限的用户才能查看journald日志。

无论是journalctl还是sudo journalctl,根据需要,都会自动在less分页器中打开日志。你看到的内容与正常的rsyslog日志文件几乎一样,唯一的例外是:

  • 长行会超出屏幕的右边缘。要查看剩余的行,可以使用右箭头键。

  • 你还会看到颜色编码和高亮显示,使不同类型的消息更加突出。ERROR级别及更高级别的消息显示为红色,而从NOTICE级别到ERROR级别的消息则以粗体字符突出显示。

有许多选项可以以不同的格式显示不同类型的信息。例如,若要仅查看关于 CentOS 或 AlmaLinux 上 SSH 服务的消息,可以使用--unit选项,如下所示:

[donnie@localhost ~]$ sudo journalctl --unit=sshd
-- Logs begin at Tue 2019-11-26 12:00:13 EST, end at Tue 2019-11-26 15:55:19 EST. --
Nov 26 12:00:41 localhost.localdomain systemd[1]: Starting OpenSSH server daemon...
Nov 26 12:00:42 localhost.localdomain sshd[825]: Server listening on 0.0.0.0 port 22.
Nov 26 12:00:42 localhost.localdomain sshd[825]: Server listening on :: port 22.
Nov 26 12:00:42 localhost.localdomain systemd[1]: Started OpenSSH server daemon.
Nov 26 12:22:08 localhost.localdomain sshd[3018]: Accepted password for donnie from 192.168.0.251 port 50797 ssh2
Nov 26 12:22:08 localhost.localdomain sshd[3018]: pam_unix(sshd:session): session opened for user donnie by (uid=0)
Nov 26 13:03:33 localhost.localdomain sshd[4253]: Accepted password for goldie from 192.168.0.251 port 50912 ssh2
Nov 26 13:03:34 localhost.localdomain sshd[4253]: pam_unix(sshd:session): session opened for user goldie by (uid=0)
[donnie@localhost ~]$

你不能使用 grep 工具来处理这些二进制日志,但你可以通过-g选项搜索一个字符串。默认情况下,它是不区分大小写的,即使目标文本字符串嵌入在另一个文本字符串中,它也能找到。在这里,我们看到它找到了文本字符串fail

. . .

除了这些,还有很多其他选项。要查看它们,只需执行:

man journalctl

现在你已经了解了如何使用rsyslogjournald的基本操作,让我们看看一个很棒的工具,它能帮助你减轻日志审核的痛苦。

使用 Logwatch 简化操作

你知道每天审查日志有多重要,但你也知道这有多么令人沮丧,简直宁愿挨一顿狠打。幸运的是,有各种工具可以让这项工作变得更轻松。在正常的 Linux 发行版仓库中,Logwatch 是我最喜欢的工具。

Logwatch 没有第三方日志聚合器那样花哨的功能,但它依然非常不错。每天早晨,你会在你的邮箱账户中收到前一天日志的摘要。根据你的邮件系统配置,你可以将摘要发送到本地机器上的用户账户,或者发送到你可以随时访问的邮箱账户。设置起来非常简单,下面我们通过一个实际操作来演示。

实操实验 – 安装 Logwatch

为了传递消息,Logwatch 要求机器上必须有正在运行的邮件服务器守护进程。根据你在安装操作系统时选择的选项,你可能已经安装了 Postfix 邮件服务器,也可能没有。当 Postfix 被设置为本地服务器时,它会将系统消息发送到 root 用户的本地账户。

若要在本地机器上查看 Logwatch 摘要,你还需要安装一个文本模式的邮件阅读器,如 mutt。

对于这个实验,你可以使用任何一台虚拟机:

  1. 安装 Logwatch、mutt 和 Postfix。(在 Ubuntu 上,安装 Postfix 时选择 local 选项。在 CentOS 或 AlmaLinux 上,local 选项已是默认选项。)在 Ubuntu 上,执行以下操作:
sudo apt install postfix mutt logwatch

对于 CentOS 7,执行以下操作:

sudo yum install postfix mutt logwatch

对于 AlmaLinux,请执行以下操作:

sudo dnf install postfix mutt logwatch
  1. 仅限 Ubuntu,创建一个邮件存储文件到你的用户账户:
sudo touch /var/mail/your_user_name
  1. 打开你喜欢的文本编辑器中的 /etc/aliases 文件。配置它,将 root 用户的邮件转发到你自己的普通账户,在文件底部添加以下行:
root:     your_user_name
  1. 保存文件后,将其中的信息复制到系统可以读取的二进制文件中。你可以通过以下方式实现:
sudo newaliases
  1. 到此为止,你已经完成了 Logwatch 的完整实现,它将以 低级别 的详细信息提供每日日志摘要。要查看默认配置,请查看默认配置文件:
less /usr/share/logwatch/default.conf/logwatch.conf
  1. 要更改配置,编辑 CentOS 和 AlmaLinux 上的 /etc/logwatch/conf/logwatch.conf 文件,或者在 Ubuntu 上创建该文件。通过添加以下行来更改为中等级别的日志详细信息:
Detail = Med

Logwatch 是一个 Python 脚本,每晚按计划运行。因此,不需要重启守护进程来使配置更改生效。

  1. 执行一些操作来生成一些日志条目。你可以通过进行系统更新、安装一些软件包,以及使用 sudo fdisk -l 查看分区配置来实现。

  2. 如果可能的话,让你的虚拟机运行整晚。第二天早上,通过以下方式查看你的日志摘要:

mutt

当提示是否在你的主目录中创建一个 Mail 目录时,按 y 键。

  1. 实验结束。

既然你已经看过了简单的日志审查方法,接下来让我们进入本章的最后一个主题——如何搭建中央日志服务器。

设置远程日志服务器

到目前为止,我们只处理了本地机器上的日志文件。但如果不必登录到每台机器来查看日志文件,而是将每台机器的所有日志文件集中到一台服务器上,岂不是很方便?你完全可以做到,最棒的是,这非常简单。

但方便性并不是将日志文件集中收集到一个服务器上的唯一原因,还有日志文件的安全性问题。如果我们把所有日志文件都保留在每台主机上,网络入侵者就更容易找到这些文件并修改它们,删除有关其恶意活动的任何信息。(这很容易做到,因为大多数日志文件只是普通文本文件,可以在普通文本编辑器中编辑。)

实验操作——设置基本日志服务器

服务器的设置在 Ubuntu、CentOS 和 AlmaLinux 中是完全相同的。只是设置客户端时有一个小差异。为了获得最佳效果,请确保服务器虚拟机和客户端虚拟机具有不同的主机名:

  1. 在日志收集服务器虚拟机上,打开/etc/rsyslog.conf文件,并使用你喜欢的文本编辑器查找这些行,它们通常位于文件的顶部:
# Provides TCP syslog reception
#module(load="imtcp") # needs to be done just once
#input(type="imtcp" port="514")
  1. 取消注释底部的两行并保存文件。此时该段应该如下所示:
# Provides TCP syslog reception
module(load="imtcp") # needs to be done just once
input(type="imtcp" port="514")
  1. 重启rsyslog守护进程:
sudo systemctl restart rsyslog
  1. 如果机器上有启用防火墙,打开514/tcp端口。

  2. 接下来,配置客户端机器。对于 Ubuntu,在/etc/rsyslog.conf文件底部添加以下行,并替换为你自己服务器虚拟机的 IP 地址:

@@192.168.0.161:514

对于 CentOS 和 AlmaLinux,查找/etc/rsyslog.conf文件底部的这一段:

# ### sample forwarding rule ###
#action(type="omfwd"  
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
#queue.filename="fwdRule1"       # unique name prefix for spool files
#queue.maxdiskspace="1g"         # 1gb space limit (use as much as possible)
#queue.saveonshutdown="on"       # save messages to disk on shutdown
#queue.type="LinkedList"         # run asynchronously
#action.resumeRetryCount="-1"    # infinite retries if host is down
# Remote Logging (we use TCP for reliable delivery)
# remote_host is: name/ip, e.g. 192.168.0.1, port optional e.g. 10514
#Target="remote_host" Port="XXX" Protocol="tcp"

删除每行不是显然为注释的注释符号。然后添加日志服务器虚拟机的 IP 地址和端口号。完成后应该如下所示:

# ### sample forwarding rule ###
action(type="omfwd"
# An on-disk queue is created for this action. If the remote host is
# down, messages are spooled to disk and sent when it is up again.
queue.filename="fwdRule1"       # unique name prefix for spool files
queue.maxdiskspace="1g"         # 1gb space limit (use as much as possible)
queue.saveonshutdown="on"       # save messages to disk on shutdown
queue.type="LinkedList"         # run asynchronously
action.resumeRetryCount="-1"    # infinite retries if host is down
# Remote Logging (we use TCP for reliable delivery)
# remote_host is: name/ip, e.g. 192.168.0.1, port optional e.g. 10514
Target="192.168.0.161" Port="514" Protocol="tcp")
  1. 保存文件后,重启rsyslog守护进程。

  2. 在服务器虚拟机上,验证来自服务器虚拟机和客户端虚拟机的消息是否已成功发送到日志文件中。(你可以通过不同的主机名来区分不同的消息。)

  3. 这就是实验的结束部分。

尽管这样很酷,但这个设置仍然存在一些缺点。其中之一是我们正在使用一个未加密的明文连接将日志文件发送到服务器。让我们来解决这个问题。

创建与日志服务器的加密连接

我们将使用stunnel包来创建加密连接。这很简单,只是 Ubuntu 和 AlmaLinux 的操作步骤不同。这些差异如下:

  • 对于 AlmaLinux 8/9,FIPS 模块可以免费使用,正如我在第六章加密技术中向你展示的那样。CentOS 7 不支持 FIPS 模块,而 Ubuntu 只有在购买支持合同的情况下才能使用。因此,目前唯一能利用stunnel中的 FIPS 模式的方法是将其配置在 AlmaLinux 8/9 或其他 RHEL 8/9 克隆版本上。

  • 在 AlmaLinux 上,stunnel作为systemd服务运行。而在 Ubuntu 上,由于某些奇怪的原因,它仍然使用老式的init脚本。所以,我们不得不处理两种不同的方式来控制stunnel守护进程。

我们从 AlmaLinux 的操作开始。

在 AlmaLinux 9 上创建 stunnel 连接——服务器端

对于本实验,我们使用的是一台已设置为 FIPS 合规模式的 AlmaLinux 9 虚拟机(查看第六章加密技术中的步骤):

  1. 在 AlmaLinux 虚拟机上,安装stunnel
sudo dnf install stunnel
  1. 在服务器上,在/etc/stunnel/目录内,创建一个新的stunnel.conf文件,内容如下:
cert=/etc/stunnel/stunnel.pem
fips=yes
[hear from client]
accept=30000
connect=127.0.0.1:6514
  1. 在服务器上,仍然在/etc/stunnel/目录中,创建stunnel.pem证书文件:
sudo openssl req -new -x509 -days 3650 -nodes -out stunnel.pem -keyout stunnel.pem
  1. 在服务器上,打开防火墙上的端口30000,并关闭端口514
sudo firewall-cmd --permanent --add-port=30000/tcp
sudo firewall-cmd --permanent --remove-port=514/tcp
sudo firewall-cmd --reload

端口6514,你在stunnel.conf文件中看到的端口,仅用于rsyslogstunnel之间的内部通信。所以,对于这一点,我们不需要打开防火墙端口。我们将stunnel配置为在rsyslog的代表下监听端口30000,因此不再需要在防火墙上开放端口514

  1. 通过以下方式启用并启动stunnel守护进程:
sudo systemctl enable --now stunnel
  1. /etc/rsyslog.conf文件中,查找文件顶部的这一行:
input(type="imtcp" port="514")

将其改为如下:

input(type="imtcp" port="6514")
  1. 保存文件后,重启rsyslog
sudo systemctl restart rsyslog
  1. 现在服务器已经准备好通过加密连接接收来自远程客户端的日志文件。

接下来,我们将配置 AlmaLinux 虚拟机将其日志发送到此服务器。

在 AlmaLinux 上创建 stunnel 连接——客户端

在这个步骤中,我们将配置一台 AlmaLinux 机器将其日志发送到日志服务器(无论日志服务器是运行在 CentOS、AlmaLinux 还是 Ubuntu 上,都没关系):

  1. 安装stunnel
sudo dnf install stunnel
  1. /etc/stunnel/目录中,创建stunnel.conf文件,内容如下:
client=yes
fips=yes
[speak to server]
accept=127.0.0.1:6514
connect=192.168.0.161:30000

connect行中,将这里看到的日志服务器的 IP 地址替换为你自己日志服务器的 IP 地址。

  1. 启用并启动stunnel守护进程:
sudo systemctl enable --now stunnel
  1. /etc/rsyslog.conf文件的底部,查找这一行:
Target="192.168.0.161" Port="514" Protocol="tcp")

将其改为如下:

Target="127.0.0.1" Port="6514" Protocol="tcp")
  1. 保存文件后,重启rsyslog守护进程:
sudo systemctl restart rsyslog
  1. 在客户端,使用logger将消息发送到日志文件:
logger "This is a test of the stunnel setup."
  1. 在服务器上,验证消息是否已添加到/var/log/messages文件中。

  2. 本实验到此结束。

现在让我们转向 Ubuntu。

在 Ubuntu 上创建 stunnel 连接——服务器端

为此,我们将使用 Ubuntu 22.04 虚拟机。我不明白为什么,但 Ubuntu 仍然使用旧式的init脚本来运行stunnel,而不是使用systemd服务。所以,你将使用的命令与平时的有所不同。

  1. 安装stunnel
sudo apt install stunnel
  1. /etc/stunnel/目录中,创建stunnel.conf文件,内容如下:
cert=/etc/stunnel/stunnel.pem
fips=no
[hear from client]
accept=30000
connect=6514
  1. /etc/stunnel/目录中,仍然创建stunnel.pem证书:
sudo openssl req -new -x509 -days 3650 -nodes -out stunnel.pem -keyout stunnel.pem
  1. 启动stunnel守护进程:
sudo service stunnel4 start
  1. 为了在系统重启时自动启动该守护进程,创建一个 root 用户的定时任务。首先,打开 crontab 编辑器,方法如下:
sudo crontab -e -u root

将此行添加到文件的末尾:

@reboot service stunnel4 start
  1. /etc/rsyslog.conf文件的顶部,查找这一行:
input(type="imtcp" port="514")

将其修改为如下:

input(type="imtcp" port="6514")
  1. 保存文件后,重新启动rsyslog守护进程:
sudo systemctl restart rsyslog
  1. 使用适当的iptablesufwnftables命令,打开防火墙上的30000/tcp端口,并关闭514端口。

  2. 这就是实验的结束。

接下来,我们将配置客户端。

在 Ubuntu 上创建 stunnel 连接 – 客户端配置

使用此程序,在 Ubuntu 客户端上操作将允许它将日志文件发送到 AlmaLinux 或 Ubuntu 日志服务器:

  1. 安装stunnel
sudo apt install stunnel
  1. /etc/stunnel/目录下,创建stunnel.conf文件,内容如下:
client=yes
fips=no
[speak to server]
accept = 127.0.0.1:6514
connect=192.168.0.161:30000

请注意,尽管我们无法在 Ubuntu 客户端上使用 FIPS 模式,但仍然可以将日志文件发送到配置为使用 FIPS 模式的 AlmaLinux 日志服务器。(所以,确实可以混用不同的系统。)

  1. 启动stunnel守护进程:
sudo service stunnel4 start
  1. 为了在系统重启时自动启动该守护进程,创建一个定时任务。通过以下方法打开 crontab 编辑器:
sudo crontab -e -u root

将此行添加到文件末尾:

@reboot service stunnel4 start
  1. /etc/rsyslog.conf文件的底部,查找包含日志服务器 IP 地址的行。将其更改为如下:
@@127.0.0.1:6514
  1. 保存文件后,重新启动rsyslog守护进程:
sudo systemctl restart rsyslog
  1. 使用logger命令将消息发送到日志服务器:
 logger "This is a test of the stunnel connection."
  1. 在服务器上,验证消息是否出现在/var/log/messages/var/log/syslog文件中,具体取决于情况。

  2. 实验结束。

好了,现在我们已经建立了一个安全的连接,这很好。但是,来自所有客户端的消息仍然混杂在服务器的日志文件中。我们来解决这个问题。

将客户端消息分隔到各自的文件中

这也是非常简单的操作。我们只需对日志服务器上的rsyslog规则做几个简单的编辑,然后重启rsyslog守护进程。为了演示,我将使用 AlmaLinux 9 虚拟机。

重要

如果你实现了这个技巧,就无法使用 Logwatch 了。嗯,实际上你是可以使用的,只不过 Logwatch 会将所有客户端文件中的事件合并成一个大摘要。所以,你无法看到哪些客户端机器生成了哪些事件。

/etc/rsyslog.conf文件的 RULES 部分,查找以下这一行:

*.info;mail.none;authpriv.none;cron.none   /var/log/messages

将其修改为如下:

*.info;mail.none;authpriv.none;cron.none ?Rmessages

在该行上方,插入以下行:

$template Rmessages,"/var/log/%HOSTNAME%/messages"

auth消息进行同样的操作:

# authpriv.* /var/log/secure
$template Rauth,"/var/log/%HOSTNAME%/sec
auth.*,authpriv.* ?Rauth

最后,重启rsyslog

sudo systemctl restart rsyslog

查看/var/log/目录,你会看到每个客户端的目录,这些客户端正在向该服务器发送日志。相当酷,对吧?

提示

这里的技巧是:始终让$template位于受影响规则之前。

本章到此结束。你现在知道如何查看日志文件,如何使日志审查更加轻松,并且了解如何设置一个安全的远程日志服务器。

总结

在本章中,我们介绍了不同类型的日志文件,重点是包含与安全相关信息的文件。接着,我们看了 rsyslogjournald 日志系统的基本操作。为了简化日志审查,我们介绍了 Logwatch,它会自动创建前一天日志文件的摘要。最后,我们通过设置一个集中式的远程日志服务器,收集来自其他网络主机的日志文件。

在下一章中,我们将探讨如何进行漏洞扫描和入侵检测。下次见。

问题

  1. 以下哪两个是记录身份验证相关事件的日志文件?

    1. syslog

    2. authentication.log

    3. auth.log

    4. secure.log

    5. secure

  2. 哪个日志文件包含有关当前登录系统的用户及其操作的记录?

    1. /var/log/syslog

    2. /var/log/utmp

    3. /var/log/btmp

    4. /var/run/utmp

  3. 以下哪一个是几乎每个现代 Linux 发行版上都运行的主要日志系统?

    1. syslog

    2. rsyslog

    3. journald

    4. syslog-ng

  4. 以下哪个是 RHEL 8/9 及其衍生版本(如 AlmaLinux 8/9)特有的?

    1. 在 RHEL 8/9 系统上,journald 从系统的其他部分收集日志数据,并将其发送到 rsyslog

    2. 在 RHEL 8/9 系统上,journald 已完全取代了 rsyslog

    3. 在 RHEL 8/9 系统上,rsyslog 从系统的其他部分收集数据,并将其发送到 journald

    4. RHEL 8/9 系统使用 syslog-ng

  5. 在设置 stunnel 时,以下哪项是需要考虑的?

    1. 在 AlmaLinux 系统上,无法使用 FIPS 模式。

    2. 在 Ubuntu 系统上,无法使用 FIPS 模式。

    3. 在 Ubuntu 系统上,FIPS 模式是可用的,但前提是购买了支持合同。

    4. 在 AlmaLinux 8/9 系统上,FIPS 模式是可用的,但前提是购买了支持合同。

  6. 以下哪两个关于 stunnel 的说法是正确的?

    1. 在 RHEL 系统上,stunnel 作为普通的 systemd 服务运行。

    2. 在 RHEL 系统上,stunnel 仍然通过旧式的 init 脚本运行。

    3. 在 Ubuntu 系统上,stunnel 作为普通的 systemd 服务运行。

    4. 在 Ubuntu 系统上,stunnel 通过旧式的 init 脚本运行。

  7. 你必须编辑哪个文件才能将 root 用户的消息转发到你自己的用户账户?

  8. 编辑完 问题 7 中提到的文件后,必须运行哪个命令才能将信息传输到系统可读的二进制文件中?

  9. 要为你的远程日志服务器创建一个 stunnel 设置,你必须为服务器和每个客户端都创建一个安全证书。

    1. 正确

    2. 错误

  10. 以下哪个命令可用于查找 journald 日志文件中的 fail 字符串?

    1. sudo grep fail /var/log/journal/messages

    2. sudo journalctl -g fail

    3. sudo journalctl -f fail

    4. sudo less /var/log/journal/messages

深入阅读

回答

  1. c, e

  2. d

  3. b

  4. a

  5. c

  6. a, d

  7. /etc/aliases

  8. sudo newaliases

  9. b

  10. c

第十三章:14 漏洞扫描与入侵检测

加入我们的书籍社区,加入 Discord

packt.link/SecNet

网络上有很多威胁,其中一些可能甚至会突破你的网络防护。你需要知道何时发生这种情况,因此你需要一个好的网络入侵检测系统NIDS)或网络入侵防御系统NIPS)。在这一章中,我们将介绍 Snort,它可能是最著名的一个。然后,我会展示一个窍门,帮助你快速部署一个高效的 NIDS/NIPS 系统。我还会展示如何快速且轻松地设置一个边缘防火墙设备,并且内建 NIPS。

我们已经学会了如何通过在要扫描的机器上安装扫描工具来扫描病毒和根套件。然而,还有许多其他漏洞可以进行扫描,我将展示一些你可以用来扫描的酷工具。

本章将涵盖以下主题:

  • Snort 和 Security Onion 简介

  • IPFire 及其内建的入侵防御系统(IPS)

  • 使用 Lynis 进行扫描和加固

  • 使用 Greenbone Security Assistant 查找漏洞

  • 使用 Nikto 进行 Web 服务器扫描

所以,如果你准备好了,我们就从深入了解 Snort 网络入侵检测系统开始吧。

Snort 和 Security Onion 简介

Snort 是一个网络入侵检测系统NIDS),作为一个免费的开源软件产品提供。程序本身是免费的,但如果你想要一个完整、最新的威胁检测规则集,你需要付费。Snort 最初是一个个人项目,但现在由思科(Cisco)拥有。需要理解的是,这并不是你安装在你想要保护的机器上的程序。相反,你会在网络中的某个地方至少设置一台专门的 Snort 机器,专门监控所有网络流量,观察是否有异常。当它发现有不应该存在的流量时——例如,发现一个机器人程序的迹象——它可以选择只发送警告信息给管理员,或者根据规则配置,它甚至可以阻止这些异常流量。对于小型网络,你可以只用一台 Snort 机器,既作为控制台又作为传感器。对于大型网络,你可以设置一台 Snort 机器作为控制台,接收其他作为传感器的 Snort 机器报告的情况。

Snort 并不难使用,但从零开始设置一个完整的 Snort 解决方案可能会有点繁琐。在了解 Snort 的基本使用方法之后,我会展示如何通过设置一个预构建的 NIPS 设备来大大简化这一过程。

由于篇幅限制,我无法提供关于 Snort 的全面教程。相反,我将提供一个高层次的概述,如果你希望深入了解 Snort,还会推荐其他资源。

此外,你可能会想知道 NIDS 和 NIPS 有什么区别。NIDS 的功能仅限于警告网络管理员它检测到的可疑网络流量,而 NIPS 不仅会警告管理员,还会自动阻止可疑流量。然而,这两种系统的界限有些模糊,因为一些以 NIDS 为名的系统也可以配置成 NIPS 功能。

首先,让我们下载并安装 Snort。

获取和安装 Snort

Snort 3,Snort 的最新版本,并未在任何 Linux 发行版的官方仓库中提供。因此,你需要从 Snort 官网获取它。它曾经以 Windows 或 Linux 安装包的形式提供,但现在已经不再如此。现在,随着 Snort 3 的发布,它可以作为需要你自己编译的源代码或预构建的 Docker 容器提供。奇怪的是,Snort 官方主页上并没有提到容器选项,直到我通过 DuckDuckGo 搜索才找到了它。

你可以从 Snort 官方网站获取 Snort 和 Snort 培训: www.snort.org

实操实验——通过 Docker 容器安装 Snort

你肯定想选择容器选项而不是源代码选项。这是因为设置源代码选项的说明并不如它们应有的那样清晰,且某个特定的库包并不总是能够正确编译。与使用官方的 Docker 软件不同,我将向你展示如何使用 Podman,它是 Red Hat 为 Docker 提供的替代品。Podman 的安全性优于 Docker,且几乎支持所有 Linux 发行版。Podman 已经在你的 AlmaLinux 8 和 9 虚拟机上预安装,但你需要在 Ubuntu 上自行安装。

仅在 Ubuntu 上,安装 podman 软件包:

sudo apt update
sudo apt install podman

仅在 Ubuntu 上,打开 /etc/containers/registries.conf 文件并在文本编辑器中查看。找到以下这一行:

# unqualified-search-registries = ["example.com"]

改为如下:

unqualified-search-registries = ["docker.io"]

下载并启动容器:

podman run --name snort3 -h snort3 -u snorty -w /home/snorty -d -it ciscotalos/snort3 bash

接下来,进入容器以便与 snort 命令交互:

podman exec -it snort3 bash

如果这个命令成功执行,你将会进入 snorty@snort3 命令提示符。

使用这个单词命令验证 Snort 配置:

snort

Snort 需要一组规则来定义它应该分析的潜在问题。付费用户将收到最新的规则集,而非付费用户可以下载大约滞后一个月的规则集。2018 年的旧规则集随 Docker 容器一起提供,因此你可能需要一些稍微更新的版本。你将无法直接将规则集下载到容器中,因此需要将它们下载到虚拟机或主机机器上,然后再转移到容器中。在主机机器上或连接到虚拟机的另一个终端上,下载最新的社区规则集,方法如下:

wget https://www.snort.org/downloads/community/snort3-community-rules.tar.gz

你不能使用 scpsftp 从虚拟机或主机连接到容器,但你可以使用它们从容器连接到虚拟机或主机。所以,在容器内部,使用 sftp 来传输新的规则集文件。你的命令应该类似于这样:

sftp donnie@192.168.0.20
get snort3-community-rules.tar.gz
bye

在容器内,解压规则集文件并将新的规则集转移到其正确的位置:

snort3-community-rules.tar.gz
cd snort3-community-rules
cp snort3-community.rules ~/snort3/etc/rules/

通过检查随示例文件一起提供的 .pcap 文件来进行测试:

snort -q --talos --rule-path snort3/etc/rules/ -r examples/intro/lab2/eternalblue.pcap

请查看 Snort 网站上的教程视频,你可以在这里看到它们:

www.snort.org/resources

完成后,输入 exit 退出容器。要关闭容器,请执行以下操作:

podman kill snort3

实验结束

以下是新版本的 Snort 3 与我在本书早期版本中介绍的旧版本 Snort 2 之间的一些显著差异。

  • 虽然你可以为 Snort 2 安装一些很酷的图形前端,但 Snort 3 没有图形前端。因此,新的 Snort 3 完全是一个命令行模式的程序。

  • Snort 3 可以将其输出文件保存为 .json 格式,这使得集中式日志聚合器可以方便地读取和解析这些文件。

  • Snort 3 的配置文件和规则文件是 .yaml 格式的。

  • Snort 3 的规则语法已经有所简化,使得编写规则变得更加容易。

接下来,让我们看看一个有着另一种入侵检测系统(IDS)内建的酷炫设备。

使用 Security Onion

Security Onion 是一套自由开源软件FOSS)工具,你可以将其安装在自己的本地 Linux 机器上。它也提供了一个预构建的 Linux 发行版镜像,这其实是推荐的安装方式。在本书的早期版本中,我展示了原始版本的 Security Onion,该版本是基于 Xubuntu Linux 构建的。这个版本有一个图形化桌面界面,使用 Snort 2 作为 IDS,并包含了多个 Snort 的图形化前端界面。新的 Security Onion 2 是一个完全不同的版本。它现在基于 CentOS 7 的文本模式安装,并且提供了比原始版本更多的功能。除了作为 IDS/IPS 使用之外,你现在还可以将其用作取证分析器、日志文件聚合器和日志文件分析器。对于日志文件的收集与分析,它包含了 ELK 堆栈。

ELK 代表Elastic SearchLogstashKibana。Logstash 与适当的收集代理一起使用,收集来自你希望监控的网络终端的日志文件。Elastic Search 将日志信息存储在一个可搜索的数据库中。Kibana 是一个基于 web 的图形组件,用于显示收集到的日志信息。

Security Onion 2 现在使用 Suricata 代替了 Snort,Suricata 是 Snort 的替代品。取代 Snort 的图形前端,Security Onion 2 现在使用Security Onion 控制台,这是一个基于 web 的前端。

有几个原因,我不会提供这部分的动手实验。首先,这并没有太大意义,因为你可以在 Security Onion 的 YouTube 频道上找到详细的教程。此外,我提供的任何动手实验很可能在你阅读本书时已经过时了。因为 Security Onion 的下一个版本将于 2023 年某个时候发布,该版本将基于 Rocky Linux 9 而非 CentOS 7。因此,我相信安装和使用流程会与现在有所不同。

你可以从这里下载 Security Onion 2:securityonionsolutions.com/

你可以在这里找到 Security Onion 的 YouTube 频道:www.youtube.com/@security-onion

也提供收费的支持选项、培训选项以及预装 Security Onion 的物理设备。

作为实验的替代,我给你留下一张 Security Onion 控制台的截图:

图 14.1:Security Onion 控制台

图 14.1:Security Onion 控制台

现在,让我们来关注一款非常酷的预构建防火墙设备,它还具有自己的入侵防御系统(IPS)。

IPFire 及其内置的入侵防御系统(IPS)

当我写这本书的原版时,我在 Snort 部分讨论了 IPFire。当时,IPFire 内置了 Snort。这是一个很棒的想法,因为你有了一个边缘防火墙和一个 入侵检测系统IDS),都在一个方便的包中。但在 2019 年夏季,IPFire 团队将 Snort 替换为他们自己的 IPS。因此,我将 IPFire 移到了它自己的章节。

IDS 和 IPS 之间的区别在于,IDS 会通知你问题,但不会阻止它们。而 IPS 则会阻止这些问题。

如果你回想一下我们在第四章《用防火墙保护你的服务器 - 第一部分》和第五章《用防火墙保护你的服务器 - 第二部分》中的防火墙讨论,我完全忽略了讨论如何创建你需要的 网络地址转换NAT)规则,以便设置边缘或网关类型的防火墙。那是因为有几种专门为此目的创建的 Linux 发行版和 BSD 发行版。其中一个这样的发行版就是 IPFire

图 14.2:IPFire 安装器

图 14.2:IPFire 安装器

IPFire 完全免费,并且只需要几分钟就能设置。你只需将它安装在至少有两个网络接口适配器的机器上,并根据你的网络配置进行配置。它是一种代理类型的防火墙,这意味着除了执行正常的防火墙包检查外,它还包括缓存、内容过滤和 NAT 功能。你可以以多种不同的配置方式设置 IPFire:

  • 在有两个网络接口适配器的计算机上,你可以将一个连接到互联网,另一个连接到内部局域网(LAN)。

  • 使用三个网络适配器,你可以将一个连接到互联网,一个连接到内部局域网,一个连接到非军事区DMZ),在这个区域你可以放置面向互联网的服务器。

  • 使用第四个网络适配器,你可以拥有我们刚才提到的所有功能,并且还能为无线网络提供保护。

说了这么多,我们来试试看吧。

实践实验 – 创建一个 IPFire 虚拟机

通常,你不会在虚拟机中运行 IPFire。相反,你会将其安装在至少有两个网络接口的物理机器上。但是,为了让你看到它的样子,先在虚拟机中进行设置是可以的。让我们开始吧:

你可以从他们的网站下载 IPFire: www.ipfire.org/

  1. 创建一个有两个网络接口的虚拟机。将一个设置为桥接模式,另一个保持 NAT 模式。将 IPFire 安装到这个虚拟机中。在设置过程中,选择桥接适配器作为绿色接口,选择 NAT 适配器作为红色接口。

  2. 安装 IPFire 后,你需要使用常规工作站的网页浏览器导航到 IPFire 仪表板。你可以使用以下 URL 进行访问:

https://192.168.0.190:444

  1. (当然,你需要将绿色接口的 IP 地址替换为你自己的地址。)

  2. 防火墙菜单下,你会看到入侵防御的选项。点击它进入这个页面,在这里你可以启用入侵防御。第一步是点击添加提供者按钮,它位于规则集设置部分下方。

    图 14.3: 点击添加提供者按钮

    图 14.3: 点击添加提供者按钮

  3. 在下一页中,选择你想要使用的规则集。保持启用自动更新复选框为启用状态。然后,点击添加按钮:

    图 14.4: 选择规则集

    图 14.4: 选择规则集

  4. 然后,你将看到这个屏幕,在这里你可以选择要启用入侵防御的接口。(选择两个接口。)然后,勾选启用入侵防御系统复选框并点击保存:

图 14.5: 启用 IPS

图 14.5: 启用 IPS

如果一切顺利,你将看到以下输出:

图 14.6: 启用 IPS 后的状态

图 14.6: 启用 IPS 后的状态

  1. 规则集设置部分,点击自定义规则集按钮。在下一页中,点击你想要启用的规则。然后,在屏幕底部点击应用按钮:

    图 14.7: 选择所需的规则

    图 14.7: 选择所需的规则

  2. 通过选择日志/IPS 日志来查看 IPS 的工作情况。(注意,你看到的内容将取决于你启用了哪些规则。即使如此,可能需要一段时间才能看到日志条目。)

    图 14.8:查看 IPS 日志

    图 14.8:查看 IPS 日志

  3. 点击其他菜单项,查看 IPFire 的其他功能。

你已经完成了这个实验——祝贺你!

你刚刚看到了如何轻松设置一个带有独立网络 IPS 的边缘防火墙。接下来,我们来看一些扫描工具。

使用 Lynis 进行扫描与强化

Lynis 是另一款 FOSS 工具,你可以用它来扫描系统中的漏洞和不良的安全配置。它以便携式 shell 脚本的形式提供,不仅可以在 Linux 上使用,也能在多种 Unix 和类似 Unix 的系统上使用。这是一个多功能工具,你可以用它进行合规性审计、漏洞扫描或强化。与大多数漏洞扫描器不同,你需要在要扫描的系统上安装并运行 Lynis。根据 Lynis 的创始人所说,这样可以进行更深入的扫描。

Lynis 扫描工具有免费版,但其扫描功能有限。如果你需要 Lynis 所提供的全部功能,你需要购买企业版许可证。

在 Red Hat/CentOS 上安装 Lynis

Red Hat、CentOS 7 和 AlmaLinux 8/9 用户可以在 EPEL 仓库中找到最新版本的 Lynis。因此,如果你已安装 EPEL,如我在第一章《在虚拟环境中运行 Linux》所展示的那样,安装过程非常简单,只需执行以下命令:

sudo yum install lynis

在 Ubuntu 上安装 Lynis

Ubuntu 在其自身的仓库中提供了 Lynis,但版本稍微滞后于最新版本。如果你能接受使用较旧的版本,安装命令是:

sudo apt install lynis

如果你想要获取 Ubuntu 的最新版本,或者如果你想在那些没有 Lynis 的操作系统上使用 Lynis,你可以从 cisofy.com/downloads/lynis/ 下载 Lynis。

这点很酷,因为一旦你下载了它,你就可以在任何 Linux、Unix 或类似 Unix 的操作系统上使用它。(这甚至包括 macOS,我通过在我的老款 Mac Pro 上运行 macOS High Sierra 进行了验证。)

由于可执行文件仅是一个普通的 shell 脚本,因此无需进行实际安装。你只需要提取归档文件,cd 进入结果目录,并从那里运行 Lynis:

tar xzvf lynis-3.0.8.tar.gz
cd lynis
sudo ./lynis -h

lynis -h 命令会显示帮助屏幕,以及你需要了解的所有 Lynis 命令。

使用 Lynis 进行扫描

无论你要扫描的操作系统是什么,Lynis 命令的使用方法都是一样的。唯一的不同是,如果你从网站下载的归档文件中运行它,你需要 cd 进入 lynis 目录,并在 lynis 命令前加上 ./。(这是因为出于安全原因,你自己的主目录不在路径设置中,无法让 shell 自动找到可执行文件。)

要扫描已安装 Lynis 的系统,执行以下命令:

sudo lynis audit system

要扫描一个你刚下载了归档文件的系统,执行以下命令:

cd lynis
sudo ./lynis audit system

从你的主目录中的 shell 脚本运行 Lynis 会显示这个信息:

donnie@ubuntu:~/lynis$ sudo ./lynis audit system
. . .
[X] Security check failed
    Why do I see this error?
    -------------------------------
    This is a protection mechanism to prevent the root user from executing user created files. The files may be altered, or including malicious pieces of script.
   . . .
[ Press ENTER to continue, or CTRL+C to cancel ]

这并不会造成任何问题,所以你可以直接按回车键继续。如果看到这个信息真的让你很困扰,你可以按提示将 Lynis 文件的所有权更改为 root 用户。现在,我只会按回车键。

以这种方式运行 Lynis 扫描类似于对通用安全配置文件运行 OpenSCAP 扫描。主要的区别是,OpenSCAP 具有自动修复功能,而 Lynis 没有。Lynis 会告诉你它发现了什么,并建议如何修复它认为是问题的部分,但它不会为你修复任何问题。

空间不允许我展示完整的扫描输出,但我可以给你展示一些示例片段:

[+] Boot and services
------------------------------------
  - Service Manager                                           [ systemd ]
  - Checking UEFI boot                                        [ DISABLED ]
  - Checking presence GRUB                                    [ OK ]
  - Checking presence GRUB2                                   [ FOUND ]
    - Checking for password protection                        [ WARNING ]
  - Check running services (systemctl)                        [ DONE ]
        Result: found 21 running services
  - Check enabled services at boot (systemctl)                [ DONE ]
        Result: found 28 enabled services
  - Check startup files (permissions)                         [ OK ]

这个警告信息显示我没有为我的 GRUB2 引导加载程序设置密码保护。这可能是个大问题,也可能不是,因为唯一能利用这一点的方法是获得对机器的物理访问权限。如果这是一台锁在只有少数信任人员才能进入的房间里的服务器,那么我不会担心,除非适用的监管机构要求我这么做。如果它是一台放在开放隔间里的台式机,那么我肯定会修复这个问题。(我们将在第十六章忙碌蜂的安全小贴士与技巧中查看 GRUB 密码保护。)

文件系统部分,我们可以看到一些项目前面有建议标志:

[+] File systems
------------------------------------
  - Checking mount points
    - Checking /home mount point                              [ SUGGESTION ]
    - Checking /tmp mount point                               [ SUGGESTION ]
    - Checking /var mount point                               [ SUGGESTION ]
  - Query swap partitions (fstab)                             [ OK ]
  - Testing swap partitions                                   [ OK ]
  - Testing /proc mount (hidepid)                             [ SUGGESTION ]
  - Checking for old files in /tmp                            [ OK ]
  - Checking /tmp sticky bit                                  [ OK ]
  - ACL support root file system                              [ ENABLED ]
  - Mount options of /                                        [ NON DEFAULT ]

正如 Lynis 所建议的那样,这将出现在输出的最后部分:

. . .
. . .
  * To decrease the impact of a full /home file system, place /home on a separated partition [FILE-6310]
      https://cisofy.com/controls/FILE-6310/
  * To decrease the impact of a full /tmp file system, place /tmp on a separated partition [FILE-6310]
      https://cisofy.com/controls/FILE-6310/
  * To decrease the impact of a full /var file system, place /var on a separated partition [FILE-6310]
      https://cisofy.com/controls/FILE-6310/
. . .
. . .

我们最后要看的部分是输出结尾的扫描详细信息:

 Lynis security scan details:
  Hardening index : 67 [#############       ]
  Tests performed : 218
  Plugins enabled : 0
  Components:
  - Firewall               [V]
  - Malware scanner        [X]
  Lynis Modules:
  - Compliance Status      [?]
  - Security Audit         [V]
  - Vulnerability Scan     [V]
  Files:
  - Test and debug information      : /var/log/lynis.log
  - Report data                     : /var/log/lynis-report.dat

对于组件,在恶意软件扫描器旁有一个红色的X。这是因为我没有在这台机器上安装 ClamAV 或 maldet,因此 Lynis 无法进行病毒扫描。

对于Lynis 模块,我们在合规状态旁看到一个问号。这是因为这个功能是为 Lynis 企业版预留的,企业版需要付费订阅。正如我们在前一章中看到的,你可以使用 OpenSCAP 配置文件使系统符合多个不同的安全标准,而这不需要花费任何费用。使用 Lynis,你需要为合规性配置文件付费,但你可以选择更多的配置文件。例如,Lynis 企业版可以扫描 Sarbanes-Oxley 合规性问题,而 OpenSCAP 做不到。

我想说的最后一件事是关于 Lynis 企业版的。在以下截图中,这来自他们的网站,你可以看到当前的定价和不同订阅计划之间的差异:

图 14.9:Lynis 企业版定价

图 14.9:Lynis 企业版定价

如你所见,你有选择的余地。

你可以在 Cisofy 网站上找到定价信息:cisofy.com/pricing/

这基本上总结了我们对 Lynis 的讨论。接下来,我们将介绍一个外部漏洞扫描器。

使用 Greenbone 安全助手查找漏洞

在本书的前几个版本中,我提到过 OpenVAS,代表开放漏洞评估扫描器。它仍然存在,但其发布者已将其名称更改为Greenbone 安全助手GSA)。尽管它是商业产品,但 Greenbone 也提供免费的开源社区版,完全免费。

Greenbone 安全助手是您用来执行远程漏洞扫描的工具。您可以使用它扫描各种网络设备。

三大安全发行版是 Kali Linux、Parrot Linux 和 Black Arch。它们主要面向安全研究人员和渗透测试人员,但它们也包含一些适用于普通 Linux 或 Windows 安全管理员的工具。GSA 就是其中之一。这三大安全发行版各有优缺点。由于 Kali 最为流行,我们将在演示中使用它。

您可以从www.kali.org/get-kali/下载 Kali Linux。

当您访问 Kali 下载页面时,会看到许多选择。您可以下载适用于x86x86_64和 Apple Silicon 的常规安装镜像。其他选项包括:

  • 用于 ARM 设备的镜像,如树莓派

  • 预构建的虚拟机镜像,适用于 VMWare、VirtualBox 和 QEMU

  • 预构建的 Docker 容器

  • 移动设备的镜像

  • Windows 子系统 Linux

Kali 是基于 Debian Linux 构建的,因此安装和更新它与安装和更新 Debian 几乎相同。

Greenbone 安全助手是一个相当占用内存的程序,因此如果您在虚拟机中安装 Kali,请确保至少分配 3GB 内存。

安装 Kali 后,您首先需要做的就是更新它,这与更新任何 Debian/Ubuntu 类型的发行版相同。然后,像这样安装 GSA:

sudo apt update
sudo apt dist-upgrade
sudo apt install openvas

请注意,openvas软件包是一个过渡软件包,它将自动安装所有适当的 Greenbone 软件包。

GSA 安装完成后,您需要运行一个脚本,创建安全证书并下载漏洞数据库:

sudo gvm-setup

这个过程会花费很长时间,所以您不妨去拿个三明治和咖啡,等它运行完。当它最终完成时,您将看到用于登录 GSA 的密码。记下并保存在安全的地方:

图 14.10:复制密码

图 14.10:复制密码

接下来,通过以下操作启动 Greenbone 服务:

sudo gvm-check-setup

为确保一切正常运行,您需要手动同步数据流,然后重启 GVA 服务:

sudo greenbone-feed-sync --type GVMD_DATA
sudo greenbone-feed-sync --type SCAP
sudo greenbone-feed-sync --type CERT
sudo gvm-stop

等待 30 秒,然后重启服务:

sudo gvm-start

服务启动完成后,打开 Firefox 并访问 localhost:9392。你会收到一个安全警告,因为 GVA 使用的是自签名的安全证书,但没关系。只需点击高级按钮,然后点击添加例外

在登录页面,输入admin作为用户名,然后输入由gvm-setup脚本生成的密码。

图 14.11:GVA 登录界面

图 14.11:GVA 登录界面

有很多花里胡哨的功能可以通过 GVA 实现,但目前我们只看如何进行基础的漏洞扫描。首先,从 GVA 仪表盘的扫描菜单中选择任务

图 14.12:选择任务

图 14.12:选择任务

当任务页面加载出来后,寻找左上角的小魔法棒。将鼠标指针悬停在这个魔法棒上,你会看到任务向导的各种选项:

图 14.13:任务向导选项

图 14.13:任务向导选项

目前,我们只选择任务向导选项,它将为我们选择所有默认的扫描设置。你需要做的唯一事情就是输入你想扫描的机器的 IP 地址,然后开始扫描:

图 14.14:开始基础扫描

图 14.14:开始基础扫描

扫描需要一些时间,所以你不妨去喝杯饮料:

图 14.15:执行基础扫描

图 14.15:执行基础扫描

你正在进行的扫描类型名为全面与快速,这是目前提供的最全面的扫描类型。要选择其他扫描类型并配置其他扫描选项,请使用高级任务向导,如图所示:

图 14.16:选择扫描选项

图 14.16:选择扫描选项

扫描完成后,点击扫描/结果菜单项:

图 14.17:查看结果

图 14.17:查看结果

为了向你展示一些有趣的内容,我特意选择了一台接近 20 年历史、操作系统过时且存在大量漏洞的目标机器。在这里,你看到这台机器使用了弱加密算法进行 Secure Shell 连接,且被分类为中等严重性问题。更糟糕的是,它还支持 SSH 版本 1,这被分类为高严重性问题。天哪!

图 14.18:扫描结果

图 14.18:扫描结果

你还需要注意那些未标记为漏洞的项目。例如,VNC 安全类型项显示端口 5900 是开放的。这意味着虚拟网络计算VNC)守护进程正在运行,允许用户远程登录到该机器的桌面。如果这台机器是面对互联网的,那将是一个真正的问题,因为 VNC 并不像 Secure Shell 那样有真正的安全性。

点击一个漏洞项,我可以看到该漏洞的解释:

图 14.19:漏洞解释

图 14.19:漏洞解释

请记住,目标机器在这种情况下是一台桌面计算机。如果是服务器,可能会遇到更多问题。

这基本上总结了 Greenbone 安全助手的内容。如前所述,你可以用它做很多非常棒的事情。不过,我展示给你的这些内容应该足以帮助你入门。你可以尝试使用不同的扫描选项,看看结果的不同。

如果你想了解更多关于 Kali Linux 的信息,你可以在 Packt Publishing 网站上找到许多相关的书籍。

好了,你现在知道如何使用 GSA 进行漏洞扫描了。接下来,我们来看看一个专门为 Web 服务器设计的扫描器。

使用 Nikto 进行 Web 服务器扫描

我们刚刚看过的 Greenbone 安全助手是一个通用的漏洞扫描工具。它能够发现大多数操作系统或大多数服务器守护进程的漏洞。不过,正如我们刚才所见,GSA 扫描可能需要较长时间运行,可能也并不是你需要的。

Nikto 是一个特殊用途的工具,只有一个目的。也就是,它专门用于扫描 Web 服务器,仅此而已。它易于安装,易于使用,并且能够快速进行全面的 Web 服务器扫描。

Kali Linux 中的 Nikto

如果你使用 Kali Linux,你会发现 Nikto 已经在漏洞分析菜单下安装好了:

图 14.20:Kali Linux 菜单中的 Nikto

图 14.20:Kali Linux 菜单中的 Nikto

然而,最好的做法是忽略它,而是直接从 GitHub 下载更新的版本。因为你看到的这里,安装在 Kali 上的 Nikto 签名数据库自 2019 年以来就没有更新过:

┌──(kali㉿kali)-[~]
└─$ cd /var/lib/nikto/databases 

┌──(kali㉿kali)-[/var/lib/nikto/databases]
└─$ ls -l
total 1652
-rw-r--r-- 1 root root    2093 Mar  9  2019 db_404_strings
-rw-r--r-- 1 root root    3147 Mar  9  2019 db_content_search
-rw-r--r-- 1 root root   15218 Mar  9  2019 db_dictionary
. . .
. . .
-rw-r--r-- 1 root root    4868 Mar  9  2019 db_variables

┌──(kali㉿kali)-[/var/lib/nikto/databases]
└─$ 

你以前可以使用sudo nikto -update命令来更新数据库,但现在不再有效,因为作者已经弃用了-update选项。(我曾希望通过正常的sudo apt dist-upgrade命令来获取一些更新,但没运气。)现在,作者建议使用git命令来下载和更新 Nikto。那么,让我们来看一下如何操作。

实验室操作——从 Github 安装 Nikto

为了简化操作,我们将在 Kali 上进行这个操作,因为它已经具备了 Nikto 运行所需的所有perl模块。如果你在 Debian 或 Ubuntu 上操作,它也应该能正常工作,但你需要自己去找需要的perl模块。而且,忘记在 AlmaLinux 上进行此操作,因为所需的perl模块甚至不在任何 AlmaLinux 或 EPEL 仓库中。(当然有其他安装方式,但这超出了本书的范围。)

  1. 在你的普通用户主目录中,克隆 Nikto 仓库。然后,cd 进入 nikto 目录,并检出当前分支:
git clone https://github.com/sullo/nikto.git
cd nikto
git checkout nikto-2.5.0
  1. 要运行 Nikto,进入程序子目录,然后从那里调用 Nikto。例如,要查看 Nikto 帮助屏幕,可以这样做:
cd program
./nikto -help
  1. 定期更新 Nikto 签名数据库。只需 cd 进入 nikto 目录并执行:
git pull
  1. 实验结束

接下来,让我们用 Nikto 做一些有用的事情。

使用 Nikto 扫描 Web 服务器

  1. 要进行简单扫描,使用 -h 选项指定目标主机,如下所示:
cd nikto/program
./nikto -h 192.168.0.9
./nikto -h www.example.com
  1. 让我们看一下部分输出。以下是顶部部分:
+ Allowed HTTP Methods: POST, OPTIONS, GET, HEAD
+ OSVDB-396: /_vti_bin/shtml.exe: Attackers may be able to crash FrontPage by requesting a DOS device, like shtml.exe/aux.htm -- a DoS was not attempted.
+ /cgi-bin/guestbook.pl: May allow attackers to execute commands as the web daemon.
+ /cgi-bin/wwwadmin.pl: Administration CGI?
+ /cgi-bin/Count.cgi: This may allow attackers to execute arbitrary commands on the server

在顶部,我们可以看到有一个 shtml.exe 文件,它显然是用于 FrontPage 网页创作程序的。我不明白它为什么在这里,考虑到这是一个 Linux 服务器,而那个是 Windows 可执行文件。Nikto 告诉我,通过存在这个文件,有人可能会对该网站发起拒绝服务DOS)攻击。

接下来,我们可以看到 /cgi-bin/ 目录中有各种脚本。从解释信息中可以看出,这不是一件好事,因为它可能允许攻击者在我的服务器上执行命令。

让我们看看第二部分:

+ OSVDB-28260: /_vti_bin/shtml.exe/_vti_rpc?method=server+version%3a4%2e0%2e2%2e2611: Gives info about server settings.
+ OSVDB-3092: /_vti_bin/_vti_aut/author.exe?method=list+documents%3a3%2e0%2e2%2e1706&service%5fname=&listHiddenDocs=true&listExplorerDocs=true&listRecurse=false&listFiles=true&listFolders=true&listLinkInfo=true&listIncludeParent=true&listDerivedT=false&listBorders=fals: We seem to have authoring access to the FrontPage web.

这里,我们可以看到 vti_bin 目录中有一个 author.exe 文件,这理论上可能允许某人获得创作权限。

现在,看看最后部分:

+ OSVDB-250: /wwwboard/passwd.txt: The wwwboard password file is browsable. Change wwwboard to store this file elsewhere, or upgrade to the latest version.
+ OSVDB-3092: /stats/: This might be interesting...
+ OSVDB-3092: /test.html: This might be interesting...
+ OSVDB-3092: /webstats/: This might be interesting...
+ OSVDB-3092: /cgi-bin/wwwboard.pl: This might be interesting...
+ OSVDB-3233: /_vti_bin/shtml.exe/_vti_rpc: FrontPage may be installed.
+ 6545 items checked: 0 error(s) and 15 item(s) reported on remote host
+ End Time:           2017-12-24 10:54:21 (GMT-5) (678 seconds)

最后一个值得关注的项目是 wwwboard 目录中的 passwd.txt 文件。显然,这个密码文件是可以浏览的,这绝对不是一件好事。

现在,在你指责我编造这些问题之前,我会透露这是对一个实际生产网站进行的扫描,且这个网站是在一个真实的托管服务上。(是的,我确实获得了扫描许可。)所以,这些问题是真实存在的,并且需要修复。

以下是我扫描一个运行 WordPress 的 Web 服务器时获得的其他一些示例消息:

HTTP TRACK method is active, suggesting the host is vulnerable to XST
Cookie wordpress_test_cookie created without the httponly flag

长话短说,这两个问题可能允许攻击者窃取用户凭证。在这种情况下,解决方法是查看 WordPress 官方是否发布了修复此问题的更新。

那么,我们如何保护 Web 服务器免受这些漏洞的攻击呢?让我们来看看:

  • 正如我们在第一个例子中看到的,你需要确保你的 Web 服务器上没有任何风险较大的可执行文件。在这种情况下,我们发现了两个 .exe 文件,它们在我们的 Linux 服务器上可能不会造成任何伤害,因为 Windows 可执行文件无法在 Linux 上运行。另一方面,这可能是一个伪装成 Windows 可执行文件的 Linux 可执行文件。我们还发现了一些 perl 脚本,这些脚本在 Linux 上肯定能运行,可能会带来问题。

  • 如果有人在你的 web 服务器上植入恶意脚本,你会希望有某种强制访问控制措施,比如 SELinux 或 AppArmor,这可以防止恶意脚本访问不应该访问的内容。(有关详细信息,请参阅第十章使用 SELinux 和 AppArmor 实施强制访问控制

  • 你还可以考虑安装 Web 应用防火墙,如 ModSecurity。由于篇幅限制,我无法详细介绍 ModSecurity,但你可以在 Packt 出版公司的网站上找到一本相关书籍。

  • 保持系统更新,尤其是当你使用基于 PHP 的内容管理系统(如 WordPress)时。如果你关注 IT 安全新闻,你会发现关于 WordPress 漏洞的报道比你希望的还要频繁。

我不能透露我扫描的站点的 URL,但你可以从www.vulnhub.com/下载一个易受攻击的虚拟机

选择一个虚拟机进行下载,然后将其导入到 VirtualBox 中。为此,在文件菜单中选择导入设备

你还可以通过在命令行中输入./nikto查看其他扫描选项。不过,目前这些足以让你开始进行基本的 web 服务器扫描。

总结

我们已经达成了旅程中的又一个里程碑,并且看到了很酷的东西。我们从讨论设置 Snort 作为 NIDS 的基础开始。然后,我向你展示了如何通过部署一个已经设置好并准备就绪的专业 Linux 发行版来“作弊”。作为奖励,我还展示了一个快速简单的边缘防火墙设备,它内置了网络入侵防御系统。

接下来,我向你介绍了 Lynis,以及你如何使用它扫描系统中的各种漏洞和合规性问题。最后,我们通过 Greenbone Security Assistant 和 Nikto 的演示做了总结。

在下一章,我们将讨论如何阻止某些应用程序运行。下次见。

问题

  1. 以下哪项最能描述 IPFire?

    1. 一种内置网络入侵检测系统的主机防火墙设备

    2. 一种内置网络入侵检测系统的边缘防火墙设备

  2. 以下哪个工具最适合扫描 Sarbanes-Oxley 合规性问题?

    1. Lynis

    2. Lynis 企业版

    3. Greenbone Security Assistant

    4. OpenSCAP

  3. 以下哪个最能代表 Snort 的功能?

    1. HIDS

    2. GIDS

    3. NIDS

    4. FIDS

  4. 以下哪项工具你会选择作为通用的外部漏洞扫描器?

    1. Greenbone Security Assistant

    2. Nikto

    3. OpenSCAP

    4. Lynis

  5. 以下问题中,使用 Nikto 扫描最有可能发现哪些问题?

    1. Samba 服务正在运行,尽管不应该运行

    2. 根用户账户通过 SSH 暴露在互联网上

    3. 可能存在恶意脚本驻留在 CGI 目录中

    4. 根用户账户配置了一个弱密码

  6. Lynis 的独特特征是什么?

    1. 它是一个专有的、闭源的漏洞扫描器。

    2. 它是一个可以用于扫描任何 Linux、Unix 或类 Unix 操作系统漏洞的 shell 脚本。

    3. 它是一个外部漏洞扫描器。

    4. 它只能安装在特定的安全发行版上,例如 Kali Linux。

  7. 在 Snort 中你最可能发现哪些问题?

    1. 一个密码弱的 root 用户账户

    2. 没有启用防火墙的服务器

    3. 网络上有加密货币挖矿恶意软件活动

    4. 通过 SSH 暴露到互联网上的 root 用户账户

进一步阅读

答案

  1. b

  2. b

  3. c

  4. a

  5. c

  6. b

  7. c

第十四章:15 防止不必要的程序运行

在 Discord 上加入我们的书籍社区

packt.link/SecNet

曾几何时,我们不需要太担心 Linux 恶意软件。虽然 Linux 用户现在仍不需要担心病毒,但还有其他类型的恶意软件确实能让 Linux 用户的日子变得糟糕。种植在你服务器上的加密货币挖矿程序会消耗内存和 CPU 时间,导致服务器比应该的工作更繁重,消耗更多的电力。勒索软件,它可以加密重要文件或系统引导加载程序,使这些重要文件甚至整个系统无法访问。即使支付了勒索款项,也不能保证你的系统会恢复正常。防止这些程序造成损害的一种方法是,只允许授权程序运行,阻止其他所有程序。这是我们本章要讨论的两个方法之一:

  • 使用 no 选项挂载分区

  • 在 Red Hat 类型系统上使用 fapolicyd

所以,如果你准备好了,我们就开始吧。

使用 no 选项挂载分区

第十二章扫描、审计与加固 中,我向你展示了 OpenSCAP 如何自动使你的 Linux 系统符合某些监管机构的安全标准。我还告诉你一个不太方便的真相,那就是 OpenSCAP 无法做到的事情,某些任务需要你自己完成。它无法做的事情之一就是像一些监管机构要求的那样为你的系统驱动器分区。例如,美国政府使用的 安全技术实施指南STIG)要求以下 Linux 系统和数据目录必须挂载到独立的分区上:

  • /var

  • /var/log/

  • /var/tmp/

  • /var/log/audit/

  • /tmp/

  • /home/

  • /boot/

/boot/efi/ (You’ll only have this one if your machine is set up in EFI mode.)

这是因为有两个原因:

  • 如果 Linux 操作系统的根分区 (/) 太满,它可能会导致操作系统完全卡死。将这些目录挂载到独立的分区上可以帮助防止 / 分区被填满。

  • STIGs,以及可能的其他安全规定,要求这些分区挂载时使用选项,以防止在其上运行可执行程序,禁止 SGID 和 SUID 文件权限生效,并防止在其上创建设备文件。

正如我之前提到的,OpenSCAP 不会自动为你设置这种分区方案。所以,你需要在安装操作系统时进行设置。这需要仔细规划,以确保分区大小正确。我是说,你不想通过将某些分区做得太大而浪费空间,也不想在真正需要额外空间的分区上出现空间不足的情况。

在我开始写这一章的几周前,RHEL 9.1 及其所有克隆版本已经发布。你可能已经注意到,在 9.1 的安装程序中存在一个在 9.0 安装程序中没有的 bug。也就是说,创建普通用户账户的选项在安装程序界面上不可见。其实,它是存在的,只是你看不见,也无法向下滚动找到它。要显示这个选项,只需要不断按 Tab 键,直到你高亮显示了创建 root 用户密码的选项。然后,再按一次 Tab 键,再按回车键。(当然,等你读到这本书时,问题可能已经被修复了。)

要设置这个,你需要选择安装程序中的自定义分区方案选项。为了让这个过程稍微真实一些,将虚拟机的虚拟硬盘大小设置为大约 1 TB。(如果你的宿主机硬盘上没有那么多空间也没关系,VirtualBox 会创建一个动态大小的虚拟硬盘,除非你放入 1 TB 的文件,否则不会占用宿主硬盘上 1 TB 的空间。)让我们来看看在 AlmaLinux 9 中是如何显示的:

图 15.1:选择创建自定义分区方案

图 15.1:选择创建自定义分区方案

选择Custom选项后,点击屏幕顶部的Done按钮。在下一个屏幕上,点击+框来创建一个挂载点。请注意,你可以创建标准分区、逻辑卷或精简配置的逻辑卷。(我会选择标准分区。)

图 15.2:创建挂载点

图 15.2:创建挂载点

我们从创建/boot/挂载点开始:

图 15.3:创建第一个挂载点

图 15.3:创建第一个挂载点

创建我在上述列表中提到的其余挂载点。你的完成方案可能如下所示:

图 15.4:创建其余的挂载点

图 15.4:创建其余的挂载点

当然,你的使用场景将决定你为每个分区设置多大的空间。在这里,你可以看到/home/目录最大,这表明我想把这台机器用作 Samba 文件服务器。如果我将这台机器用作其他用途,比如数据库服务器,我会根据需要调整这些分区的大小。

在 RHEL 安装程序中有一个长期存在的上游 bug,这个 bug 同样影响 RHEL 的克隆版本。也就是说,无论每个分区需要多少空间,你必须将每个分区的大小设置为至少 1 GB。否则,安装将失败,并显示error in POSTTRANS scriptlet in rpm package kernel-core的错误信息。这个问题已经存在很长时间了,但仍然没有被修复。(是的,虽然这样浪费了一些磁盘空间,但我们无能为力。)

现在,我们要稍微作弊一下。我们假装正在处理美国政府的要求,必须符合 STIG 规范。所以,在安装界面上,我们点击应用安全配置文件的选项。接下来的界面中,我们滚动到看到 STIG 配置文件的位置,并选择它。在底部,你会看到该配置文件根据需要将noexecnodevnosuid选项添加到分区中。(/var/分区只需要nodev选项,/boot/分区只需要nodevnosuid选项。)

图 15.5: 应用 STIG 配置文件

图 15.5: 应用 STIG 配置文件

以下是这三种挂载选项的作用:

  • noexec:任何挂载了此选项的分区上的可执行文件都不能运行。(这包括可执行的 Shell 脚本,除非你使用sh来调用脚本。稍后我会展示更多关于这方面的内容。)

  • nodev:用户无法在挂载了此选项的分区上创建任何设备文件。

  • nosuid:在挂载了此选项的分区上,添加 SUID 或 SGID 权限对文件没有任何影响。

安装完成后,我们的/etc/fstab文件将会是这样:

UUID=72d0a3b3-cd07-45c0-938e-4e3377750adb /             xfs     defaults        0 0
UUID=7bf3315e-525e-4940-b562-7e0b634d65de /boot       xfs     defaults,nosuid,nodev 0 0
UUID=4df2f723-e875-4194-9ccd-b4a2733fd617 /home       xfs     defaults,noexec,nosuid,nodev 0 0
UUID=d89b01b1-c3ee-48b6-bb40-0311fdd2838a /tmp        xfs     defaults,nodev,noexec,nosuid 0 0
UUID=d79889b0-1635-47d5-950d-8dbca088c464 /var         xfs     defaults,nodev        0 0
UUID=be9a3d41-0e07-4466-8eb7-57fb850df2d4 /var/log     xfs     defaults,nodev,noexec,nosuid 0 0
UUID=ed001588-333b-4027-bef1-754fcc5e868d /var/log/audit     xfs     defaults,nodev,noexec,nosuid 0 0
UUID=05c1b1e9-7f32-4791-9d41-492fce6f5166 /var/tmp     xfs     defaults,nodev,noexec,nosuid 0 0
UUID=c4fdabb8-7e45-4717-b7f5-1cad5f8e7720 none            swap    defaults        0 0
tmpfs /dev/shm tmpfs defaults,relatime,inode64,nodev,noexec,nosuid 0 0

(注意,某些行在打印的页面上可能会换行。)

现在,让我们看看能否从这些目录中的任何一个运行可执行脚本。在我自己的主目录中,我创建了一个如下的 Shell 脚本:

#!/bin/bash
echo "This is a test of the noexec option."
exit

在为自己添加可执行权限后,我尝试运行它。然后,我将其复制到/tmp/目录并再次尝试运行。以下是我得到的结果:

[donnie@localhost ~]$ chmod u+x donnie_script.sh 
[donnie@localhost ~]$ ./donnie_script.sh
-bash: ./donnie_script.sh: Permission denied
[donnie@localhost ~]$ cp donnie_script.sh /tmp
[donnie@localhost ~]$ cd /tmp
[donnie@localhost tmp]$ ./donnie_script.sh
-bash: ./donnie_script.sh: Permission denied
[donnie@localhost tmp]$

所以,我不能运行它,至少作为普通用户不能。但是,如果我用sudo试试呢?让我们看看:

[donnie@localhost tmp]$ sudo ./donnie_script.sh
[sudo] password for donnie: 
sudo: unable to execute ./donnie_script.sh: Permission denied
[donnie@localhost tmp]$

很酷,noexec选项实际上有效。好吧,对于这个例子是有效的。如果我们用sh调用脚本会怎样呢?让我们看看:

[donnie@localhost ~]$ sh ./donnie_script.sh
This is a test of the noexec option.
[donnie@localhost ~]$ cd /tmp
[donnie@localhost tmp]$ sh ./donnie_script.sh
This is a test of the noexec option.
[donnie@localhost tmp]$

所以,对于 Shell 脚本,阻止功能并不完美。让我们看看编译后的可执行文件会发生什么。首先,从这里下载 DERO 加密货币项目的命令行钱包/挖矿程序:

dero.io/download.html#linux

将文件传输到虚拟机并解压:

[donnie@localhost ~]$ tar xzvf dero_linux_amd64.tar.gz 
./dero_linux_amd64/
./dero_linux_amd64/Start.md
./dero_linux_amd64/explorer-linux-amd64
./dero_linux_amd64/simulator-linux-amd64
./dero_linux_amd64/dero-miner-linux-amd64
./dero_linux_amd64/derod-linux-amd64
./dero_linux_amd64/dero-wallet-cli-linux-amd64
[donnie@localhost ~]$

请注意,所有可执行文件的执行权限已经设置好,所以你无需再添加它。

现在,进入关键时刻。进入dero_linux_amd64目录并尝试运行derod-linux-amd64程序:

[donnie@localhost ~]$ cd dero_linux_amd64/
[donnie@localhost dero_linux_amd64]$ ./derod-linux-amd64
-bash: ./derod-linux-amd64: Permission denied
[donnie@localhost dero_linux_amd64]$

由于这是一个已编译的可执行文件,而不是 Shell 脚本,在任何情况下,在命令前加上sh都不会对我们有所帮助。总之,保留好这些 DERO 的内容,因为我们将在下一节中再次使用它。

如果你想知道什么是 DERO,可以把它当作是以太坊的私人版本。你可以在其上构建其他代币并创建智能合约应用,就像在以太坊上做的那样。不同之处在于,DERO 保护你的隐私,而以太坊则没有。

第十二章扫描、审计与加固中,我曾向你展示过,只有 RHEL 类型的发行版在安装操作系统时才会给我们提供应用 SCAP 配置文件的选项。在非 RHEL 的发行版上,你需要在安装完成后应用 SCAP 配置文件,前提是你的发行版提供了适用的配置文件。无论如何,如果你不需要应用整个 SCAP 配置文件,但仍希望将这些安全选项添加到你的分区,或者你的发行版没有可用的 SCAP 配置文件,只需手动编辑/etc/fstab文件即可将它们添加进去。

接下来,我们将看看另一种控制机制,至少目前为止,它是 Red Hat 世界独有的。

理解 fapolicyd

文件访问策略守护进程fapolicyd)是一个相对较新的功能,添加到了 Red Hat Enterprise Linux 及其各种克隆版本中。它是自由软件,任何人都可以使用,但目前为止,Ubuntu 和 SUSE 还没有为它们的发行版提供支持。为了快速了解它是如何工作的,请回到你刚才使用的虚拟机。首先,将整个derod-linux-amd64目录移动到/分区的顶层:

[donnie@localhost ~]$ sudo mv dero_linux_amd64/ /
[sudo] password for donnie: 
[donnie@localhost ~]$ 

通过移动目录而不是复制目录,你的目录和文件的所有权将被保留:

[donnie@localhost /]$ ls -ld dero_linux_amd64/
drwx------. 3 donnie donnie 4096 Jan  2 15:42 dero_linux_amd64/
[donnie@localhost /]$

现在,将你创建的脚本复制到/usr/local/bin/

[donnie@localhost dero_linux_amd64]$ cd
[donnie@localhost ~]$ sudo cp donnie_script.sh /usr/local/bin
[sudo] password for donnie: 
[donnie@localhost ~]$

当你查看这个脚本文件的权限设置时,你会看到一些非常不寻常的地方:

[donnie@localhost ~]$ cd /usr/local/bin
[donnie@localhost bin]$ ls -l
total 16364
-rwx------. 1 root root       61 Dec 31 16:01 donnie_script.sh
[donnie@localhost bin]$

你会看到,执行cp操作时,文件的所有权会自动更改为目标目录的所有者,在这种情况下就是 root 用户。这是正常操作,所以没有什么特别之处。奇怪的是,这个文件的权限设置为700。这与我们的 STIG 配置文件做的另一件事有关。也就是说,STIG 配置文件在这个系统上设置了UMASK077,如我们所见:

[donnie@localhost ~]$ umask
0077
[donnie@localhost ~]$

这意味着,你创建的任何普通文件将仅对所有者具有读写权限,而你创建的任何目录将仅对所有者具有读、写和执行权限。为了让这个演示能够工作,我们需要将权限设置更改为755,像这样:

[donnie@localhost bin]$ sudo chmod 755 donnie_script.sh
[sudo] password for donnie: 
[donnie@localhost bin]$ ls -l
total 16364
-rwxr-xr-x. 1 root root       61 Dec 31 16:01 donnie_script.sh
[donnie@localhost bin]$

很棒,我们现在可以让演示正常工作了。我们将从进入/dero-linux-amd64/目录开始,尝试调用derod-linux-amd64可执行文件:

[donnie@localhost ~]$ cd /dero_linux_amd64/
[donnie@localhost dero_linux_amd64]$ derod-linux-amd64 
-bash: /dero_linux_amd64/derod-linux-amd64: Operation not permitted
[donnie@localhost dero_linux_amd64]$

即使你现在是在一个没有noexec选项挂载的分区中调用这个程序,你仍然不能运行它。这是因为它现在被fapolicyd阻止了。也就是说,尽管目录和可执行文件都属于你,但你仍然无法用普通用户权限运行它。

fapolicyd有一个特殊的行为,我在任何文档中都没看到过,也是在偶然间才发现的。也就是说,只有当普通的非特权用户尝试运行不受信任的程序时,它才会阻止这些程序。但你可以通过正确的sudo权限来运行它们。(这更能说明,只应授予除最信任的管理员外其他用户有限的sudo权限。)

接下来,让我们看看如何使用 Shell 脚本:

[donnie@localhost bin]$ donnie_script.sh 
This is a test of the noexec option.
[donnie@localhost bin]$

那么,为什么我可以在这里调用这个脚本,却不能在我的主目录中调用它呢?原因是,在我的主目录中,noexec挂载选项阻止了脚本的运行。但在/usr/local/bin/目录下,我们没有这个挂载选项。这里只有fapolicyd。我们可以使用fapolicyd-cli -list命令查看当前生效的规则,这也许能解释为什么我能运行这个脚本。(请注意,格式限制使我无法展示完整的输出。)

[donnie@localhost ~]$ sudo fapolicyd-cli -list
[sudo] password for donnie: 
. . .
. . .
11\. deny_audit perm=any all : ftype=%languages
12\. allow perm=any all : ftype=text/x-shellscript
13\. deny_audit perm=execute all : all
14\. allow perm=open all : all
[donnie@localhost ~]$

看看第 12 条规则。该规则允许 Shell 脚本在没有noexec挂载选项的所有分区上运行,即使是非特权用户也可以运行。这是有道理的,因为即使是非特权用户也广泛使用 Shell 脚本来自动化重复性任务。但是,如果你完全确定没有非特权用户需要在系统上运行 Shell 脚本,你始终可以禁用此规则。而且,无论如何,只要你拥有适当的sudo权限,仍然可以运行 Shell 脚本。

说到规则,我们接下来就来看一下它们。

理解 fapolicyd 规则

fapolicyd框架使用/etc/fapolicyd/rules.d/目录中的规则来创建一个允许或拒绝在系统上执行的程序列表。当你安装fapolicyd时,你将获得一组已设置好并且可以立即使用的默认规则。如果你需要允许比默认规则更多的内容,你可以创建自己的自定义规则,或将你想要的程序添加到受信任应用程序列表中。

/etc/fapolicyd/rules.d/目录下,有 11 个规则文件。每个文件都有不同的用途:

[donnie@localhost ~]$ sudo ls -l /etc/fapolicyd/rules.d
[sudo] password for donnie: 
total 44
-rw-r--r--. 1 root fapolicyd 456 Dec 29 14:42 10-languages.rules
-rw-r--r--. 1 root fapolicyd 131 Dec 29 14:42 20-dracut.rules
-rw-r--r--. 1 root fapolicyd 192 Dec 29 14:42 21-updaters.rules
-rw-r--r--. 1 root fapolicyd 225 Dec 29 14:42 30-patterns.rules
-rw-r--r--. 1 root fapolicyd 101 Dec 29 14:42 40-bad-elf.rules
-rw-r--r--. 1 root fapolicyd 248 Dec 29 14:42 41-shared-obj.rules
-rw-r--r--. 1 root fapolicyd  71 Dec 29 14:42 42-trusted-elf.rules
-rw-r--r--. 1 root fapolicyd 143 Dec 29 14:42 70-trusted-lang.rules
-rw-r--r--. 1 root fapolicyd  96 Dec 29 14:42 72-shell.rules
-rw-r--r--. 1 root fapolicyd  76 Dec 29 14:42 90-deny-execute.rules
-rw-r--r--. 1 root fapolicyd  69 Dec 29 14:42 95-allow-open.rules
[donnie@localhost ~]$

文件名开头的数字表示这些规则文件处理的顺序,因为规则处理的顺序非常重要。与其尝试解释这些不同类别的规则对我们做了什么,我更愿意让你打开每个文件并阅读其内容。它们都非常简短,并包括注释来解释每个文件的作用。

虽然你可以为自己的自定义应用程序创建自定义规则,但这并不是推荐的方法。出于性能和安全的考虑,最好只是将你的应用程序添加到受信任列表中,方法如下:

[donnie@localhost ~]$ sudo fapolicyd-cli --file add /dero_linux_amd64/derod-linux-amd64
[sudo] password for donnie: 
[donnie@localhost ~]$

我提到安全性问题是因为当你编写自己的自定义规则时,很容易犯错误,从而锁定整个系统。如果你只是将文件添加到受信任列表中,倒不需要太担心这个问题。

这个命令将所需的文件及其相关的 SHA256 哈希值添加到/etc/fapolicyd/fapolicyd.trust文件中,正如我们在这里看到的:

[donnie@localhost ~]$ sudo cat /etc/fapolicyd/fapolicyd.trust
[sudo] password for donnie: 
# AUTOGENERATED FILE VERSION 2
# This file contains a list of trusted files
#
#  FULL PATH        SIZE                             SHA256
# /home/user/my-ls 157984 61a9960bf7d255a85811f4afcac51067b8f2e4c75e21cf4f2af95319d4ed1b87
/dero_linux_amd64/derod-linux-amd64 16750936 847ea80b83a1df887d245085db60a9b0626aacb6cd4f0f192eb2e982643c5529
[donnie@localhost ~]$

为了使此更改生效,我们需要更新数据库并重启fapolicyd服务,如下所示:

[donnie@localhost ~]$ sudo fapolicyd-cli --update
[sudo] password for donnie: 
Fapolicyd was notified
[donnie@localhost ~]$ sudo systemctl restart fapolicyd
[sudo] password for donnie: 
[donnie@localhost ~]$

现在,当我以普通用户权限调用这个应用程序时,它将正常运行:

[donnie@localhost ~]$ cd /dero_linux_amd64/
[donnie@localhost dero_linux_amd64]$ ./derod-linux-amd64 
02/01 16:13:31  INFO    derod   DERO HE daemon :  It is an alpha version, use it for testing/evaluations purpose only.
02/01 16:13:31  INFO    derod   Copyright 2017-2021 DERO Project. All rights reserved.
02/01 16:13:31  INFO    derod           {"OS": "linux", "ARCH": "amd64", "GOMAXPROCS": 1}
02/01 16:13:31  INFO    derod           {"Version": "3.5.2-114.DEROHE.STARGATE+01102022"}
. . .
. . .

那么,现在你可能会想,是否需要手动将每个新安装的应用程序添加到受信任列表中。好吧,这取决于你如何安装它。如果你像我们在前面示例中那样下载一个已编译的程序,或者自己编译一个程序,那么是的,你需要手动将它添加到受信任列表中。但默认情况下,系统包管理器安装的每个程序都会自动被信任。这意味着,如果你使用dnf从软件仓库安装一个包,或者使用rpm安装一个你下载或创建的rpm包,那么相关的应用程序会自动被信任。

到目前为止,我们已经看了这三个no挂载选项和fapolicyd是如何协同工作、互补的。在这种情况下,挂载选项和fapolicyd都已自动设置,因为我们在安装操作系统时应用了 STIG OpenSCAP 配置文件。我们也可以在没有 STIG 配置文件的情况下安装fapolicyd,这就是我们接下来要看的一部分。

安装 fapolicyd

通常,fapolicyd不会在 AlmaLinux 上自动安装。在这种情况下它已被安装,因为我们应用的 STIG 配置文件要求它以及为我们的分区设置的限制性挂载选项。如果要在未安装fapolicyd的系统上安装它,只需执行以下命令:

[donnie@localhost ~]$ sudo dnf install fapolicyd
. . .
. . .
[donnie@localhost ~]$ sudo systemctl enable --now fapolicyd
Created symlink /etc/systemd/system/multi-user.target.wants/fapolicyd.service → /usr/lib/systemd/system/fapolicyd.service.
[donnie@localhost ~]$

关于fapolicyd,还有一些我没有展示的内容,但我认为你已经看到了足够的内容,能理解它的核心。要获取更多的细节,并了解如何将其用作文件完整性检查器,请务必访问官方的 Red Hat 文档。(链接位于下方的进一步阅读部分。)

noexecnosuidnodev挂载选项添加到分区中效果不错,除了你不能将它们添加到所有分区。显然,不能将它们添加到任何应该包含可执行文件的分区,否则你的系统将无法正常工作。fapolicyd框架为你提供了一种方法,可以防止恶意程序在这些分区上运行,只要恶意入侵者尚未获得根权限。

好了,让我们总结一下。

总结

在本章中,我们介绍了两种防止不信任的程序在系统上运行的方法。第一种方法适用于任何 Linux 发行版,即将各个系统和数据目录分离到独立的分区中,然后用适当的 noexecnosuidnodev 挂载选项挂载每个分区。第二种方法,至今仅在 Red Hat 及其衍生版上可用,是使用 fapolicyd 框架。我们看到如何在安装操作系统时,通过应用 STIG OpenSCAP 配置文件自动启用这两种方法。最后,我们看到如何单独安装 fapolicyd,而不需要应用 STIG 配置文件。

在下一章中,我们将快速回顾一些未能完全归入前几章的主题。我们在那里见。

进一步阅读

RHEL 安装程序中的 bug:forums.rockylinux.org/t/kernel-core-error-at-install/3683

Red Hat 8 的 STIG:www.stigviewer.com/stig/red_hat_enterprise_linux_8/

Linux 勒索病毒:phoenixnap.com/blog/linux-ransomware

Linux 文件访问策略守护进程 (fapolicyd) 视频:youtu.be/txThobi7oqc

Red Hat 官方 fapolicyd 文档:access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/security_hardening/assembly_blocking-and-allowing-applications-using-fapolicyd_security-hardening

问题

  1. 以下哪个说法是正确的?

    1. 你可以在任何 Linux 发行版上使用 noexecnosuidnodev 挂载选项。

    2. 你可以在任何 Linux 发行版上使用 fapolicyd

    3. 你可以通过在 / 分区上使用 noexec 挂载选项来防止恶意程序运行。

    4. 要使用 noexecnosuidnodev 挂载选项,你可以编辑 /etc/mtab 文件。

  2. 你需要运行一个 fapolicyd 通常不会允许的程序。解决此问题的最佳方法是什么?

    1. 通过手动编辑 /etc/fapolicyd/fapolicyd.trust 文件来添加它。

    2. 通过创建自定义规则添加它。

    3. 通过运行 sudo fapolicyd-cli --file add 命令来添加它。

    4. 通过手动编辑 /etc/fapolicyd/fapolicyd.conf 文件来添加它。

  3. 当你应用 STIG OpenSCAP 配置文件时,创建文件和目录时将具有什么权限设置?

    1. 文件权限为 644,目录权限为 755。

    2. 文件权限为 600,目录权限为 700。

    3. 文件权限为 640,目录权限为 750。

    4. 文件权限为 755,目录权限为 755。

  4. 关于应用 STIG OpenSCAP 配置文件,以下哪项是正确的?

    1. 你可以在安装过程中将配置文件应用于任何 Linux 操作系统。

    2. 在安装过程中应用 STIG 配置文件会为你完成所有操作。

    3. 在应用 STIG 配置文件之前,你需要设置一个自定义分区方案,将某些目录分配到它们自己的分区上。

    4. 在 Red Hat 类型的系统上,只有在安装系统之后才能应用 STIG 配置文件。

  5. fapolicyd在其fapolicyd.trust文件中使用哪种类型的哈希值?

    1. SHA1

    2. Blowfish

    3. MD5

    4. SHA256

答案

  1. a

  2. c

  3. b

  4. c

  5. d

第十五章:16 个忙碌蜜蜂的安全小贴士和技巧

加入我们的书籍社区,加入 Discord

packt.link/SecNet

在本章的最后,我想总结一些快捷的小贴士和技巧,这些技巧不一定适合之前的章节。你可以把这些小贴士当作是忙碌管理员的时间节省法宝。首先,你将学习一些快速查看哪些系统服务正在运行的方法,以确保没有不需要的服务在运行。接下来,我们将看看如何为 GRUB 2 启动加载程序设置密码保护,如何安全配置 BIOS/UEFI 来帮助防止对物理可访问机器的攻击,以及如何使用检查清单进行安全的初始系统设置。

在本章中,我们将涵盖以下主题:

  • 审计系统服务

  • 为 GRUB2 配置密码保护

  • 安全配置并设置 UEFI/BIOS 密码保护

  • 在设置系统时使用安全检查清单

如果你准备好了,我们开始吧。

技术要求

本章的代码文件可以在这里找到:github.com/PacktPublishing/Mastering-Linux-Security-and-Hardening-3E

审计系统服务

无论我们谈论的是哪种操作系统,服务器管理的一个基本原则是,永远不要在服务器上安装任何你绝对不需要的东西。你尤其不希望任何不必要的网络服务在运行,因为这会为黑客提供额外的攻击途径。而且,黑客总有可能植入某些恶意程序,充当网络服务,你肯定希望了解这一点。在本节中,我们将看看几种不同的方法来审计你的系统,确保没有不必要的网络服务在运行。

使用 systemctl 审计系统服务

在配备 systemd 的 Linux 系统上,systemctl 命令几乎是一个通用命令,可以为你做很多事情。除了控制系统的服务外,它还可以显示这些服务的状态,例如:

donnie@linux-0ro8:~> sudo systemctl -t service --state=active

以下是前述命令的拆解:

  • -t service:我们希望查看关于服务——或者说以前称为守护进程——的信息。

  • --state=active:这指定我们想查看所有正在运行的系统服务的信息。

这个命令的部分输出大致如下:

UNIT                                                  LOAD   ACTIVE SUB     DESCRIPTION
accounts-daemon.service                               loaded active running Accounts Service
after-local.service                                   loaded active exited  /etc/init.d/after.local Compatibility
alsa-restore.service                                  loaded active exited  Save/Restore Sound Card State
apparmor.service                                      loaded active exited  Load AppArmor profiles
auditd.service                                        loaded active running Security Auditing Service
avahi-daemon.service                                  loaded active running Avahi mDNS/DNS-SD Stack
cron.service                                          loaded active running Command Scheduler
. . .
. . .

通常,你可能不想看到这么多信息,尽管有时你可能会。这个命令显示了系统上每个正在运行的服务的状态。现在我们真正关心的是能够让其他人连接到你系统的网络服务。因此,我们来看看如何将内容缩小一些。

使用 netstat 审计网络服务

你可能想跟踪系统上哪些网络服务正在运行的两个原因如下:

  • 为确保没有任何你不需要的合法网络服务在运行

  • 为确保没有恶意软件在监听来自其主控的网络连接

netstat命令既方便又易于使用。首先,假设你想查看正在监听的网络服务列表,等待有人连接到它们。(由于格式限制,我只能在这里展示部分输出。我们稍后会查看一些我无法在这里显示的行。此外,你可以从 Packt Publishing 网站下载包含完整输出的文本文件。)

donnie@linux-0ro8:~> netstat -lp -A inet
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 *:ideafarm-door *:* LISTEN -
tcp 0 0 localhost:40432 *:* LISTEN 3296/SpiderOakONE
tcp 0 0 *:ssh *:* LISTEN -
tcp 0 0 localhost:ipp *:* LISTEN -
tcp 0 0 localhost:smtp *:* LISTEN -
tcp 0 0 *:db-lsp *:* LISTEN 3246/dropbox
tcp 0 0 *:37468 *:* LISTEN 3296/SpiderOakONE
tcp 0 0 localhost:17600 *:* LISTEN 3246/dropbox
. . .
. . .

下面是详细信息:

  • -lpl表示我们想查看哪些网络端口在监听。换句话说,我们想查看哪些网络端口在等待连接。p表示我们希望看到每个端口上监听的程序或服务的名称和进程 ID 号。

  • -A inet:这意味着我们只想查看属于inet家族的网络协议信息。换句话说,我们只想查看rawtcpudp网络套接字的信息,但我们不想看到任何关于只处理操作系统内部进程间通信的 Unix 套接字的信息。

由于这个输出来自我用来编写本章原始版本的 openSUSE 工作站,所以你不会看到任何通常的服务器类服务。然而,你会看到一些你可能不希望在服务器上看到的东西。例如,来看一下第一个项目:

Proto Recv-Q Send-Q Local Address      Foreign Address         State       PID/Program name
tcp        0      0 *:ideafarm-door    *:*                     LISTEN      -

Local Address列指定了这个监听套接字的本地地址和端口。星号意味着这个套接字在本地网络上,而ideafarm-door是正在监听的网络端口的名称。(默认情况下,netstat会通过从/etc/services文件中提取端口信息,尽可能显示端口的名称。)

现在,由于我不知道ideafarm-door服务是什么,我用了我最喜欢的搜索引擎去查找。通过将ideafarm-door这个词放入 DuckDuckGo,我找到了答案:

图 16.1:WhatPortIs

图 16.1:WhatPortIs

顶部搜索结果将我带到了一个名为WhatPortIs的网站。根据这个网站,ideafarm-door实际上是端口902,它属于VMware Server Console。好的,这个解释有道理,因为我的机器上确实安装了 VMware Player。所以,一切都好。

你可以在这里查看WhatPortIs网站:whatportis.com/

这是列表中的下一个项目:

tcp        0      0 localhost:40432    *:*       LISTEN      3296/SpiderOakONE

这个项目显示本地地址为localhost,监听端口是40432。这次,PID/Program Name列实际告诉我们这是什么。SpiderOak ONE是一个基于云的备份服务,你可能会或可能不会希望它在服务器上运行。

现在,让我们看一下更多的项目:

tcp 0      0 *:db-lsp                   *:*      LISTEN      3246/dropbox
tcp 0      0 *:37468                    *:*      LISTEN      3296/SpiderOakONE
tcp 0      0 localhost:17600            *:*      LISTEN      3246/dropbox
tcp 0      0 localhost:17603            *:*      LISTEN      3246/dropbox

在这里,我们可以看到 dropboxSpiderOakONE 都列出了带有星号的本地地址。因此,它们都在使用本地网络地址。dropbox 的端口名称是 db-lsp,代表Dropbox 局域网同步协议SpiderOakONE 的端口没有正式名称,因此仅列为端口 37468。底部的两行显示 dropbox 也使用本地机器的地址,端口为 1760017603

到目前为止,我们只看到了 TCP 网络套接字。接下来看看它们与 UDP 套接字的区别:

udp        0      0 192.168.204.1:ntp       *:*                                 -
udp        0      0 172.16.249.1:ntp        *:*                                 -
udp        0      0 linux-0ro8:ntp          *:*                                 -

首先需要注意的是,State 列下没有任何内容。这是因为,使用 UDP 时,没有状态。它们实际上是在监听数据包的到来,并准备发送数据包。但由于 UDP 套接字几乎只能执行这些操作,因此实际上没有必要为它们定义不同的状态。

在前两行中,我们看到了一些奇怪的本地地址。这是因为我的工作站上安装了 VMware Player 和 VirtualBox。这两个套接字的本地地址是 VMware 和 VirtualBox 虚拟网络适配器的地址。最后一行显示了我的 OpenSUSE 工作站的主机名作为本地地址。在这三种情况下,端口是网络时间协议端口,用于时间同步。

现在,让我们来看最后一组 UDP 项目:

udp        0      0 *:58102         *:*                                 5598/chromium --pas
udp        0      0 *:db-lsp-disc   *:*                                 3246/dropbox
udp        0      0 *:43782         *:*                                 5598/chromium --pas
udp        0      0 *:36764         *:*                                 
udp        0      0 *:21327         *:*                                 3296/SpiderOakONE
udp        0      0 *:mdns          *:*                                 5598/chromium --pas

在这里,我们看到我的 Chromium 浏览器准备好在几个不同的端口上接收网络数据包。我们还看到 Dropbox 使用 UDP 接收来自其他安装了 Dropbox 的本地机器的发现请求。我猜测端口 21327 对于 SpiderOak ONE 执行相同的功能。

当然,由于这台机器是我的工作站之一,Dropbox 和 SpiderOak ONE 对我来说几乎是必不可少的。我是自己安装的它们,所以我一直知道它们在那里。然而,如果你在服务器上看到类似的东西,你需要调查一下,看看服务器管理员是否知道这些程序已被安装,然后找出它们为何被安装。它们可能执行某些合法功能,也可能没有。

Dropbox 和 SpiderOak ONE 的区别在于,Dropbox 会在文件上传到 Dropbox 服务器后才对其进行加密。因此,Dropbox 的工作人员拥有你的文件的加密密钥。另一方面,SpiderOak ONE 会在本地机器上加密文件,并且加密密钥始终掌握在你自己手中。因此,如果你确实需要一个基于云的备份服务,并且你处理的是敏感文件,像 SpiderOak ONE 这样的服务肯定比 Dropbox 更好。(不,SpiderOak ONE 的人没有付钱让我这么说。)

如果你想查看端口号和 IP 地址,而不是网络名称,请添加 n 选项。和之前一样,这是部分输出:

donnie@linux-0ro8:~> netstat -lpn -A inet
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address      Foreign Address     State       PID/Program name
tcp        0      0 0.0.0.0:902        0.0.0.0:*           LISTEN      -
tcp        0      0 127.0.0.1:40432    0.0.0.0:*           LISTEN      3296/SpiderOakONE
tcp        0      0 0.0.0.0:22         0.0.0.0:*           LISTEN      -
tcp        0      0 127.0.0.1:631      0.0.0.0:*           LISTEN      -
tcp        0      0 127.0.0.1:25       0.0.0.0:*           LISTEN      -
tcp        0      0 0.0.0.0:17500      0.0.0.0:*           LISTEN      3246/dropbox
tcp        0      0 0.0.0.0:37468      0.0.0.0:*           LISTEN      3296/SpiderOakONE
tcp        0      0 127.0.0.1:17600    0.0.0.0:*           LISTEN      3246/dropbox
. . .
. . .

只需要省略l选项就可以查看已建立的 TCP 连接。在我的工作站上,这会生成非常长的列表,因此我只会展示其中的几项:

donnie@linux-0ro8:~> netstat -p -A inet
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address      Foreign Address         State       PID/Program name
tcp        1      0 linux-0ro8:41670   ec2-54-88-208-223:https CLOSE_WAIT  3246/dropbox
tcp        0      0 linux-0ro8:59810   74-126-144-106.wa:https ESTABLISHED 3296/SpiderOakONE
tcp        0      0 linux-0ro8:58712   74-126-144-105.wa:https ESTABLISHED 3296/SpiderOakONE
tcp        0      0 linux-0ro8:caerpc  atl14s78-in-f2.1e:https ESTABLISHED 10098/firefox
. . .
. . .

Foreign Address列显示了连接远端机器的地址和端口号。第一项显示与 Dropbox 服务器的连接处于CLOSE_WAIT状态。这意味着 Dropbox 服务器已经关闭了连接,而我们现在正在等待本地机器关闭套接字。

由于那些远程地址的名称没有多大意义,我们可以添加n选项来查看 IP 地址:

donnie@linux-0ro8:~> netstat -np -A inet
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address         Foreign Address      State        PID/Program name
tcp        0      1 192.168.0.222:59594   37.187.24.170:443    SYN_SENT     10098/firefox
tcp        0      0 192.168.0.222:59810   74.126.144.106:443   ESTABLISHED  3296/SpiderOakONE
tcp        0      0 192.168.0.222:58712   74.126.144.105:443   ESTABLISHED  3296/SpiderOakONE
tcp        0      0 192.168.0.222:38606   34.240.121.144:443   ESTABLISHED  10098/firefox
. . .
. . .

这次,我们看到了一些新内容。第一项显示了 Firefox 连接的SYN_SENT状态。这意味着本地机器正在尝试与远程 IP 地址建立连接。另外,在Local Address下,我们可以看到我 OpenSUSE 工作站的静态 IP 地址。

如果我有足够的空间显示完整的netstat输出,你会发现Proto列下全是tcp。这是因为 UDP 协议不像 TCP 协议那样建立连接。

有一点需要记住的是,rootkit 可能会用它们自己篡改过的版本替换掉合法的 Linux 工具。例如,一个 rootkit 可能有它自己篡改过的netstat版本,显示所有网络进程,除了与 rootkit 相关的进程。这就是为什么你需要尽一切努力防止未授权用户获得 root 权限,以防止他们能够安装 rootkit。

如果你需要更多关于netstat的信息,请查看netstat的手册页。

实操实验——使用 netstat 查看网络服务

在这个实验中,你将实践刚刚学到的关于netstat的内容。在具有桌面界面的虚拟机上执行此操作,这样你就可以使用 Firefox 浏览网站。按照以下步骤进行:

  1. 查看正在监听连接的网络服务列表:
netstat -lp -A inet
netstat -lpn -A inet
  1. 查看已建立连接的列表:
netstat -p -A inet
netstat -pn -A inet
  1. 打开 Firefox 并导航到任何网站。然后,重复步骤 2

  2. 再次重复步骤 2,但在每个命令前加上sudo。注意输出与步骤 2的不同之处。

  3. 从你的主机机器上,通过 SSH 登录到虚拟机。然后,重复步骤 2

你已经完成了实验——恭喜!

你刚刚学习了如何使用netstat审计网络服务。现在,让我们来看看如何使用 Nmap 做这件事。

使用 Nmap 审计网络服务

netstat工具非常好,它可以为你提供大量关于网络服务状态的有用信息。唯一的小缺点是,你必须登录到网络上的每一台主机才能使用它。

如果你想远程审计你的网络,查看每台计算机上运行的服务,而不需要登录每一台计算机,那么你需要一个像 Nmap 这样的工具。它适用于所有主要的操作系统,所以即使你只能在工作站上使用 Windows,也不用担心。Kali Linux 中已经安装了最新版本,如果你正在使用它。它也在所有主要 Linux 发行版的仓库中,因此安装非常简单。如果你使用的是 Windows 或 macOS,你可以直接从 Nmap 官网为它们下载相应的版本。

你可以从nmap.org/download.html下载适用于所有主要操作系统的 Nmap。

在所有情况下,你还会找到安装说明。

你将在所有操作系统上以相同的方式使用 Nmap,唯一的例外是。在 Linux 和 macOS 机器上,你需要在某些 Nmap 命令前加上sudo,而在 Windows 机器上则不需要。(不过,在 Windows 10/11 上,你可能需要以管理员身份打开command.exe终端。)由于我恰好在我的 OpenSUSE 工作站上工作,我将展示如何在 Linux 上使用它。让我们从做一次 SYN 包扫描开始:

donnie@linux-0ro8:~> sudo nmap -sS 192.168.0.37
Starting Nmap 6.47 ( http://nmap.org ) at 2017-12-24 19:32 EST
Nmap scan report for 192.168.0.37
Host is up (0.00016s latency).
Not shown: 996 closed ports
PORT STATE SERVICE
22/tcp open ssh
515/tcp open printer
631/tcp open ipp
5900/tcp open vnc
MAC Address: 00:0A:95:8B:E0:C0 (Apple)
Nmap done: 1 IP address (1 host up) scanned in 57.41 seconds
donnie@linux-0ro8:~>

这里是详细信息:

  • -sS:小写的s表示我们想要执行的扫描类型。大写的S表示我们正在进行 SYN 包扫描。(稍后会详细讲解。)

  • 192.168.0.37:在这个例子中,我只扫描一台机器。然而,我也可以扫描一组机器或者整个网络。

  • Not shown: 996 closed ports:显示所有这些关闭的端口而不是filtered端口,这告诉我这台机器没有防火墙。(稍后会详细讲解。)

接下来,我们会看到一组开放的端口。(稍后会详细讲解。)

这台机器的 MAC 地址表明它是某种苹果产品。稍后我将向你展示如何获得有关它是哪种苹果产品的更多细节。

现在,让我们更详细地看一下。

端口状态

Nmap 扫描会显示目标机器的端口状态,可能是以下三种端口状态之一:

  • filtered:这意味着端口被防火墙阻塞。

  • open:这意味着端口没有被防火墙阻塞,且与该端口相关联的服务正在运行。

  • closed:这意味着端口没有被防火墙阻塞,但与该端口相关联的服务未运行。

因此,在我们对 Apple 机器的扫描中,我们看到安全外壳服务已经准备好在端口 22 上接受连接,打印服务已经准备好在端口 515631 上接受连接,并且虚拟网络计算VNC)服务已经准备好在端口 5900 上接受连接。所有这些端口都对有安全意识的管理员来说非常重要。如果安全外壳服务正在运行,那么了解它是否配置得安全就显得很有趣。打印服务正在运行意味着它已经设置为使用互联网打印协议IPP)。了解为什么我们使用 IPP 而不是普通的网络打印非常重要,同时了解该版本的 IPP 是否存在任何安全隐患也很有意义。当然,我们已经知道 VNC 不是一个安全的协议,因此我们需要了解它为何仍在运行。我们还发现没有端口被列为 filtered,因此我们也想知道为什么这台机器没有防火墙。

我终于要透露一个小秘密了,这台机器和我在 Greenbone Security Assistant 扫描演示中使用的是同一台机器。所以,我们已经获得了一些所需的信息。Greenbone 扫描告诉我们,这台机器上的安全外壳服务使用了弱加密算法,并且打印服务存在安全漏洞。不久之后,我将向你展示如何使用 Nmap 获取其中的一些信息。

扫描类型

有许多不同的扫描选项,每种选项都有其特定的目的。我们在这里使用的 SYN 数据包扫描被认为是一种隐蔽的扫描类型,因为它比其他某些类型的扫描生成的网络流量和系统日志条目要少。在这种扫描类型中,Nmap 向目标机器的一个端口发送 SYN 数据包,就像是试图与该机器建立一个 TCP 连接。如果目标机器响应一个 SYN/ACK 数据包,则意味着该端口处于 open 状态,准备建立 TCP 连接。如果目标机器响应一个 RST 数据包,则意味着该端口处于 closed 状态。如果没有任何响应,则意味着该端口被 filtered,被防火墙阻塞。作为一名普通的 Linux 管理员,这是你大多数时候会使用的一种扫描类型。

-sS 扫描显示了 TCP 端口的状态,但它不会显示 UDP 端口的状态。要查看 UDP 端口,请使用 -sU 选项:

donnie@linux-0ro8:~> sudo nmap -sU 192.168.0.37
Starting Nmap 6.47 ( http://nmap.org ) at 2017-12-28 12:41 EST
Nmap scan report for 192.168.0.37
Host is up (0.00018s latency).
Not shown: 996 closed ports
PORT     STATE         SERVICE
123/udp  open          ntp
631/udp  open|filtered ipp
3283/udp open|filtered netassistant
5353/udp open          zeroconf
MAC Address: 00:0A:95:8B:E0:C0 (Apple)
Nmap done: 1 IP address (1 host up) scanned in 119.91 seconds
donnie@linux-0ro8:~>

在这里,你看到了一些不同的情况:两个端口被列为 open|filtered。这是因为,由于 UDP 端口对 Nmap 扫描的响应方式,Nmap 并不总是能够判断一个 UDP 端口是 open 还是 filtered。在这种情况下,我们知道这两个端口可能是开放的,因为我们已经看到它们对应的 TCP 端口是开放的。

ACK 数据包扫描也很有用,但并不是用来查看目标机器网络服务的状态。相反,它是当你需要查看是否有防火墙在你和目标机器之间阻碍时的一个好选择。一个 ACK 扫描命令如下所示:

sudo nmap -sA 192.168.0.37

你不仅限于一次扫描单台机器。你可以一次扫描一组机器或整个子网:

sudo nmap -sS 192.168.0.1-128
sudo nmap -sS 192.168.0.0/24

第一个命令仅扫描此网络段的前 128 个主机。第二个命令扫描一个使用 24 位子网掩码的子网中的所有 254 个主机。

发现扫描对于你仅仅需要查看网络上有哪些设备时非常有用:

sudo nmap -sn 192.168.0.0/24

使用-sn选项,Nmap 会检测你是在扫描本地子网还是远程子网。如果子网是本地的,Nmap 会发送一个地址解析协议ARP)广播,来请求子网上每台设备的 IPv4 地址。这是一种可靠的设备发现方式,因为 ARP 是不会被设备的防火墙阻止的。(我的意思是,没有 ARP,网络将无法正常运作。)不过,ARP 广播不能跨越路由器,这意味着你不能使用 ARP 来发现远程子网的主机。所以,如果 Nmap 检测到你在进行远程子网的发现扫描,它会发送 ping 数据包,而不是 ARP 广播。使用 ping 数据包进行发现不如使用 ARP 可靠,因为一些网络设备可以被配置为忽略 ping 数据包。无论如何,这是我自己家庭网络中的一个示例:

donnie@linux-0ro8:~> sudo nmap -sn 192.168.0.0/24
Starting Nmap 6.47 ( http://nmap.org ) at 2017-12-25 14:48 EST
Nmap scan report for 192.168.0.1
Host is up (0.00043s latency).
MAC Address: 00:18:01:02:3A:57 (Actiontec Electronics)
Nmap scan report for 192.168.0.3
Host is up (0.0044s latency).
MAC Address: 44:E4:D9:34:34:80 (Cisco Systems)
Nmap scan report for 192.168.0.5
Host is up (0.00026s latency).
MAC Address: 1C:1B:0D:0A:2A:76 (Unknown)
. . .
. . .

我们在这段输出中看到三个主机,并且每个主机有三行输出。第一行显示 IP 地址,第二行显示主机是否处于活动状态,第三行显示主机网络适配器的 MAC 地址。每个 MAC 地址的前三对字符表示该网络适配器的制造商。(顺便说一下,那块未知的网络适配器在最近型号的技嘉主板上。我不知道为什么它没有出现在 Nmap 数据库中。)

我们将要看的最终扫描为我们完成四个任务:

  • 它会识别openclosedfiltered的 TCP 端口。

  • 它识别运行中的服务的版本。

  • 它运行一组随 Nmap 附带的漏洞扫描脚本。

  • 它尝试识别目标主机的操作系统。

执行所有这些操作的扫描命令如下所示:

sudo nmap -A 192.168.0.37

我猜你可以把-A选项看作是所有选项,因为它确实能做到所有事情。(嗯,几乎是所有,毕竟它并不扫描 UDP 端口。)首先,这是我运行的扫描命令:

donnie@linux-0ro8:~> sudo nmap -A 192.168.0.37

这里是结果,已根据格式化需求分成几个部分:

Starting Nmap 6.47 ( http://nmap.org ) at 2017-12-24 19:33 EST
Nmap scan report for 192.168.0.37
Host is up (0.00016s latency).
Not shown: 996 closed ports

我们立刻看到,这台机器没有活跃的防火墙,因为没有端口处于filtered状态。默认情况下,Nmap 只扫描最受欢迎的 1,000 个端口。由于 996 个端口处于closed状态,我们显然只有四个活跃的网络服务会在这 1,000 个端口上监听:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 5.1 (protocol 1.99)
|_ssh-hostkey: ERROR: Script execution failed (use -d to debug)
|_sshv1: Server supports SSHv1
515/tcp open printer?

端口22用于安全外壳(SSH)访问,这是我们通常会期待的。然而,看看 SSH 的版本。版本 5.1 是 OpenSSH 的一个非常旧的版本。(在写作时,当前版本是 9.1。)更糟糕的是,这个 OpenSSH 服务器支持安全外壳协议的版本 1。版本 1 存在严重缺陷,非常容易被利用,所以你绝对不希望在网络中看到这种情况。

接下来,我们看到关于通过 Greenbone Security Assistant 扫描发现的打印服务漏洞的更多信息:

631/tcp open ipp CUPS 1.1
| http-methods: Potentially risky methods: PUT
|_See http://nmap.org/nsedoc/scripts/http-methods.html
| http-robots.txt: 1 disallowed entry
|_/
|_http-title: Common UNIX Printing System

631/tcp行中,我们看到相关服务是ipp。这个协议基于我们用来浏览网页的 HTTP。HTTP 用于从客户端到服务器传输数据的两种方式是POSTPUT。我们真正希望的是每个 HTTP 服务器都使用POST方法,因为PUT方法使得通过操控 URL 很容易让人妥协服务器。所以,如果你扫描一个服务器并发现它允许使用PUT方法进行任何类型的 HTTP 通信,那你就有潜在问题了。在这种情况下,解决方案是更新操作系统,并希望这些更新能够解决问题。如果这是一个 Web 服务器,你就需要和 Web 服务器管理员沟通,告诉他们你发现的问题。

接下来,我们看到这个机器上运行着 VNC 服务:

5900/tcp open vnc Apple remote desktop vnc
| vnc-info:
| Protocol version: 3.889
| Security types:
|_ Mac OS X security type (30)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at http://www.insecure.org/cgi-bin/servicefp-submit.cgi :
SF-Port515-TCP:V=6.47%I=7%D=12/24%Time=5A40479E%P=x86_64-suse-linux-gnu%r(
SF:GetRequest,1,"\x01");
MAC Address: 00:0A:95:8B:E0:C0 (Apple)
Device type: general purpose

VNC 有时确实很方便。它就像 Windows 的 Microsoft 远程桌面服务,不过它是免费的开源软件。但它也是一个安全问题,因为它是一个未加密的协议。所以,所有信息都会以明文形式通过网络传输。如果你必须使用 VNC,最好通过 SSH 隧道运行它。

接下来,我们看看 Nmap 关于目标机器操作系统的发现:

Running: Apple Mac OS X 10.4.X
OS CPE: cpe:/o:apple:mac_os_x:10.4.10
OS details: Apple Mac OS X 10.4.10 - 10.4.11 (Tiger) (Darwin 8.10.0 - 8.11.1)
Network Distance: 1 hop
Service Info: OS: Mac OS X; CPE: cpe:/o:apple:mac_os_x

等等,什么?Mac OS X 10.4?那不是非常非常古老的吗?嗯,是的,确实是。过去几章我一直守口如瓶的秘密是,我的 Greenbone Security Assistant 和 Nmap 扫描演示的目标机器其实是我那台 2003 年出产的古老的、值得收藏的 Apple eMac。我觉得扫描它会给我们带来一些有趣的结果,看来我没猜错。(是的,没错,那是eMac,而不是iMac。)

最后我们看到的是TRACEROUTE信息。不过这并不太有趣,因为目标机器就在我旁边,只有一个 Cisco 交换机在我们之间:

TRACEROUTE
HOP RTT ADDRESS
1 0.16 ms 192.168.0.37
OS and Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 213.92 seconds
donnie@linux-0ro8:~>

假设目标机器将其 SSH 服务更改为某个备用端口,而不是运行在默认端口22上。如果你用普通的-sS-sT扫描,这时 Nmap 将无法正确识别该备用端口上的 SSH 服务。但是,-A扫描会正确识别 SSH 服务,无论它使用哪个端口。

好的,让我们做一个实验。

实验操作 – 使用 Nmap 扫描

在这个实验中,你将看到扫描一台机器的结果,看看它的各种服务是启用还是禁用。你将从一台防火墙禁用的虚拟机开始。让我们开始吧:

  1. 通过使用以下命令,简要浏览 Nmap 帮助屏幕:
nmap
  1. 无论是从你的主机还是从另一台虚拟机,针对防火墙禁用的虚拟机执行这些扫描(请用你自己的 IP 地址替换我在这里使用的地址):
sudo nmap -sS 192.168.0.252
sudo nmap -sT 192.168.0.252
sudo nmap -SU 192.168.0.252
sudo nmap -A 192.168.0.252
sudo nmap -sA 192.168.0.252
  1. 在 Ubuntu 上停止目标机器的 SSH 服务:
sudo systemctl stop ssh

在 CentOS 或 AlmaLinux 上,使用以下命令:

sudo systemctl stop sshd
  1. 重复步骤 2

你已经完成了这个实验室的所有步骤——恭喜!

现在你已经了解了如何扫描一个系统,接下来我们来看看 GRUB2 引导加载程序。

为 GRUB 2 引导加载程序设置密码保护

有时候,人们会忘记密码,即使他们是管理员。而且有时候,人们购买二手电脑时,忘记问卖家密码是什么。(是的,我做过。)不过没关系,因为所有主要操作系统都有方法让你重置或恢复丢失的管理员密码。这很方便,除非你考虑到一个问题,那就是当某人可以物理接触到机器时,登录密码的意义就显得有些多余。假设你的笔记本被偷了。如果你没有加密硬盘,窃贼只需几分钟就能重置密码并窃取你的数据。如果你加密了硬盘,那么保护级别将取决于你使用的操作系统。使用标准的 Windows 文件夹加密,窃贼只需重置密码就能访问加密的文件夹。而如果是 Linux 机器上的 LUKS 整盘加密,窃贼将无法绕过输入加密密码短语这一关。

在 Linux 上,我们有方法来防止未经授权的密码重置,即使我们没有使用整盘加密。我们只需要为 Grand Unified BootloaderGRUB)设置密码保护,这样窃贼就无法通过进入紧急模式来重置密码。

是否需要本节中的建议取决于你所在组织的物理安全设置。这是因为将 Linux 机器引导到紧急模式需要物理访问权限。这不是你可以远程操作的事情。在拥有良好物理安全的组织中,服务器——尤其是那些存储敏感数据的服务器——会被锁在一个房间内,该房间又被锁在另一个房间里。只有极少数受信任的人员可以进入,并且他们必须在两个出入点出示证件。因此,为这些服务器的引导加载程序设置密码是没有意义的,除非你正在处理某个要求这样做的监管机构。

另一方面,为暴露在外的工作站和笔记本电脑设置启动加载器密码保护可能会非常有用。然而,光靠这个保护并不足以保护你的数据。有人仍然可以从 Live 磁盘或 USB 存储设备启动机器,挂载机器的硬盘,并获取敏感数据。因此,你还需要加密你的敏感数据,就像我在 第六章 中展示的那样,加密技术

要重置密码,你只需要在启动菜单出现时中断启动过程,修改几个内核参数,或者选择 恢复 模式选项(如果可用)。无论哪种方式,机器将会以紧急模式启动,而不需要输入密码。然而,从紧急模式中进行密码重置并不是你能做的唯一操作。一旦你进入紧急模式,你将获得对整个系统的完全 root 用户控制权限。

现在,为了让你了解我所说的通过 GRUB 2 启动菜单编辑内核参数,下面我将向你展示如何在 Red Hat 系统上执行密码重置。

动手实验 – 重置 Red Hat/CentOS/AlmaLinux 的密码

除了一个非常小的例外,这一过程在 CentOS 7、AlmaLinux 8 和 AlmaLinux 9 上几乎完全相同。我们开始吧:

  1. 启动虚拟机。当启动菜单出现时,通过按下向下箭头键中断启动过程。然后,按向上箭头键一次,选择默认启动选项:

    图 16.2:选择启动选项

    图 16.2:选择启动选项

  2. 按下 e 键以编辑内核参数。当 GRUB 2 配置页面出现时,向下移动光标,直到看到这一行:

    图 16.3:编辑内核选项

    图 16.3:编辑内核选项

    请注意,在 CentOS 7 上,这一行以 linux16 开头,如下所示。在 AlmaLinux 8/9 上,这一行以 linux 开头。

  3. 删除这一行中的 rhgb quiet 字样,然后在行末添加 rd.break enforcing=0。这两个新选项的作用如下:

  4. rd.break:这会使机器进入紧急模式,允许你获得 root 用户权限,而无需输入 root 用户密码。即使 root 用户密码尚未设置,这个方法也能奏效。

  5. enforcing=0:当你在启用了 SELinux 的系统上重置密码时,/etc/shadow 文件的安全上下文会变更为错误的类型。如果系统在执行此操作时处于强制模式,SELinux 会阻止你登录,直到 shadow 文件被重新标记。然而,在启动过程中重新标记文件可能需要很长时间,尤其是在大硬盘上。通过将 SELinux 设置为宽容模式,你可以等待到重启后再恢复 shadow 文件的正确安全上下文。

  6. 完成内核参数编辑后,按 Ctrl + X 继续启动过程。这会将你带到紧急模式,并出现 switch_root 命令提示符:

    图 16.4:在紧急模式下

    图 16.4:在紧急模式下

  7. 在紧急模式下,文件系统以只读方式挂载。你需要将其重新挂载为读写模式,并进入chroot模式,才能重置密码,使用以下两个命令:

mount -o remount,rw /sysroot
chroot /sysroot

输入这两个命令后,命令提示符将变为正常的 bash shell 提示符:

图 16.5:进入 chroot

图 16.5:进入 chroot

现在你已经到达这个阶段,终于准备好重置密码了。

  1. 如果你想重置 root 用户密码,或者甚至想为之前没有密码的 root 用户创建一个密码,只需输入:
passwd

然后,输入所需的新密码。

  1. 如果系统从未设置过 root 用户密码,并且你仍然希望它没有密码,可以重置一个具有完全 sudo 权限的账户的密码。例如,在我的系统中,命令如下:
passwd donnie
  1. 接下来,将文件系统重新挂载为只读模式。然后,输入exit两次以继续重启:
mount -o remount,ro /
exit
exit
  1. 重启后你需要做的第一件事是恢复/etc/shadow文件的正确 SELinux 安全上下文。然后,将 SELinux 恢复为强制模式:
sudo restorecon /etc/shadow
sudo setenforce 1

这是我shadow文件上下文设置的前后截图:

图 16.6:用于 shadow 文件的 SELinux 上下文设置

图 16.6:用于 shadow 文件的 SELinux 上下文设置

在这里,你可以看到重置密码将文件类型更改为unlabeled_t。运行restorecon命令后,文件类型恢复为shadow_t

你已完成本实验——祝贺你!

现在,我们来看一下在 Ubuntu 系统上的相同操作步骤。

实操实验 – 重置 Ubuntu 的密码

在 Ubuntu 系统上重置密码的过程与其他系统有些不同,而且简单得多。然而,在 Ubuntu 16.04 和 Ubuntu 18.04 或更高版本之间有一个小的差别。即,在 Ubuntu 16.04 上,查看启动菜单不需要任何操作。而在 Ubuntu 18.04 上,你需要按下 Shift 键(对于基于 BIOS 的系统)或 Esc 键(对于基于 UEFI 的系统)才能看到启动菜单。在当前的 Ubuntu 22.04 上,无论是基于 BIOS 还是 UEFI 的系统,都需要按 Esc 键。除此之外,从 Ubuntu 16.04 到当前的 Ubuntu 22.04,操作过程完全相同。那么,现在我们开始吧:

  1. 启动虚拟机。按下 Esc 键以调出启动菜单。

  2. 按下向下箭头键以突出显示 Ubuntu 菜单中的高级选项项,然后按 Enter 键:

    图 16.7:Ubuntu 高级选项子菜单

    图 16.7:Ubuntu 高级选项子菜单

  3. Ubuntu 高级选项子菜单中,选择恢复模式选项,并按 Enter 键:

    图 16.8:选择恢复模式选项

    图 16.8:选择恢复模式选项

  4. 恢复菜单出现时,选择root选项,并按 Enter 键:

    图 16.9:选择 root 选项

    图 16.9:选择 root 选项

  5. 再次按下回车键。这将带你进入 root shell:

    图 16.10:恢复模式

    图 16.10:恢复模式

  6. 由于 Ubuntu 通常没有为 root 用户分配密码,你很可能会重置具有完全 sudo 权限的用户的密码,像这样:

passwd donnie
  1. 完成后,像平常一样重启:
shutdown -r now

机器现在将正常启动。

你已完成本实验——恭喜!

当然,我们不希望每个人都能编辑内核参数或在启动机器时进入恢复模式。所以,让我们来解决这个问题。

在 Red Hat/CentOS/AlmaLinux 上防止编辑内核参数

自从 Red Hat/CentOS 7.2 引入以来,设置 GRUB 2 密码以防止编辑内核参数变得容易。幸运的是,这个技巧在 Red Hat 和 AlmaLinux 的最新版本中仍然有效。你只需要运行一个命令并选择一个密码:

[donnie@localhost ~]$ sudo grub2-setpassword
[sudo] password for donnie:
Enter password:
Confirm password:
[donnie@localhost ~]$

就是这么简单。密码哈希将被存储在/boot/grub2/user.cfg文件中。

现在,当你重启机器并尝试编辑内核参数时,你将被提示输入用户名和密码:

图 16.11:RHEL 7.2 及更新版本的密码保护

图 16.11:RHEL 7.2 及更新版本的密码保护

请注意,即使系统中尚未设置root用户的密码,你仍然需要输入root作为用户名。在这种情况下,root用户只是 GRUB 2 的超级用户。

当你启动 Red Hat、CentOS 或 AlmaLinux 机器时,你会看到一个0-rescue选项出现在启动菜单的底部。(你可以在图 16.2 中看到它。)如果选择该选项,你会发现它什么也不做,只会把你带到一个普通的登录提示符,要求你输入用户名和密码。(Red Hat 类型的发行版确实有一个救援模式,但你必须从安装介质启动机器才能进入。)

在 Ubuntu 上防止编辑内核参数或访问恢复模式

Ubuntu 没有像 Red Hat、CentOS 和 AlmaLinux 那样酷的工具,所以你需要手动编辑配置文件来设置 GRUB 2 密码。

/etc/grub.d/目录中,你将看到构成 GRUB 2 配置的文件:

donnie@ubuntu3:/etc/grub.d$ ls -l
total 76
-rwxr-xr-x 1 root root  9791 Oct 12 16:48 00_header
-rwxr-xr-x 1 root root  6258 Mar 15  2016 05_debian_theme
-rwxr-xr-x 1 root root 12512 Oct 12 16:48 10_linux
-rwxr-xr-x 1 root root 11082 Oct 12 16:48 20_linux_xen
-rwxr-xr-x 1 root root 11692 Oct 12 16:48 30_os-prober
-rwxr-xr-x 1 root root  1418 Oct 12 16:48 30_uefi-firmware
-rwxr-xr-x 1 root root   214 Oct 12 16:48 40_custom
-rwxr-xr-x 1 root root   216 Oct 12 16:48 41_custom
-rw-r--r-- 1 root root   483 Oct 12 16:48 README
donnie@ubuntu3:/etc/grub.d$

你需要编辑的文件是40_custom文件。然而,在编辑文件之前,你需要创建密码哈希。使用grub-mkpasswd-pbkdf2工具来完成这项工作:

donnie@ubuntu3:/etc/grub.d$ grub-mkpasswd-pbkdf2
Enter password:
Reenter password:
PBKDF2 hash of your password is grub.pbkdf2.sha512.10000.F1BA16B2799CBF6A6DFBA537D43222A0D5006124ECFEB29F5C81C9769C6C3A66BF53C2B3AB71BEA784D4386E86C991F7B5D33CB6C29EB6AA12C8D11E0FFA0D40.371648A84CC4131C3CFFB53604ECCBA46DA75AF196E970C98483385B0BE026590C63A1BAC23691517BC4A5D3EDF89D026B599A0D3C49F2FB666F9C12B56DB35D
donnie@ubuntu3:/etc/grub.d$

用你喜欢的文本编辑器打开40_custom文件,并添加一行定义超级用户。再添加一行用于密码哈希。在我的情况下,文件现在看起来像这样:

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment. Be careful not to change
# the 'exec tail' line above.
set superusers="donnie"
password_pbkdf2 donnie grub.pbkdf2.sha512.10000.F1BA16B2799CBF6A6DFBA537D43222A0D5006124ECFEB29F5C81C9769C6C3A66BF53C2B3AB71BEA784D4386E86C991F7B5D33CB6C29EB6AA12C8D11E0FFA0D40.371648A84CC4131C3CFFB53604ECCBA46DA75AF196E970C98483385B0BE026590C63A1BAC23691517BC4A5D3EDF89D026B599A0D3C49F2FB666F9C12B56DB35D

password_pbkdf2开头的这串文本是整行显示,在打印页面上会换行。

保存文件后,最后一步是生成一个新的grub.cfg文件:

donnie@ubuntu3:/etc/grub.d$ sudo update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.4.0-104-generic
Found initrd image: /boot/initrd.img-4.4.0-104-generic
Found linux image: /boot/vmlinuz-4.4.0-101-generic
Found initrd image: /boot/initrd.img-4.4.0-101-generic
Found linux image: /boot/vmlinuz-4.4.0-98-generic
Found initrd image: /boot/initrd.img-4.4.0-98-generic
done
donnie@ubuntu3:/etc/grub.d$

现在,当我重新启动这台机器时,我必须输入我的密码才能编辑内核参数或访问Ubuntu 高级选项子菜单:

图 16.12:Ubuntu 的密码保护

图 16.12:Ubuntu 的密码保护

这有一个问题。它不仅阻止了除超级用户外的任何人编辑内核参数,而且还阻止了除超级用户外的任何人正常启动。是的,没错。即使是正常启动,Ubuntu 现在也会要求你输入授权超级用户的用户名和密码。幸运的是,这是一个容易修复的问题。

这个修复需要在/boot/grub/grub.cfg文件中插入一个单词。听起来很简单,对吧?然而,这并不是一个优雅的解决方案,因为你本不应手动编辑grub.cfg文件。在文件的顶部,我们看到了这一行:

# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#

这意味着每当我们做一些会更新grub.cfg文件的操作时,我们手动对文件所做的任何更改都会丢失。这包括当我们进行系统更新,安装新内核时,或者执行sudo apt autoremove来移除不再需要的旧内核时。最具讽刺意味的是,官方的 GRUB 2 文档却告诉我们应该手动编辑grub.cfg文件来处理这些问题。一个更好的方法是修改update-grub工具使用的 shell 脚本,来构建grub.cfg文件。这样可以避免意外覆盖你需要保留的更改。

/etc/grub.d/目录中,你会看到几个用于构建grub.cfg的脚本。我们需要的是10_linux文件中的那个。用文本编辑器打开它,并导航到大约第 197 行的位置。查找这两行:

echo "menuentry '$(echo "$title" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-$version-$type-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
. . .
. . .
echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"

(请注意,每一行都是在打印页面上换行的一行。)

在每一行后面加上--unrestricted,使得这些行现在看起来像这样:

echo "menuentry '$(echo "$title" | grub_quote)' ${CLASS} --unrestricted \$menuentry_id_option 'gnulinux-$version-$type-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
. . .
. . .
echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} --unrestricted \$menuentry_id_option 'gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"

最后,运行sudo update-grub命令,你就可以正常启动机器,使用默认选项。但如果你想进入Ubuntu 高级选项子菜单,那就另当别论了。在设置了超级用户密码的情况下,你将始终需要输入超级用户密码才能进入Ubuntu 高级选项子菜单。即使你在10_linux 脚本中添加了--unrestricted选项,情况也是如此。实际上,这样可以防止没有密码的人访问恢复选项。

禁用 Ubuntu 的子菜单

在 Ubuntu 系统上,你可以轻松禁用 Ubuntu 子菜单,这样默认情况下你就会看到所有启动选项,结果看起来像这样:

图 16.13:没有子菜单的 Ubuntu 启动菜单

图 16.13:没有子菜单的 Ubuntu 启动菜单

如果需要,你还可以设置使得你无需按下 Shift 或 Esc 键即可看到启动菜单。

首先,在文本编辑器中打开/etc/default/grub文件。通过添加GRUB_DISABLE_SUBMENU=y这一行来禁用子菜单。为了使启动菜单默认可见,查找以下两行:

GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0

注释掉第一行,并将第二行的值更改为非零数字。现在这些行应该看起来像这样:

# GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=10

最后,运行sudo update-grub命令。现在,当你重新启动机器时,启动菜单会自动显示出来,你会看到完整的启动选项列表,而不仅仅是默认启动选项和子菜单选项。经过十秒钟的超时后,系统将自动启动默认选项。

禁用 Ubuntu 子菜单的主要问题是,如果你按照我刚才所示配置了带有--unrestricted选项的 GRUB,用户将能够在不输入密码的情况下启动恢复模式。所以,这样就像是你从一开始就没有为 GRUB 设置密码保护一样。如果你禁用了 Ubuntu 子菜单,记得同时禁用恢复模式选项。在编辑器中打开/etc/default/grub文件,查找这一行:

# GRUB_DISABLE_RECOVERY="true"

删除行前面的#符号,使其现在看起来像这样:

GRUB_DISABLE_RECOVERY="true"

如你之前所做的那样更新 GRUB 配置:

sudo update-grub

最后,重新启动机器并验证恢复模式选项是否已消失。如果你禁用了恢复启动菜单选项并且仍然需要进入恢复模式,你仍然可以通过在启动过程中编辑内核参数来实现。这个过程与你刚才看到的 AlmaLinux 略有不同,因为你在 Ubuntu 上无需担心 SELinux。为了避免重复此过程,我会在进一步阅读部分留一个教程链接。(该链接的文章适用于 Ubuntu 18.04,但该过程仍适用于当前的 Ubuntu 22.04。)

所以,你现在可能会问,为什么我需要禁用 Ubuntu 子菜单? 实际上,你永远不需要禁用它。对我来说,这只是个人偏好问题。与 Red Hat 发行版不同,Ubuntu 在更新操作时如果安装了新内核,它不会自动删除旧的 Linux 内核。如果你在更新后没有记得执行sudo apt autoremove命令来清理旧内核,可能会填满/boot/分区,从而阻止未来更新安装新内核。通过禁用子菜单并使启动菜单默认可见,我可以在启动机器时看到已安装的 Linux 内核数量。(但嘿,那只是我,我有点怪。只要问问认识我的人就知道。)在生产机器上,启用子菜单和恢复选项,并设置 GRUB 2 密码会更为合理。

你可以在www.gnu.org/software/grub/manual/grub/grub.html#Security找到官方 GRUB 2 文档的安全部分。

安全配置 BIOS/UEFI

这个话题与我们之前讨论的内容不同,因为它与操作系统无关。现在,我们将讨论计算机硬件。

每个计算机主板都有一个 BIOS 或 UEFI 芯片,用于存储计算机的硬件配置以及在开机后启动过程所需的引导指令。UEFI 替代了较老的 BIOS,并且它比旧版 BIOS 拥有更多的安全特性。

我不能给你关于 BIOS/UEFI 设置的具体信息,因为每种型号的主板都有不同的设置方式。我能提供的是一些更通用的信息。

当你考虑 BIOS/UEFI 安全时,你可能会想到禁用从除了正常系统驱动器以外的其他设备启动。在下面的截图中,你可以看到我禁用了所有 SATA 驱动端口,除了与系统驱动器连接的端口:

图 16.14:禁用我的惠普 Envy 驱动端口

图 16.14:禁用我的惠普 Envy 驱动端口

当计算机暴露在公众面前,任何人都能轻易接触到时,这可能是一个考虑因素。对于那些存放在有安全保护的房间内、限制访问的服务器来说,就没有必要过于担心,除非某个监管机构的安全要求另有规定。对于暴露在外的机器,整个磁盘加密可以防止有人在从光盘或 USB 设备启动后窃取数据。然而,你可能仍然有其他理由阻止任何人从这些替代启动设备启动机器。

另一个考虑因素是,如果你在处理超级敏感数据的安全环境中工作。如果你担心未经授权的数据外泄,可能需要考虑禁用对 USB 设备的写入能力。这也将阻止人们从 USB 设备启动机器:

图 16.15:禁用 USB 设备

图 16.15:禁用 USB 设备

有时候,你可能不想完全禁用机器的 USB 端口。相反,你可以将其保持启用,并使用 USBGuard 只允许某些 USB 设备连接。与其自己写一篇说明,不如参考我找到的这篇写得非常好的教程:

www.cyberciti.biz/security/how-to-protect-linux-against-rogue-usb-devices-using-usbguard/

USBGuard 的主要问题是,它仍然无法阻止某人从 USB 设备启动。

然而,BIOS/UEFI 安全不仅仅是这样。今天的现代服务器 CPU 配备了各种安全功能,以帮助防止数据泄露。例如,我们来看看 Intel Xeon CPU 中实现的一些安全功能:

  • 身份保护技术

  • 高级加密标准新指令

  • 受信执行技术

  • 硬件辅助虚拟化技术

AMD,这个在 CPU 市场上奋力拼搏的“黑马”,在他们的 EPYC 服务器 CPU 系列中也有自己的新安全功能。这些功能包括:

  • 安全内存加密

  • 安全加密虚拟化

无论如何,你将会在服务器的 UEFI 设置工具中配置这些 CPU 安全选项。

你可以在www.intel.com/content/www/us/en/newsroom/news/xeon-scalable-platform-built-sensitive-workloads.html阅读有关 Intel Xeon 安全功能的内容。

你可以在semiaccurate.com/2017/06/22/amds-epyc-major-advance-security/www.servethehome.com/amd-psb-vendor-locks-epyc-cpus-for-enhanced-security-at-a-cost/阅读有关 AMD EPYC 安全功能的内容。

当然,对于任何暴露在外的机器,最好为 BIOS 或 UEFI 设置密码保护:

图 16.16:为 BIOS/UEFI 设置密码保护

图 16.16:为 BIOS/UEFI 设置密码保护

如果没有其他原因,至少也应该为了防止别人乱动你的设置而做这件事。

现在你了解了一些关于锁定 BIOS/UEFI 的信息,我们来谈谈安全检查清单。

使用安全检查清单进行系统设置

之前,我向你介绍了 OpenSCAP,这是一个非常有用的工具,可以通过最小的努力来锁定你的系统。OpenSCAP 附带了多种配置文件,你可以应用这些配置文件,帮助你的系统符合不同监管机构的标准。然而,OpenSCAP 并不能为你做所有事情。例如,某些监管机构要求你的服务器硬盘按特定方式分区,将某些目录单独分成独立的分区。如果你已经将服务器设置为所有内容都放在一个大分区中,那么仅通过 OpenSCAP 的修复程序是无法解决的。确保你的服务器符合任何适用的安全法规的过程必须在你安装操作系统之前就开始。为此,你需要合适的检查清单。

如果你只需要一个通用的安全检查清单,有几个地方可以获取。德克萨斯大学奥斯汀分校发布了适用于 Red Hat Enterprise Linux 7 的通用检查清单,如果你需要将其用于 CentOS 7、Oracle Linux 7 或 Scientific Linux 7,你可以进行调整。(遗憾的是,他们没有提供任何更新的版本。)

你可能会发现一些检查清单项目与你的情况不符,可以根据需要进行调整:

图 16.17:德克萨斯大学检查清单

图 16.17:德克萨斯大学检查清单

对于特定的行业领域,你需要从相关的监管机构获取检查清单。如果你从事金融行业或与接受信用卡支付的企业合作,你需要从支付卡行业安全标准委员会(PCI)获取检查清单:

图 16.18:PCI-DSS 网站

图 16.18:PCI-DSS 网站

对于美国的医疗保健组织,有 HIPAA 及其相关要求。对于美国的上市公司,有 Sarbanes-Oxley 及其相关要求。

你可以在 wikis.utexas.edu/display/ISO/Operating+System+Hardening+Checklists 获取德克萨斯大学的检查清单。

你可以在 www.pcisecuritystandards.org/ 获取 PCI-DSS 检查清单。

你可以在 www.hhs.gov/hipaa/for-professionals/security/guidance/cybersecurity/index.html 获取 HIPAA 检查清单。

你可以在 www.sarbanes-oxley-101.com/sarbanes-oxley-checklist.htm 获取 Sarbanes-Oxley 检查清单。

其他监管机构可能也有自己的检查清单。如果你知道必须处理它们中的任何一个,务必获取相应的检查清单。

总结

再次,我们来到了本章的结尾,我们涵盖了许多有趣的主题。我们首先讨论了审计系统上运行的服务的不同方法,并看到了一些你可能不想看到的例子。然后,我们展示了如何使用 GRUB 2 的密码保护功能,并讨论了使用这些功能时需要处理的小问题。接着,我们换了一个节奏,讨论了如何通过正确设置系统的 BIOS/UEFI 来进一步加固系统。最后,我们讨论了为什么我们需要开始为设置加固系统做准备,通过获取并遵循适当的检查清单。

这不仅是另一个章节的结束,也是本书的结束。然而,这并不意味着你在掌握 Linux 安全和加固的旅程就此结束。哦,不。当你继续这段旅程时,你会发现还有更多的知识需要学习,仍然有很多内容无法在一本书中囊括。你接下来的方向主要取决于你所从事的 IT 管理领域。不同类型的 Linux 服务器,无论是 Web 服务器、DNS 服务器还是其他,都会有自己的特殊安全需求,你需要遵循最适合你需求的学习路径。

我很高兴能够陪伴你走过这段旅程,希望你也和我一样喜欢这段经历。

问题

  1. 你需要查看正在监听传入连接的网络服务列表。你会使用以下哪条命令?

    1. sudo systemctl -t service --state=active

    2. netstat -i

    3. netstat -lp -A inet

    4. sudo systemctl -t service --state=listening

  2. 以下哪条命令可以查看已建立的 TCP 连接列表?

    1. netstat -p -A inet

    2. netstat -lp -A inet

    3. sudo systemctl -t service --state=connected

    4. sudo systemctl -t service --state=active

  3. 当 Nmap 告诉你某个端口处于开放状态时,这意味着什么?

    1. 该端口在防火墙上是开放的。

    2. 该端口在防火墙上是开放的,并且与该端口关联的服务正在运行。

    3. 该端口可以通过互联网访问。

    4. 确保该端口的访问控制列表设置为开放。

  4. 以下哪种 Nmap 扫描选项最可能用于扫描开放的 TCP 端口?

    1. -sn

    2. -sU

    3. -sS

    4. -sA

  5. 当重置 Red Hat/CentOS/AlmaLinux 机器的 root 用户密码时,你想做什么?

    1. 确保 AppArmor 处于强制模式。

    2. 确保 SELinux 处于强制模式。

    3. 确保 AppArmor 处于报告模式。

    4. 确保 SELinux 处于宽容模式。

  6. Nmap 的发现模式是如何工作的?

    1. 它通过向网络的广播地址发送 ping 数据包来发现网络设备。

    2. 它通过向网络的广播地址发送 SYN 数据包来发现网络设备。

    3. 它向本地网络发送 ARP 数据包,向远程网络发送 ping 数据包。

    4. 它向本地网络发送 ping 数据包,向远程网络发送 ARP 数据包。

  7. 你想使用 Nmap 对整个子网进行 UDP 端口扫描。你会使用以下哪条命令?

    1. sudo nmap -sU 192.168.0.0

    2. sudo nmap -U 192.168.0.0

    3. sudo nmap -U 192.168.0.0/24

    4. sudo nmap -sU 192.168.0.0/24

  8. 你会如何开始加固一台新计算机系统的过程?

    1. 在安装操作系统时应用 OpenSCAP 配置文件。

    2. 通过按照检查清单开始初始设置。

    3. 安装操作系统后,应用 OpenSCAP 配置文件。

    4. 安装操作系统后,按照加固清单操作。

  9. 在 Red Hat/CentOS/AlmaLinux 服务器上,你最有可能做什么来强制用户在启动时编辑内核参数之前输入密码?

    1. 输入sudo grub2-password命令。

    2. 手动编辑 grub 配置文件。

    3. 输入sudo grub2-setpassword命令。

    4. 输入sudo grub-setpassword命令。

    5. 输入sudo grub-password命令。

深入阅读

答案

  1. c

  2. a

  3. b

  4. c

  5. d

  6. c

  7. d

  8. b

  9. c

posted @ 2025-07-05 19:51  绝不原创的飞龙  阅读(6)  评论(0)    收藏  举报