PAM—在任何地方保护 Linux 系统

作者:Federico Kereki

如果您喜欢英国侦探小说,并且对诸如夏洛克·福尔摩斯、塞克斯顿·布莱克、J. G. 里德先生、马普尔小姐、赫尔克里·波洛、布朗神父、约翰·伊夫林·桑代克博士和彼得·温西勋爵这样的名字感到熟悉,那么您可能也会认出 E. W. Hornung(阿瑟·柯南·道尔爵士,夏洛克·福尔摩斯的创造者的姐夫)笔下的人物:白手套小偷,莱佛士。在短篇故事“禧年礼物”中,小偷对大英博物馆展出的古董金杯着迷。当只找到一名警卫时,莱佛士质疑他所认为的缺乏安全,并得到了警卫自信的回答:“您看,先生,现在还早;再过几分钟,这些房间就会挤满人;俗话说,人多势众,安全有保障。” 对于 Linux 而言,安全性不是靠人多势众(最终这对可怜的警卫没有任何好处;请参阅“资源”部分,了解完整故事的链接),而是由可插拔身份验证模块 (PAM) 管理的。在本文中,我们将研究 PAM 的功能、配置和用法。

让我们从头开始,考虑应用程序如何验证用户身份。如果没有通用的基本机制,每个应用程序都需要使用特定的身份验证逻辑进行编程,例如检查 /etc/passwd 以获取有效的用户和密码。但是,如果您有多个不同的应用程序需要身份验证怎么办?您是否要在所有应用程序中都包含相同的特定逻辑?而且,如果您的安全需求各不相同怎么办?您是否需要修改和重新编译所有这些应用程序?这不会是一种实用的方法,而且肯定会成为一个漏洞。您如何确保所有应用程序都已正确更新并正确实施了您的新规范?

PAM 项目通过添加额外的层来提供解决方案。需要身份验证的程序使用标准库或 API(应用程序编程接口),系统管理员可以单独配置该库将执行哪些检查。(检查是通过独立的模块实现的;您甚至可以编写自己的模块。)这样,您可以动态更改安全检查,并且所有实用程序都会自动遵循您的新规则。换句话说,您可以修改任何 PAM 感知应用程序使用的身份验证机制,而无需接触应用程序本身。对于程序员来说,这也是一件好事,因为他们无需关心将要使用的机制。只需使用 PAM 库,无论何时运行应用程序,都会执行相应的检查(图 1)。

PAM 库将身份验证分解为四个区域或组(表 1)。请注意,并非所有应用程序总是需要之前的四个操作。例如,passwd 命令只需要最后一组。(快速提示:如何了解应用程序是否使用了 PAM?使用ldd打印程序所需的共享库,并检查 libpam.so;请参阅清单 1 中的示例。)

PAM—Securing Linux Boxes Everywhere

图 1. 每当应用程序发出身份验证请求时,PAM 库都会执行配置文件中指定的任何模块,并决定是批准(成功)还是拒绝(失败)该请求。

清单 1. 要了解程序是否使用了 PAM,请使用 ldd 并查找 libpam.so 库。您需要提供程序的完整路径;如果您不知道,请使用 whereis。

$ whereis login
login: /bin/login /etc/login.defs /usr/share/man/man3/login.3.gz 
 ↪/usr/share/man/man1/login.1.gz
$ ldd /bin/login
        linux-gate.so.1 =>  (0xffffe000)
        libpam_misc.so.0 => /lib/libpam_misc.so.0 (0xb7eff000)
        libpam.so.0 => /lib/libpam.so.0 (0xb7ef3000)
        libaudit.so.0 => /lib/libaudit.so.0 (0xb7edf000)
        libc.so.6 => /lib/libc.so.6 (0xb7dac000)
        libdl.so.2 => /lib/libdl.so.2 (0xb7da8000)
        /lib/ld-linux.so.2 (0xb7f25000)

表 1. PAM 具有四组检查,组织为堆栈。将要使用的组取决于用户的需求。

身份验证 (auth)与用户身份识别相关,例如用户需要输入密码时。这通常是第一组检查。
账户 (account)与用户账户管理有关,包括检查密码是否已过期或是否存在时间访问限制。一旦用户通过身份验证模块识别身份,账户模块将确定是否可以授予他们访问权限。
会话 (session)处理连接管理,包括诸如记录条目或活动的操作,或在会话结束后执行一些清理操作。
密码 (password)包括诸如更新用户密码的功能。

表 2. 对于每个堆栈,模块按顺序执行,具体取决于它们的控制标志。您必须指定相应的检查是强制性的、可选的等等。

