我正在尝试让 ssh 以一种可以使用密钥跳过密码验证的方式工作,此外,在新安装的 debian 9 上,每次登录都会使用 google 的 libpam 进行 totp 跟进。
到目前为止,我已经能够使第一部分工作,如果我提供一个密钥,服务器就会要求我输入 otp,但是现在的情况是,我不得不将两者注释掉,@include common-auth
并@include common-password
抑制密码提示/etc/pam.d/sshd
。
很明显,如果我
AuthenticationMethods publickey,keyboard-interactive:pam password,keyboard-interactive:pam
在 sshd_config 中执行此操作并尝试在没有密钥的情况下登录,那么我提供什么密码都无关紧要,因为密码检查部分已被注释掉。
对于我这样的新手来说,解决这个问题的合理方法是定义不同的 pam 方法或类,然后以某种方式在我的 sshd_config 中引用它们,但我似乎找不到有关此类操作的任何信息。
有可能完成这个特殊的组合吗?
编辑1:
进一步修改这一点,它确实没有我最初想象的那么有意义。如果我注释掉@include common-auth
和@include common-password
,我就可以publickey,keyboard-interactive:pam
不要求输入密码。如果我现在AuthenticationMethods password
为特定用户设置,该用户根本无法登录,因为每个密码都被拒绝,即使它真的是有效的。所以从逻辑上讲,似乎 sshd password
auth 方法也使用/etc/pam.d/sshd
配置。如果我不注释掉那些包含的内容,keyboard-interactive:pam
会要求输入密码和验证码,但password
对于任何已初始化 otp 的用户,auth 方法仍然会失败(并且除了我给 google libpam nullok 选项之外,其他用户都会失败)。似乎password
只是一个蹩脚的版本keyboard-interactive:pam
,只能提示一个输入,因此如果需要输入多个,它总是会失败。
如果我编写自己的 pam.d 模块,有没有办法让 ssh 使用它/etc/pam.d/sshd
?
编辑2:
我开始认为我做不到(password && otp) || (publickey && otp)
,因为公钥是在不同的地方检查的,所以除非我可以定义要使用哪个 pam 配置AuthenticationMethods
,或者我可以以某种方式将参数/参数发送到 pam 模块,否则知道何时检查两者以及何时只检查 otp 似乎是不可能的
答案1
该问题源于 PAM,它几乎使这一切变得不可能。
核心问题是公钥部分由 ssh 守护进程处理,而键盘交互始终由 PAM 处理(并且密码也经常由 PAM 处理,具体取决于配置)。因此,PAM 无法知道是否已设置公钥,并且无法将 ssh 配置为根据是否使用公钥来随意选择不同的 AuthenticationMethods。
但是,当设置了公钥并验证为已批准时,ssh 会设置环境变量 SSH_AUTH_INFO_0,并且 PAM 可以看到此变量。因此,您要做的就是编写一个 pam_exec 脚本,该脚本的退出方式取决于 SSH_AUTH_INFO_0 是否设置为某个值。并且 pam_exec 可以使用 success=1 跳过一行(如果需要,可以跳过多行,在我修改它之前,RHEL8 上的代码已经是意大利面条式代码了,这确实使事情变得更加复杂)。
我使用了一个 perl 脚本:
#!/usr/bin/perl
if ($ENV{'SSH_AUTH_INFO_0'} =~ /^publickey /) {
exit(0);
} else {
exit(1);
}
并且它使用此行调用(success=2 是因为预先存在的代码,我不得不跳过 pam_sss.so 后面的 pam_deny(它是“足够的”并且没有遇到拒绝)。是否使用“ignore”或“ok”取决于 sshd 的其余 PAM 堆栈。(并且在某些情况下,如果您在输入密码之前提示输入 OTP,您可能能够使用“success=done”,尽管当我认为它应该工作时,我无法让它工作。)
auth [success=2 default=ignore] pam_exec.so quiet /usr/local/sbin/ssh-pubkey-check
您还需要在 sshd_config 中设置这行有点奇怪的内容:
AuthenticationMethods publickey,keyboard-interactive keyboard-interactive
它首先尝试公钥和键盘交互。如果成功,PAM 将从环境变量中得知它可以完全跳过密码提示。如果失败(因为没有提供公钥),它接下来只尝试键盘交互,但在这种情况下,PAM 将提示输入 OTP(留给学生练习)和密码提示。
还有更多内容,但这一切都取决于您现有的 PAM 设置是什么样的,以及您要根据什么进行身份验证(本地密码文件、LDAP、NIS 等等),所以我无法提供一刀切的解决方案。
还要注意,使用环境变量来控制身份验证步骤感觉不太好,理论上可能会导致只需要 OTP 而不需要其他令牌的漏洞。尽管在我能想到的每种情况下,如果有人可以远程或本地设置 PAM 从 sshd 调用时看到的环境变量,那么您的系统已经受到攻击,这不是额外的风险。
答案2
您在 sshd_config 中寻找的参数是AuthenticationMethods
。我写了一个博客文章关于之前将 SSH 密钥认证与 OTP 结合起来。
但是,这篇博文使用带有 pam 模块的 OTP 来对抗 privacyIDEA 身份验证系统。但您应该能够轻松地将其与 google pam 进行交换。
答案3
如果您有许多其他安全层,并且只想将这一功能用于单个帐户/密钥组合,您可以尝试这种肮脏的黑客攻击……这是一个非常糟糕的主意,您不应该这样做。一些明显的风险将在底部列出。但如果您只是在一个高度隔离的测试网络中进行零风险的修补,我想您可以尝试这个:
(password && otp) || (publickey)
按照许多教程进行设置这个.然后向您的公钥方法添加一个强制命令来验证 OTP。
.ssh/authorized_keys
像这样在前面添加你的密钥:
restrict,pty,command="/usr/local/bin/promptOtpShell" ssh-rsa AAAAB3NzaC1yc2E...
然后,在/usr/local/bin/promptOtpShell
:
#!/bin/bash
if [ -n "$SSH_ORIGINAL_COMMAND" ]; then
logger -p auth.error -t ${0##*/} "error: OTP: Subsystem refused for ${LOGNAME} from ${SSH_CLIENT%% *}"
exit 1
fi
retry=3
key=$(head -1 ~/.google_authenticator)
while [ "$retry" -gt 0 ]; do
read -p "OTP: " -n 6 -t 120 -s otp
echo
if echo -e "$key\n$otp" | oathtool -b --totp -w 2 - - >/dev/null 2>&1; then
exec -la "${SHELL##*/}" "$SHELL"
fi
((retry-=1))
done
logger -p auth.error -t ${0##*/} "error: OTP: Authentication failure for ${LOGNAME} from ${SSH_CLIENT%% *}"
请务必这样做apt install oathtool
,不要忘记chmod 755 /usr/local/bin/promptOtpShell
。
风险:
- 环境变量的可信度高于安全性
- 如果启用,端口、代理和 X11 转发将在 OTP 验证之前被允许(这就是我们
restrict
在此示例中的原因) - 如果您采用这种方法来从
/etc/sshd_config
ForceCommand 使用,那么.ssh/authorized_keys
您必须限制所有匹配连接的转发 - 聪明的人几乎肯定会在 20 分钟或更短的时间内列出另外一些问题