OpenSSH的PermitRootLogin配置问题分析

因为要在 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-auth
lrwxrwxrwx. 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 Root
PermitRootLogin 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