required (必要)此模块必须成功结束。如果失败,总体结果将为失败。如果所有模块都标记为 required,则任何单个失败都将拒绝身份验证,尽管堆栈中的其他模块仍将被尝试。
requisite (必须)工作方式类似于 required,但在失败的情况下,立即返回,而无需遍历堆栈的其余部分。
sufficient (充分)如果此模块成功结束,则将跳过其他模块,并且总体结果将为成功。
optional (可选)如果此模块失败,则总体结果将取决于其他模块。如果没有 required 或 sufficient 模块,则至少一个 optional 模块应成功结束才能允许身份验证。
配置 PAM

对于每个服务(例如登录或 SSH),您必须定义将对每个组执行哪些检查。操作列表称为堆栈。根据每个堆栈中操作的结果,用户将成功或失败,并且他们尝试执行的任何操作都将被允许或拒绝。您可以使用 /etc/pam.d 中特定的文件(更现代的方法)或编辑单个通用的 /etc/pam.conf 文件(较旧的方法)来为每个服务指定堆栈中的每个操作;在本文中,我们使用前一种方法。

注意

请记住,玩弄配置文件可能对您的健康有害!一个特别糟糕的事情是意外删除所有配置文件,因为那样您将无法再次登录。在您开始实验之前,请务必备份所有文件,并准备好 live CD 以备不时之需。

