支持MFA的PAM模块开发:SSHD多凭证验证失败问题求助
问题:SSH PAM模块多凭证(密码+OTP)验证失败,无第二次输入提示
我正在开发一款对接HTTPS服务的PAM模块,目前仅编写测试版本验证流程,未接入实际业务逻辑。测试代码如下:
static const char *valid_username = "xrfang"; static const char *valid_password = "password"; static const char *valid_otpcode = "123456"; PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *username; char *password = NULL; char *otpcode = NULL; int rc = PAM_AUTH_ERR; openlog("pam_ums", LOG_PID | LOG_CONS, LOG_AUTH); if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) { syslog(LOG_ALERT, "Failed to get username"); goto done; } syslog(LOG_ALERT, "username:>%s<", username); if (pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &password, "Password: ") != PAM_SUCCESS) { syslog(LOG_ALERT, "Failed to get password"); goto done; } syslog(LOG_ALERT, "password:>%s<", password); if (pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &otpcode, "OTP Code: ") != PAM_SUCCESS) { syslog(LOG_ALERT, "Failed to get otp code"); goto done; } syslog(LOG_ALERT, "otpcode:>%s<", otpcode); if (strcmp(username, valid_username) != 0) { syslog(LOG_ALERT, "invalid username: given=%s; wanted=%s;", username, valid_username); goto done; } if (strcmp(password, valid_password) != 0) { syslog(LOG_ALERT, "invalid password: given=%s; wanted=%s;", password, valid_password); goto done; } if (strcmp(otpcode, valid_otpcode) != 0) { syslog(LOG_ALERT, "invalid otpcode: given=%s; wanted=%s;", otpcode, valid_otpcode); goto done; } rc = PAM_SUCCESS; syslog(LOG_ALERT, "Authentication successful"); done: closelog(); if (password) free(password); if (otpcode) free(otpcode); return rc; }
将该模块配置到/etc/pam.d/sshd:
# PAM configuration for the Secure Shell service auth sufficient pam_ums.so
问题现象
未添加OTP码提示时认证正常,添加第二次OTP提示后认证失败,SSH未弹出OTP输入框,日志如下:
Feb 5 17:49:15 office pam_ums[1145092]: username:>xrfang< Feb 5 17:49:15 office pam_ums[1145092]: password:>password< Feb 5 17:49:15 office pam_ums[1145092]: pam_ums(sshd:auth): conversation failed Feb 5 17:49:15 office pam_ums[1145092]: Failed to get otp code Feb 5 17:49:15 office sshd[1145092]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.93.200 user=xrfang Feb 5 17:49:17 office sshd[1145092]: Failed password for xrfang from 192.168.93.200 port 47670 ssh2
需求:希望在单个PAM模块内实现多凭证验证,不想拆分用两个模块(比如类似Google Authenticator的方式)。
解决方案
1. 开启SSH的Challenge-Response支持
SSH默认可能限制单次PAM认证的交互次数,修改/etc/ssh/sshd_config确保以下配置:
ChallengeResponseAuthentication yes UsePAM yes
修改后重启sshd:
systemctl restart sshd
2. 替换pam_prompt为底层pam_conv函数
pam_prompt是简化封装,在多交互场景下兼容性较差,改用底层对话接口更可靠。修改OTP获取部分的代码:
struct pam_message msg[1]; struct pam_response *resp = NULL; const struct pam_message *pmsg[1]; int rc; // 构建OTP提示消息 msg[0].msg_style = PAM_PROMPT_ECHO_ON; msg[0].msg = "OTP Code: "; pmsg[0] = &msg[0]; rc = pam_conv(pamh, 1, pmsg, &resp); if (rc != PAM_SUCCESS) { syslog(LOG_ALERT, "Failed to get otp code via pam_conv"); goto done; } otpcode = resp[0].resp; // 注意:此处不要手动free(resp),PAM框架会负责释放;若需释放需调用pam_free
3. 调整PAM规则的优先级
确保你的模块是第一个被调用的认证模块,避免其他模块(如pam_unix)提前拦截或干扰交互。可以修改/etc/pam.d/sshd:
auth sufficient pam_ums.so auth required pam_unix.so nullok_secure
如果使用sufficient,确保模块失败后不会直接跳过后续验证,或者根据需求调整为required(但会强制所有模块通过)。
4. 验证客户端支持
某些SSH客户端可能不支持多次Challenge-Response交互,使用ssh -v xrfang@your-server查看详细日志,确认客户端是否接收到第二次提示。如果是客户端问题,建议使用标准OpenSSH客户端测试。
5. 调试PAM交互流程
添加更详细的日志跟踪pam_conv的返回值,或者临时启用PAM调试日志:
echo "debug" >> /etc/pam.d/sshd
重启sshd后查看系统日志,确认PAM对话的每一步是否正常执行。
内容的提问来源于stack exchange,提问作者xrfang
相关产品推荐
相关产品推荐

