pam_pkcs11:错误情况下的潜在身份验证绕过(CVE-2025-24531)
#local #PAM #CVE目录
- 1) 引言
- 2) 问题发现/与 GDM 智能卡身份验证的关系
- 3) pam_pkcs11 中的
PAM_IGNORE问题 - 4) 受影响的发行版和配置
- 5) 可能的解决方法
- 6) Bug修复
- 7) 吸取的教训
- 8) 时间线
- 9) 参考资料
1) 引言
本报告是关于 pam_pkcs11 0.6.12 版本中的一个回归。在此版本中,pam_sm_authenticate() 的实现已更改为在许多退出路径中返回 PAM_IGNORE,这在某些情况下可能导致完全的身份验证绕过。本报告基于上游 Git 标签“pam_pkcs11-0.6.12”。在 0.6.13 版本中找到了 Bug 修复。
该问题是否可以被利用是一个复杂的问题,很大程度上取决于系统配置。以下部分将深入介绍我们如何发现该问题以及为什么在某些情况下其严重性可能很高。第 3 部分详细介绍了在 pam_pkcs11 中发现的问题。报告的其余部分探讨了哪些 Linux 发行版可能受到该问题的影响,可能的解决方法以及上游 Bug 修复。最后,我们将回顾从这一发现中可以吸取的教训。
2) 问题发现/与 GDM 智能卡身份验证的关系
SUSE 的同事 Matthias Gerstner 在他的 openSUSE Tumbleweed 桌面系统上使用 YubiKey 进行登录。2024 年 10 月,他注意到他的 GDM 登录设置中行为发生了变化。通过深入研究,他发现某些情况下无需输入密码或使用 YubiKey 即可登录。
在分析问题时,我们发现 GDM 3 中存在一个 Bug(或特性?),该 Bug 会将 YubiKeys 视为智能卡。这是其中一个相关因素。许多 Linux 发行版使用专用的“gdm-smartcard”PAM 堆栈配置文件,用于 GDM 中的智能卡登录。在 openSUSE 上,我们在此 gdm-smartcard PAM 堆栈中依赖 pam_pkcs11 作为唯一的(正确)身份验证模块。我们花了些时间才弄清楚 gdm-smartcard PAM 堆栈到底在 GDM 中是如何使用的。选择此 PAM 堆栈的逻辑位于一个完全不同的 Gnome 组件中,即 gnome-shell。在那里,一些 JavaScript 负责通过 gnome-settings-daemon 的 D-Bus 接口检测智能卡,并更改 GDM 的身份验证模式。
我们通过在 QEMU 虚拟机中使用智能卡仿真来重现这种情况,以便能够正确检测 GDM 和 pam_pkcs11 中的智能卡。一旦系统正确设置了智能卡,GDM 就会切换到智能卡身份验证模式(默认启用支持)。显示管理器中不再显示用户列表,而是必须手动输入用户名。输入用户名后,将执行“gdm-smartcard”PAM 堆栈,随之是 pam_pkcs11。系统不会询问密码,登录也会成功。
在此测试设置以及使用 YubiKey 的实际设置中,pam_pkcs11 在记录“Failed to initialize crypto”后停止执行,令人惊讶的是,登录仍然成功。这背后的原因在于 pam_pkcs11,如下一节所述。
我们没有调查“initialize crypto”错误究竟为何发生,因为我们认为它与安全问题无关。即使发生错误,PAM 堆栈执行的结果也不应该允许在不提供凭据的情况下进行身份验证。
3) pam_pkcs11 中的 PAM_IGNORE 问题
pam_pkcs11 在没有适当身份验证的情况下成功登录源于 pam_pkcs11 版本 0.6.12 中的一项更改。此问题已通过 commit bac6cf8 引入(请注意,上游 Git 存储库似乎存在一个伪像:在“master”上的提交日志中存在一个看似相同的 commit 88a87d5)。
通过此更改,pam_sm_authenticate() 函数的许多退出路径现在返回 PAM_IGNORE,而不是 PAM_CRED_INSUFFICIENT。特别是,第 284 行的代码意味着在错误条件下,如果不存在“登录令牌名称”,则默认返回值是 PAM_IGNORE。
if (!configuration->card_only || !login_token_name) {
/* Allow to pass to the next module if the auth isn't
restricted to card only. */
pkcs11_pam_fail = PAM_IGNORE;
} else {
pkcs11_pam_fail = PAM_CRED_INSUFFICIENT;
}
card_only 标志是指 模块参数,其含义似乎随着时间的推移而改变,并且不再完全符合文档说明。它在“gdm-smartcard”堆栈中启用,因此 if 条件的这部分不会触发。login_token_name 的背景是,PAM 模块包含用于解锁屏幕保护程序的特殊逻辑,但仅当会话登录是使用 pam_pkcs11 执行的。在初始登录期间,这始终为 false,因此 if 条件的这部分在这种情况下适用。
当 PAM 模块返回 PAM_IGNORE 时,其结果不应被用于确定 PAM 堆栈执行的结果。openSUSE 在其“gdm-smartcard”配置中为 pam_pkcs11 使用 required 控制设置。在扩展 PAM 语法中,required 表示为:
required
[success=ok new_authtok_reqd=ok ignore=ignore default=bad]
当 pam_pkcs11 返回 PAM_IGNORE 时,“required”控制设置将不再产生普通管理员所期望的结果,即如果无法进行成功的智能卡身份验证,身份验证将失败。
发生的情况取决于 PAM 堆栈的“auth”部分中存在的其他模块。如果堆栈中根本没有其他 PAM 模块,则身份验证失败,因为 PAM 库期望堆栈中的任何模块至少有一个决定性的返回值。如果堆栈中存在另一个实际进行身份验证的 PAM 模块,那么该模块将在未提供凭据时设置失败状态,从而防止成功登录。
为了评估“gdm-smartcard”PAM 堆栈的情况,让我们更仔细地查看其“auth”部分:
auth requisite pam_faillock.so preauth
auth required pam_pkcs11.so wait_for_card card_only
auth required pam_shells.so
auth requisite pam_nologin.so
auth optional pam_permit.so
auth required pam_env.so
auth [success=ok default=1] pam_gdm.so
auth optional pam_gnome_keyring.so
配置了许多其他模块,但遗憾的是,没有一个模块实际进行身份验证。这些就是我们在本次讨论中称之为“实用模块”的模块:它们提供支持功能。例如,pam_faillock 模块检查是否发生了过多的身份验证错误,或者 pam_gnome_keyring 模块尝试拦截用于登录的明文密码,以透明地解锁用户密钥环。通常,此类模块在大多数情况下返回 PAM_SUCCESS。因此,当 pam_pkcs11 返回 PAM_IGNORE 时,PAM 身份验证的总体结果将变为 PAM_SUCCESS,这由“gdm-smartcard”PAM 堆栈中的非身份验证模块提供。
在上述 pam_sm_authenticate() 函数的代码位置下方,只有两个代码路径返回的值不是 PAM_IGNORE:
这两个返回路径都会将 pkcs11_pam_fail 重置为安全的 PAM_AUTH_ERR 值。以下是所有将返回 PAM_IGNORE 的返回路径列表:
- 第 305 行:如果用户是从远程登录,或者可以控制 DISPLAY 环境变量(例如在 sudo 上下文中)。
- 第 316 行:如果
crypto_init()调用失败。 - 第 328 行:如果检测到屏幕保护程序上下文且未记录登录令牌,则会执行显式跳转到
PAM_IGNORE返回。 - 第 343、357 行:如果加载或初始化 PKCS#11 模块失败。
- 第 374 行:如果找不到配置的令牌并且未设置
card_only。考虑到card_only的语义,这可能没问题,但仍然很奇怪。如果系统管理员希望 pam_pkcs11 身份验证是可选的,他们可以通过使用 PAM 堆栈配置来实现,使用optional控制设置。通过一个看似无害的模块选项如此剧烈地改变模块返回值的语义是不寻常的。 - 第 416 行:即使在可能等待之后仍找不到智能卡。如果找到智能卡,但 PKCS#11 库函数或证书检查中的任何一项失败,则可能会发生进一步的
PAM_IGNORE返回,如果以下任何操作失败:open_pkcs11_session()(第 432 行)get_slot_login_required()(第 443 行)- 读取密码时失败 (第 471 行)
- 读取了空密码,但未设置
nullok(第 486 行) get_certificate_list()(第 522 行)pam_set_item(..., PAM_USER, ...)(第 597 行)match_user()(第 613 行)(未找到匹配的证书)(第 634 行)get_random_value()(第 663 行)sign_value()(第 677 行)close_pkcs11_session()(第 776 行)
正如这个长列表所示,本地攻击者很可能能够触发 pam_pkcs11 中的 PAM_IGNORE 返回值。对于物理攻击者来说,最简单的方法是将任意智能卡插入现有读卡器,或将外围智能卡设备连接到系统。配置了的 pam_pkcs11 模块将尝试访问智能卡:如果访问失败,则模块返回 PAM_IGNORE,从而可能导致身份验证绕过。
4) 受影响的发行版和配置
该问题是在 2021 年 7 月发布的 pam_pkcs11 版本 0.6.12 中引入的。任何依赖 pam_pkcs11 作为唯一身份验证因素的 PAM 堆栈都将受到该问题的影响。
在 openSUSE Tumbleweed 上,该问题仅由于 GDM 中的上述更改而变得明显,这些更改在某些情况下会将 YubiKeys 视为智能卡。我们认为,在 GDM 作为显示管理器的情况下,在 openSUSE Tumbleweed 上插入任何类型的、不匹配的智能卡(或 YubiKey)都将允许绕过登录。
如果其他 Linux 发行版启用了 GDM 智能卡登录并自动检测智能卡,也可能发生类似情况。即便如此,要触发该问题,仍然需要存在受影响的“gdm-smartcard”PAM 堆栈。GDM 存储库中可以找到依赖 pam_pkcs11 的 gdm-smartcard PAM 堆栈:
我们尝试在 Arch Linux 上重现该问题。在那里,gdm-smartcard PAM 堆栈随 GDM 一起安装,但在标准存储库中没有 pam_pkcs11 包。不过,可以从 AUR 安装。安装后,并同时安装 gdm 和 ccid 包,该问题基本上也可以被利用。然而,我们只用一个定制的 sudo PAM 堆栈对此进行了测试,因为我们未能让 gDM 在 Arch Linux 上进入智能卡身份验证模式。似乎仍然缺少一些触发该功能的组件。
在 Arch Linux 上,我们还注意到 AUR pam_pkcs11 包没有在 /etc 中放置任何默认的“pam_pkcs11.conf”文件。这也避免了安全问题,因为当 slot_num 设置未配置为其内置默认值 -1 时,pam_sm_authenticate() 将提前以 PAM_AUTHINFO_UNAVAIL 返回。然而,在 openSUSE 上,我们确实提供了一个默认配置 slot_num = 0。
当前的 Fedora Linux 不再使用 pam_pkcs11 进行智能卡身份验证(而是使用 pam_sss)。较旧版本的 Fedora 可能仍会受到影响。
5) 可能的解决方法
为了防止登录绕过,一个快速的解决方法是使用以下 PAM 堆栈配置行,而不是在第 3 部分中看到的“gdm-smartcard”PAM 堆栈配置行:
auth [success=ok default=bad] pam_pkcs11.so wait_for_card card_only
与在第 3 部分中看到的 required 控制设置中的 ignore=ignore 不同,PAM 库将 ignore(实际上是除 success 之外的任何结果)视为身份验证堆栈的不良结果。这将导致身份验证失败,即使 pam_pkcs11 返回 PAM_IGNORE。
6) Bug修复
经过关于问题性质和潜在兼容性问题的广泛讨论后,上游项目提出了一个相当直接的 Bug 修复,该修复可在 commit 2ecba68d40 中找到。基本上,PAM_IGNORE 返回值已再次更改为 PAM_CRED_INSUFFICIENT。
此 Bug 修复是 上游版本 0.6.13 的一部分,该版本还修复了 PAM 模块中的另一个独立发现的漏洞。
7) 吸取的教训
我们未能从 PAM 管理员或开发人员文档中找到关于 PAM_IGNORE 正确用法的任何明确建议。因此,我们试图在本节中概述当前情况和建议的最佳实践。
关于 PAM_IGNORE 的使用
由于关于 pam_pkcs11 因使用 PAM_IGNORE 而是否应负责存在疑问,我们对 openSUSE 中打包的其他 PAM 模块进行了调查。我们发现了一个 PAM 模块 pam_u2f,它在错误情况下也存在 PAM_IGNORE 的不当使用,并且我们已经在之前的报告中发布了该问题。该报告已导致在 oss-security 邮件列表上讨论了在实现 PAM 模块时可能存在的结构性问题。
除此以外,我们还发现了以下 PAM_IGNORE 的用法:
核心 PAM 模块
- pam_wheel:这只是一个过滤器模块,非
root用户将被拒绝,而root用户则返回PAM_IGNORE;实际的身份验证决定由其他模块做出。 - pam_sepermit:如果用户不在配置文件列出的列表中,则返回
PAM_IGNORE。 - pam_lastlog:如果无法读取 lastlog 文件(位于特权位置),则使用
PAM_IGNORE。 - pam_userdb:如果没有配置数据库,则返回
PAM_IGNORE。 - pam_listfile:如果即将登录的用户不符合配置的标准,则返回
PAM_IGNORE。
第三方 PAM 模块
- pam_google_authenticator:如果没有状态文件且传递了
nullok选项,则返回PAM_IGNORE。 - nss-pam-ldapd:如果用户未知或没有可用的身份验证信息,则返回
PAM_IGNORE,但前提是明确配置如此(cfg->ignore_authinfo_unavail,cfg->ignore_unknown_user)。 - pam_krb5
- 如果用户未知,则返回
PAM_IGNORE,但前提是设置了options->ignore_unknown_principals。 - 如果配置了
minimum_uid且用户不匹配,则返回PAM_IGNORE。
- 如果用户未知,则返回
- pam_radius:如果网络不可用并且通过
localifdown选项明确配置了 ignore,则返回PAM_IGNORE。 - pam_yubico:如果没有用户的令牌且传递了
nullok选项,则返回PAM_IGNORE。
从这个列表可以看出,大多数 PAM 模块只有在通过配置选项或特权配置文件中的设置明确选择加入时才会返回 PAM_IGNORE。大多数时候,返回值的含义是身份验证机制根本没有配置,或者没有为正在进行身份验证的用户配置。只有当相关模块是可选的身份验证机制,并且堆栈中存在备用 PAM 模块进行身份验证时,才能安全地使用此类配置。
从 pam_pkcs11 和 pam_u2f 中看到的这些问题,我们认为 PAM 模块实现特别注意不要在不清楚的错误情况下使用 PAM_IGNORE,因为本地或物理存在的攻击者可能会触发它们。
关于 PAM_SUCCESS 的使用
只提供实用功能的 PAM 模块,而不实际进行身份验证的模块,可以考虑不返回 PAM_SUCCESS,而是返回 PAM_IGNORE。这将避免在此报告所述情况下发生意外的成功身份验证。PAM 模块作者似乎自然地认为,如果其模块中没有任何内容失败,就应返回 PAM_SUCCESS。许多模块都是这样工作的,改变它们将是一项艰巨的任务。
保守的 PAM 堆栈配置
遗憾的是,PAM 对于非开发人员来说可能很难理解,甚至对于 PAM 模块作者来说也是如此。管理员和集成商在编写 PAM 堆栈时尤其需要谨慎,特别是当使用不太常见的 PAM 模块作为唯一的身份验证要求时。在这种情况下,可以使用像我们建议的解决方法那样使用扩展 PAM 语法来进行加固,以确保不会出现意外的身份验证结果。
8) 时间线
| 2024-11-06 | 在 pam_pkcs11 或 OpenSC 项目中没有记录维护者、安全联系人或披露流程。为了找到一个合适的上游联系人,我们联系了 Ludovic Rousseau,他是 pam_pkcs11 的贡献者,也是 GitHub 上 OpenSC 组织的成员。 |
| 2024-11-06 | Ludovic 回复说他已不再活跃于该项目,并指出了公开报告问题的途径,而我们此时宁愿不使用。 |
| 2024-11-07 | 我们联系了另一位 pam_pkcs11 的近期贡献者 Paul Wolneykien,并征求指导。 |
| 2024-11-07 | Paul 回复说 Ludovic 将是合适的维护者,Frank Morgner 是备用。他还指出了(公开的)opensc 开发人员邮件列表。 |
| 2024-11-08 | 仍然没有明确的联系人,我们在 opensc 开发人员邮件列表中公开征求了安全联系人。 |
| 2024-11-08 | 作为对我们问题的回应,OpenSC 项目的 Frank Morgner 在 pam_pkcs11 GitHub 存储库中启用了私密安全报告。 |
| 2024-11-11 | 我们使用现在可用的 GitHub 私密问题报告分享了我们的报告,提供了协调披露和最多 90 天的禁运期。 |
| 2024-11-12 | 几位上游开发者加入了私密 GitHub 问题,并开始进行各种讨论。 |
| 2024-11-13 | 由于对 PAM_IGNORE 的正确使用以及 pam_pkcs11 的正确修复方法存在不确定性,我们建议提前发布该问题以进行公开讨论。 |
| 2024-11-17 | 关于发布问题的意见不一,因此此时未能达成一致。未确定计划的发布日期。 |
| 2024-11-20 | 在研究其他 PAM 模块及其 PAM_IGNORE 的使用时,我们发现 pam-u2f 模块也存在类似问题。我们已将该问题报告给 Yubico 上游,请参阅我们之前的报告。 |
| 2024-11-26 | linux-pam 开发者 Dmitry V. Levin 加入了讨论,以判断 pam_pkcs11 中 PAM_IGNORE 的使用是否存在问题。他表示,切换到 PAM_IGNORE 在最终用户不知道行为变化时会产生问题。 |
| 2024-12-05 | 在没有明确前进方向的情况下,我们建议尽快与 linux-distros 邮件列表共享报告以取得进展。但未能就发布达成一致。 |
| 2025-01-07 | 上游开发者讨论了修复该问题的补丁,但自 12 月 12 日以来沟通中断。我们再次询问了发布报告和 Bug 修复的前进方向。 |
| 2025-01-13 | 上游要求我们申请 CVE。我们从 Mitre 申请了,但申请卡住了近两周。 |
| 2025-01-14 | 衍生的 pam-u2f 问题已发布。不幸的是,这个问题先于 pam_pkcs11 问题发布,因为我们当时无法公开讨论涉及 pam_pkcs11 的更大范围。 |
| 2025-01-20 | 一位上游开发者表示,存在一个包含 Bug 修复的私有分支,并询问是否应将其发布。我们要求在未就日期和流程达成一致的情况下不要发布任何内容。 |
| 2025-01-23 | Mitre CVE 申请问题已解决,并为其分配了 CVE-2025-24531。我们在私密上游问题中分享了这个 CVE。 |
| 2025-01-23 | 我们再次要求协调发布日期,并建议于 1 月 30 日与 linux-distros 邮件列表共享该问题,并于 2 月 6 日进行公开出版。 |
| 2025-01-24 | 就建议的发布日期达成了普遍一致。 |
| 2025-01-30 | 我们与 linux-distros 邮件列表共享了报告和 Bug 修复,并通知禁运期直至 2 月 6 日出版。 |
| 2025-02-06 | 上游按计划发布了 Bug 修复版本 0.6.13。 |