每个堆栈都由模块构建而成,这些模块按给定的顺序依次执行。对于每个模块,您可以指定它是必要的(失败会自动拒绝访问)、充分的(成功会自动授予访问权限)还是可选的(允许替代检查)。表 2 显示了实际的控制标志。每个服务的文件都包含一个规则列表,每个规则都在自己的行上。(较长的行可以通过以 \ 结尾来拆分,但这很少需要。)以井号字符 (#) 开头的行被视为注释,因此将被忽略。每个规则包含三个字段:上下文区域(表 1)、控制标志(表 2)和将要运行的模块,以及可能的(可选)额外参数。因此,login 的 PAM 检查规范将在 /etc/pam.d/login 文件中找到。

控制标志字段实际上可能更复杂,但我不会在此处介绍所有细节。如果您有兴趣,请参阅“资源”部分。此外,您可以使用 include,例如auth include common-account,这意味着包含来自其他文件的规则。

有一个特殊的通用服务名为 other,用于没有特定规则的服务。从安全角度来看,一个好的开始是创建 /etc/pam.d/other,如清单 2 所示。所有尝试都被拒绝,并且向管理员发送警告。如果您想更宽容一些,请用 pam_unix2.so 替换 pam_deny.so,然后将使用标准的 Linux 身份验证方法,尽管仍会发送警告(清单 3)。如果您不关心安全性,请替换为 pam_permit.so,这将允许所有人进入,但别说我没警告过您。

最后,快速浏览一下 /etc/pam.d 中的文件。如果您发现了您不使用的应用程序的配置文件,只需重命名这些文件,以便 PAM 回退到您的 “other” 配置。如果您稍后发现您确实需要该应用程序,请将配置文件改回其原始名称,一切都会恢复正常。

清单 2. 安全的 “other” 定义禁止在没有特定规则的情况下进行所有通用访问。pam_deny.so 模块始终返回失败,因此所有访问尝试都将被拒绝,并且 pam_warn.so 向系统管理员发送警告。

#
# default; deny all accesses
#
auth    required       pam_deny.so
auth    required       pam_warn.so
account required       pam_deny.so
password        required        pam_deny.so
password        required        pam_warn.so
session required        pam_deny.so

清单 3. PAM 定义,等效于标准的 UNIX 安全规则。注意:在某些发行版上,您可能需要使用 pam_unix.so 代替。

#
# standard UNIX minimalistic rules
#
auth    required        pam_unix2.so
account required        pam_unix2.so
password        required        pam_unix2.so
session required        pam_unix2.so

清单 4. /etc/pam.d/sshd 指定了 SSH 连接的安全规则。pam_access.so 模块已添加到标准配置中,以提供进一步的检查。

auth    required        pam_unix2.so
auth    required        pam_nologin.so
account required        pam_unix2.so
account required        pam_access.so
session required        pam_limits.so
session required        pam_unix2.so
session optional        pam_umask.so
password        requisite       pam_pwcheck.so cracklib
password        required        pam_unix2.so use_authtok

清单 5. pam_access.so 使用 /etc/security/access.conf 来决定允许哪些用户登录以及从哪些 IP 登录。在本例中,来自本地网络的每个人都可以登录,但只有 remoteKereki 允许外部访问。

+ : ALL : 192.168.
+ : remoteKereki : ALL
- : ALL : ALL

清单 6. /etc/pam.d/passwd 文件中的密码部分,强制执行新密码的良好做法。

#
# retry=3 allows three tries for a new password
# minlen=10 requires at least ten characters
# ucredit=-1 requires at least one uppercase character
# lcredit=0 accepts any number of lowercase characters
# dcredit=-2 requires at least two digits
# ocredit=-1 requires at least one non-alphabetic symbol
#
password required pam_cracklib.so retry=3 minlen=10 \
     ucredit=-1 lcredit=0 dcredit=-2 ocredit=-1 
#
# As pam_cracklib only checks passwords, but doesn't store 
# them, we require the standard pam_unix module for this.
# The use_authtok parameter ensures pam_unix won't ask for a 
# password by itself, but rather will use the one provided by 
# pam_cracklib.
#
password  required pam_unix.so use_authtok nullok
安全远程访问

为了掌握这一切,让我们考虑一个实际的应用程序。我希望能够通过 SSH 远程访问我的机器,但我不希望允许任何其他用户(清单 4)。因此,我配置了我的 /etc/pam.d/sshd 文件。“模块,无处不在”侧边栏提供了有关这些模块和其他模块的更多详细信息。以下是我使用的一些模块

  • pam_unix2.so:以经典的 UNIX 方式提供传统的密码、权限、会话和密码更改方法。

  • pam_nologin.so:如果文件 /etc/nologin 存在,则禁止登录。

  • pam_access.so:实施访问控制的额外规则(本文稍后将详细介绍我如何使用它)。

  • pam_limits.so:根据文件 /etc/security/limits.conf 强制执行用户或组的限制。

  • pam_umask.so:为当前环境设置文件模式创建掩码(执行info umask以获取更多信息)。

  • pam_pwcheck:强制执行密码强度检查(本文稍后将详细介绍此模块的进一步用途)。

如果您检查自己的 /etc/pam.d/sshd 文件,它可能看起来像这样,除了 pam_access 模块,这是有趣的部分。此模块根据 /etc/security/access.conf 文件实现额外的安全控制。我编辑了它,以便指定谁可以访问我的机器(清单 5)。第一行表示任何人 (ALL) 都可以从家庭内部网络登录到我的机器。第二行允许 remoteKereki 用户从世界任何地方访问我的机器,最后一行是一个通用规则,禁止未明确包含在这些行中的任何人访问。我创建了具有最低权限的 remoteKereki 用户,以便允许我自己进入机器,然后我执行su并以我自己的身份甚至在需要时以 root 身份工作。如果人们猜对了 remoteKereki 的正确密码,对他们也没有太大帮助,因为攻击者仍然必须猜出其他更有用的用户的密码。实际上,它在入侵者造成严重损害之前提供了一道额外的屏障。

我必须通过添加一行来修改 /etc/ssh/sshd_configUsePAM yes,以便 sshd 使用 PAM 配置。我必须使用/etc/init.d/sshd restart重新启动 SSH,以便使用该配置。为了获得更安全的连接,您还可以将 SSH 标准端口 (22) 更改为不同的值,禁止 root 远程登录并限制重试次数以阻止暴力攻击,但这些主题超出了本文的范围。执行man ssh_config以获取更多详细信息。

要求使用强密码

大多数用户在无人监管的情况下,会(轻信且不知不觉地)使用容易猜测且永不更改的密码,从而简化了入侵者的工作。借助 PAM,您可以使用密码堆栈和 pam_pwcheck.so 模块来强制执行密码管理的若干良好做法。此模块对您的密码强度进行多项检查

  • 新密码是否太短?

  • 新密码与旧密码是否过于相似?

  • 新密码是否仅仅是旧密码的反转或旋转(例如,safe123 和 123safe)?

  • 新密码是否与旧密码相同,只是大小写发生了变化(例如,sEcReT 和 SEcrET)?

  • 新密码以前是否已使用过?(旧密码存储在 /etc/security/opasswd 文件中。)

您可以向模块添加多个参数(执行man pam_pwcheck以获取完整文档)以获得额外的规则,例如

  • minlen=数字:指定新密码的最小长度(默认情况下为五个字符)。如果您将其设置为零,则接受所有密码长度。

  • cracklib=字典路径:允许使用 cracklib 库进行密码检查。如果新密码在字典中,则简单的暴力攻击会很快猜到它。

  • tries=数字:设置如果之前的尝试因过于简单而被拒绝,则允许多少次尝试。

  • remember=数字:定义将记住多少个以前的密码。

另一个模块 pam_cracklib.so 提供了类似的功能,但它有一些不同的参数。例如,您可以指定新旧密码之间必须有多少个字符不同,以及您是否要包含数字、大写字母、小写字母和非字母字符。执行man pam_cracklib以获取更多信息。

结论

人数众多可能具有安全性(正如可怜的大英博物馆警卫在试图阻止莱佛士偷走杯子时所想的那样),但对于 Linux 而言,PAM 才是正确的选择。即使不诉诸于推出您自己的模块,您也可以通过设置一些配置文件来为您的安全性增加足够的灵活性,并确信这些规则将会在全局范围内得到遵守。

模块,无处不在

您系统的安全性取决于您使用的模块。模块存储在 /lib/security 或 /lib64/security(对于 64 位系统)中,但某些发行版不遵循此标准。例如,您可能会在 /usr/lib/security 中找到模块。如果您愿意,您可以编写自己的模块(请参阅“资源”部分),但对于初学者来说,您可能可以使用标准模块进行管理。以下是更常见模块的列表。有关更多信息,请使用man命令。另请注意,没有标准模块列表,每个发行版可能包含更多模块或以下模块的变体。

pam_access: 根据 IP、登录名、主机名或域名等允许或拒绝访问。默认情况下,访问规则在 /etc/security/access.conf 中指定。每当用户登录时,都会按顺序扫描访问规则以找到第一个匹配项,并相应地授予或拒绝权限。另请参阅 pam_time 以了解更多限制。

pam_cracklibpam_pwcheck: 提供密码强度检查,并禁止重复、过于简单和容易猜测的可能性。系统会提示用户输入密码,如果密码通过预定义的规则并且被认为是强密码,则会再次提示用户进行检查。

pam_deny: 简单地拒绝访问。它可以用于阻止用户作为默认规则。另请参阅 pam_permit。

pam_echo: 向用户显示(可配置的)文本消息。另请参阅 pam_motd。

pam_env: 允许设置或取消设置环境变量。默认规则取自 /etc/security/pam_env.conf。

pam_exec: 调用外部命令。

pam_lastlog: 显示上次登录的日期和时间。

pam_limits: 设置用户可能需要的系统资源限制。默认限制取自 /etc/security/limits.conf。

pam_listfile: 根据文件允许或拒绝服务。例如,您可以通过在 /etc/pam.d/ftpd 文件中包含以下行来限制对文件 /etc/ftpusers_ok 中用户的 FTP 访问auth required pam_listfile.so item=user sense=allow file=/etc/ftpusers_ok onerr=fail在 /etc/pam.d/ftpd 文件中。另请参阅 pam_nologin。

pam_mail: 通知用户他们是否有邮件。

pam_mkhomedir: 如果用户主目录在本地计算机上不存在,则创建用户主目录。这允许您使用中央身份验证(例如 NIS 或 LDAP),并在需要时才创建用户目录。

pam_motd: 向用户显示 “每日消息” 文件。另请参阅 pam_echo。

pam_nologin: 当 /etc/nologin 存在时禁止登录。

pam_permit: 允许无需检查即可进入—非常不安全!另请参阅 pam_deny。

pam_rootok: 允许 root 用户无需进一步检查即可访问。这通常在 /etc/pam.d/su 中使用,以允许 root 用户充当另一个用户而无需输入密码。该文件应包含以下行(关于第二行,请参阅 pam_wheel)

auth  sufficient   pam_rootok.so
auth  required     pam_wheel.so
auth  required     pam_unix.so

pam_succeed_if: 测试账户特征,例如属于某个组、具有某个 UID 等。

pam_time: 根据星期几和一天中的时间限制对服务的访问。默认规则取自 /etc/security/time.conf。但是请注意,仅强制执行登录时间。没有办法强制用户之后注销。

pam_umask: 设置文件模式创建掩码。

pam_unixpam_unix2: 基于 /etc/passwd 和 /etc/shadow 文件的经典 UNIX 风格身份验证。另请参阅 pam_userdb。

pam_userdb: 针对数据库进行身份验证。另请参阅 pam_unix。

pam_warn: 将服务、终端、用户和更多数据记录到系统日志中。该模块可以在任何地方使用,因为它不会影响身份验证过程。

pam_wheel: 仅允许 wheel 组的成员进行 root 访问。这通常用于su,因此只有选定的用户可以使用它。有关示例,请参阅 pam_rootok 条目。

Federico Kereki 是一位乌拉圭系统工程师,在大学教学、进行开发和咨询工作以及撰写文章和课程资料方面拥有 20 多年的经验。他使用 Linux 已有很多年,并在多家不同的公司安装了它。他对 Linux 系统的更好安全性和性能特别感兴趣。

加载 Disqus 评论