因为要在 CentOS 7.6 上进行漏洞修复,就自行编译了 OpenSSH RPM,A产品上验证通过,但是B产品在拿到RPM按步骤升级后却出现了问题,新开 ssh 会话无法使用 root 用户进入后台,只能靠老会话维持排查问题。那么到底是什么原因导致的呢?
问题介绍 OS: CentOS 7.6.1810 OpenSSH版本:OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017
经过漏扫工具进行扫描后需要进行漏洞修复,这里选择自行编译最新的 OpenSSH RPM 进行版本升级,并在A产品进行业务验证。A产品上校验通过,但使用相同 OS 的B产品上却出现了非常严重的问题,在操作后新开 ssh 会话无法使用 root 用户进入后台,只能靠老会话维持排查问题。
通过老会话可以查看到如下信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [root@localhost]# systemctl status sshd ● sshd.service - SYSV: OpenSSH server daemon Loaded: loaded (/etc/rc.d/init.d/sshd; bad; vendor preset: enabled) Active: active (running) since Mon 2026-05-04 22:34:55 CST; 49min ago Docs: man:systemd-sysv-generator(8) Process: 9262 ExecStart=/etc/rc.d/init.d/sshd start (code=exited, status=0/SUCCESS) Main PID: 9284 (sshd) Tasks: 1 Memory: 11.0M CGroup: /system.slice/sshd.service └─9284 sshd: /usr/sbin/sshd [listener] 0 of 10-100 startups May 04 22:34:55 localhost systemd[1]: Starting SYSV: OpenSSH server daemon... May 04 22:34:55 localhost sshd[9284]: Server listening on 0.0.0.0 port 22. May 04 22:34:55 localhost sshd[9284]: Server listening on :: port 22. May 04 22:34:55 localhost sshd[9262]: Starting sshd:[ OK ] May 04 22:34:55 localhost systemd[1]: Started SYSV: OpenSSH server daemon. May 04 22:36:56 localhost sshd[9284]: Timeout before authentication for connection from 192.168.0.133 to 192.168.0.105, pid = 9590 May 04 22:46:21 localhost sshd-session[10584]: Accepted keyboard-interactive/pam for develop from 192.168.0.105 port 54452 ssh2 May 04 23:24:31 localhost sshd-session[11113]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=127.0.0.1 user=root May 04 23:24:31 localhost sshd-session[11113]: pam_succeed_if(sshd:auth): requirement "uid >= 1000" not met by user "root" May 04 23:24:32 localhost sshd-session[11111]: error: PAM: Authentication failure for root from 127.0.0.1
通过如上信息可以看到,PAM 模块显示,当前只能 uid 大于等于1000的普通用户登录。如果是初次看,很容易被引导到 PAM 配置上的问题,我们也确实被引导到这个路线上排查了一段时间。
分析PAM相关配置 按照提示信息,查看PAM相关的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@localhost]# grep -ni "pam_succeed_if" /etc/pam.d/*ac /etc/pam.d/fingerprint-auth-ac:10:account sufficient pam_succeed_if.so uid < 1000 quiet /etc/pam.d/fingerprint-auth-ac:18:session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid /etc/pam.d/password-auth-ac:7:auth requisite pam_succeed_if.so uid >= 1000 quiet_success /etc/pam.d/password-auth-ac:12:account sufficient pam_succeed_if.so uid < 1000 quiet /etc/pam.d/password-auth-ac:24:session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid /etc/pam.d/postlogin-ac:6:session [success=1 default=ignore] pam_succeed_if.so service !~ gdm* service !~ su* quiet /etc/pam.d/smartcard-auth-ac:10:account sufficient pam_succeed_if.so uid < 1000 quiet /etc/pam.d/smartcard-auth-ac:18:session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid /etc/pam.d/system-auth-ac:8:auth requisite pam_succeed_if.so uid >= 1000 quiet_success /etc/pam.d/system-auth-ac:13:account sufficient pam_succeed_if.so uid < 1000 quiet /etc/pam.d/system-auth-ac:23:session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid [root@localhost]# ls -lrt /etc/pam.d/password-authlrwxrwxrwx. 1 root root 16 Apr 16 2023 /etc/pam.d/password-auth -> password-auth-ac
其中和 password 有关的是 password-auth-ac:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [root@localhost]# cat /etc/pam.d/password-auth-ac # %PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so auth required pam_faildelay.so delay=2000000 auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth required pam_deny.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 1000 quiet account required pam_permit.so password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type= password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so
那么,正常通过 root 登录的流程大概是下面的链路:
1 2 3 4 5 sshd -> /etc/pam.d/sshd -> auth substack password-auth -> /etc/pam.d/password-auth -> /etc/pam.d/password-auth-ac
然后在 password-auth-ac 上逐行匹配条件,依次执行。直到
1 auth sufficient pam_unix.so nullok try_first_pass
这里 pam_unix.so 会验证本地 /etc/shadow 里的 root 密码。如果 root 密码正确,pam_unix.so 返回成功。由于它的控制标志是 sufficient。如果本模块认证成功,并且前面没有 required 模块失败,那么整个认证栈可以直接认为成功,后面的模块不再继续执行。
所以如果 root 密码正确,后面这句 uid 判断不会触发执行。
1 auth requisite pam_succeed_if.so uid >= 1000 quiet_success
但在A、B两个产品上都没有修改过 password-auth-ac,且和刚装完 OS 的内容一致。那么到底什么因素影响的呢?
故障解除 回来重新分析下问题,现状是两个:
A产品在升级前后都能够正常使用
B产品在没有升级OpenSSH之前,root是能够正常使用的,只是升级后才出现了root无法登录
到这里需要修改下怀疑方向,大概率问题还是在 ssh 的配置有所区别,导致表现不一样。
A产品的 ssh_config 默认使用了自定义内容,会对初始化安装的配置文件进行覆盖:
1 2 3 [root@localhost]# cat /etc/ssh/sshd_config | grep RootPermitRootLogin yes # the setting of "PermitRootLogin without-password" .
B产品的 ssh_config 默认使用了 CentOS 7.6 初始化安装完的状态:
1 2 3 [root@localhost]# cat /etc/ssh/sshd_config | grep Root# PermitRootLogin yes # the setting of "PermitRootLogin without-password" .
在B产品的配置上,将 PermitRootLogin 调整为 yes,重启 sshd 服务后故障解除。新开 ssh 会话能够正常使用 root 用户登录后台。
问题根因 但这里要思考一个问题,在B产品上没有修改过 sshd_config、password-auth-ac 配置,升级后出现的问题到底是由谁导致的?为什么把 PermitRootLogin yes 打开就恢复了?
这里使用虚拟机进行一个故障模拟,在一台初始化安装完的 CentOS 7.6机器上,查看信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 [root@localhost]# cat /etc/ssh/sshd_config | grep Root# PermitRootLogin yes # the setting of "PermitRootLogin without-password" . [root@localhost]# sshd -T | grep permit permitrootlogin yes permittty yes permituserrc yes permitemptypasswords no permituserenvironment no permittunnel no permitopen any
其中 sshd -T 会解析所有配置并显示最终生效的参数,用于配置验证、故障排查、安全审计等使用。这里可以看出 permitrootlogin 默认就是 yes。
当升级完 Openssh 版本之后,再次查看信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@localhost]# cat /etc/ssh/sshd_config | grep Root# PermitRootLogin yes # the setting of "PermitRootLogin without-password" . [root@localhost]# sshd -T | grep permit permitrootlogin without-password permittty yes permituserrc yes permitemptypasswords no permittunnel no permitopen any permitlisten any permituserenvironment no
可以看到 PermitRootLogin 已经变成了 without-password。这个值和prohibit-password等价,代表root 可以使用公钥登录,但不能使用密码登录,也不能使用 keyboard-interactive/PAM 交互式密码登录。
到这里,就可以得出第一个结论:
在升级 OpenSSH 版本后,对 PermitRootLogin 的默认行为发生了改变。由 yes 变为了 without-password,导致新开 ssh 会话无法使用 root 用户登录。
谁改了PermitRootLogin默认行为 经过上面的分析,可以继续深究的问题是,PermitRootLogin 的默认行为发生了改变,由 yes 变为了 without-password,是什么时候发生的事?
通过查找资料,在 OpenSSH 7.0 release Changelog 中可能不兼容的变更说明里找到了如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 OpenSSH 7.0 was released on 2015-08-11 Potentially-incompatible Changes -------------------------------- ..... * The default for the sshd_config(5) PermitRootLogin option has changed from "yes" to "prohibit-password". * PermitRootLogin=without-password/prohibit-password now bans all interactive authentication methods, allowing only public-key, hostbased and GSSAPI authentication (previously it permitted keyboard-interactive and password-less authentication if those were enabled).
但是 OpenSSH 7.0 发布在 2015-08-11,CentOS 7.6.1810默认携带的版本是 OpenSSH 7.4p1版本,OpenSSH 7.4 发布在 2016-12-19。
查看7.4的源代码,可以看出来当没有设置 PermitRootLogin 时,将赋值为 PERMIT_NO_PASSWD,启用的是 without-password/prohibit-password。 代码链接:https://github.com/openssh/openssh-portable/blob/V_7_4_P1/servconf.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... options->permit_root_login = PERMIT_NOT_SET; if (options->permit_root_login == PERMIT_NOT_SET) options->permit_root_login = PERMIT_NO_PASSWD; ... static const struct multistate multistate_permitrootlogin [] = { { "without-password" , PERMIT_NO_PASSWD }, { "prohibit-password" , PERMIT_NO_PASSWD }, { "forced-commands-only" , PERMIT_FORCED_ONLY }, { "yes" , PERMIT_YES }, { "no" , PERMIT_NO }, { NULL , -1 } };
那么早在7.0版本中就完成变更的事,为什么在 CentOS 7.6.1810中,PermitRootLogin 的默认行为仍是 yes呢?
通过进一步查找资料,在CentOS 7的归档仓库中找到了相关的openssh patch,其中有一条是openssh-7.4p1-permit-root-login.patch,该 patch 将上述 OpenSSH 源码中的变更又改了回去…,默认赋值为 PERMIT_YES
代码链接: https://gitlab.com/CentOS/archives/git.centos.org/rpms/openssh/-/blob/c7/SOURCES/openssh-7.4p1-permit-root-login.patch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 diff -up openssh-7.4p1/servconf.c.permit-root openssh-7.4p1/servconf.c --- openssh-7.4p1/servconf.c.permit-root 2017-02-10 10:27:18.109487568 +0100 +++ openssh-7.4p1/servconf.c 2017-02-10 10:28:12.385776132 +0100 @@ -231,7 +231,7 @@ fill_default_server_options(ServerOption if (options->login_grace_time == -1) options->login_grace_time = 120; if (options->permit_root_login == PERMIT_NOT_SET) - options->permit_root_login = PERMIT_NO_PASSWD; + options->permit_root_login = PERMIT_YES; if (options->ignore_rhosts == -1) options->ignore_rhosts = 1; if (options->ignore_user_known_hosts == -1) diff -up openssh-7.4p1/sshd_config.5.permit-root openssh-7.4p1/sshd_config.5 --- openssh-7.4p1/sshd_config.5.permit-root 2017-02-10 10:28:24.174605582 +0100 +++ openssh-7.4p1/sshd_config.5 2017-02-10 10:28:42.254344023 +0100 @@ -1227,7 +1227,7 @@ The argument must be or .Cm no . The default is -.Cm prohibit-password . +.Cm yes . .Pp If this option is set to .Cm prohibit-password diff -up openssh-7.4p1/sshd_config.permit-root openssh-7.4p1/sshd_config --- openssh-7.4p1/sshd_config.permit-root 2017-02-10 10:26:52.256797645 +0100 +++ openssh-7.4p1/sshd_config 2017-02-10 10:26:52.276797405 +0100 @@ -35,7 +35,7 @@ SyslogFacility AUTHPRIV # Authentication: #LoginGraceTime 2m -#PermitRootLogin prohibit-password +#PermitRootLogin yes #StrictModes yes #MaxAuthTries 6 #MaxSessions 10
所以这里可以得出第二个结论:
在CentOS 7上又或者是其上游 Redhat 的原因,在打包 OpenSSH 7.4p1 时打了一个补丁,可能是出于兼容性的原因修改了源码,把默认值又改回了 yes。所以在 CentOS 7 刚装完系统时,即使配置文件里是 #PermitRootLogin yes,它的默认行为依然是允许 root 密码登录。
综上,当我们手动升级到官方版本编译出的制品物料时,从 OpenSSH 7.0 开始的默认行为 PERMIT_NO_PASSWD (即 prohibit-password / without-password) 就在机器上生效了,导致升级完 SSH 版本,PAM 配置一行没动,新开 ssh 会话时 root 就不能用正常登录。
总结 1、OpenSSH 7.0版本开始,对 PermitRootLogin 的默认行为发生了改变。由 yes 变为了 prohibit-password / without-password 2、操作系统的发行版可能会因为兼容性等因素,改变 OpenSSH 的默认行为,配置影响会非常明显,要根据不同发行版具体分析 3、在处理组件升级变更时,还是要多验证分析,对关键配置项进行不同验证,避免出现配置问题影响
参考 1、https://www.openssh.org/txt/release-7.0 2、https://www.openssh.org/txt/release-7.4 3、https://gitlab.com/CentOS/archives/git.centos.org/rpms/openssh/-/blob/c7/SOURCES/openssh-7.4p1-permit-root-login.patch 4、https://github.com/openssh/openssh-portable/blob/V_7_4_P1/servconf.c