OpenSSH
OpenSSH 客户端和服务器的日志配置
OpenSSH 客户端和服务器提供了许多选择,用户可以决定日志写入的位置以及收集的信息量。
记录日志的前提是系统时钟准确,使用网络时间协议(NTP)或类似服务提供持续的时间同步。日志中的时间戳越精确,协调跨主机、站点或服务提供商之间的取证工作就越快捷。如果必须联系外部方(如服务提供商),通常只有在时间非常精确的情况下才能取得进展。
服务器日志
默认情况下,sshd(8)
将日志信息发送到系统日志,使用日志级别 INFO
和系统日志设施 AUTH
。因此,要查看来自 sshd(8)
的日志数据,应该查看 /var/log/auth.log
文件。可以通过 SyslogFacility
和 LogLevel
指令覆盖这些默认设置。以下是授权日志中的典型服务器启动条目:
Mar 19 14:45:40 eee sshd[21157]: Server listening on 0.0.0.0 port 22.
Mar 19 14:45:40 eee sshd[21157]: Server listening on :: port 22.
在大多数情况下,默认的日志级别已足够,但在新服务或活动的初始测试过程中,有时需要更多的信息。调试信息通常会输出到标准错误(stderr)。从 OpenSSH 7.6 开始,可以使用 Match
块为特定条件设置备用的日志级别。
下面的日志摘录显示了相同的服务器启动过程,但包含了更多的细节。对比以下日志级别 DEBUG1
与上面的默认日志:
debug1: sshd version OpenSSH_6.8, LibreSSL 2.1
debug1: private host key #0: ssh-rsa SHA256:X9e6YzNXMmr1O09LVoQLlCau2ej6TBUxi+Y590KVsds
debug1: private host key #1: ssh-dss SHA256:XcPAY4soIxU2IMtYmnErrVOjKEEvCc3l5hOctkbqeJ0
debug1: private host key #2: ecdsa-sha2-nistp256 SHA256:QIWi4La8svQSf5ZYow8wBHN4tF0jtRlkIaLCUQRlxRI
debug1: private host key #3: ssh-ed25519 SHA256:fRWrx5HwM7E5MRcMFTdH95KwaExLzAZqWlwULyIqkVM
debug1: rexec_argv[0]='/usr/sbin/sshd'
debug1: rexec_argv[1]='-d'
debug1: Bind to port 22 on 0.0.0.0.
Server listening on 0.0.0.0 port 22.
debug1: Bind to port 22 on ::.
Server listening on :: port 22.
使用最详细的日志级别 DEBUG3
时,日志内容如下:
debug2: load_server_config: filename /etc/ssh/sshd_config
debug2: load_server_config: done config len = 217
debug2: parse_server_config: config /etc/ssh/sshd_config len 217
debug3: /etc/ssh/sshd_config:52 setting AuthorizedKeysFile .ssh/authorized_keys
debug3: /etc/ssh/sshd_config:86 setting UsePrivilegeSeparation sandbox
debug3: /etc/ssh/sshd_config:104 setting Subsystem sftp internal-sftp
debug1: sshd version OpenSSH_6.8, LibreSSL 2.1
debug1: private host key #0: ssh-rsa SHA256:X9e6YzNXMmr1O09LVoQLlCau2ej6TBUxi+Y590KVsds
debug1: private host key #1: ssh-dss SHA256:XcPAY4soIxU2IMtYmnErrVOjKEEvCc3l5hOctkbqeJ0
debug1: private host key #2: ecdsa-sha2-nistp256 SHA256:QIWi4La8svQSf5ZYow8wBHN4tF0jtRlkIaLCUQRlxRI
debug1: private host key #3: ssh-ed25519 SHA256:fRWrx5HwM7E5MRcMFTdH95KwaExLzAZqWlwULyIqkVM
debug1: rexec_argv[0]='/usr/sbin/sshd'
debug1: rexec_argv[1]='-ddd'
debug2: fd 3 setting O_NONBLOCK
debug1: Bind to port 22 on 0.0.0.0.
Server listening on 0.0.0.0 port 22.
debug2: fd 4 setting O_NONBLOCK
debug1: Bind to port 22 on ::.
每次失败的登录尝试都会被记录下来。一旦超过 MaxAuthTries
指令中的最大认证尝试次数,连接会被断开。以下是一些失败尝试的日志摘录:
Mar 19 11:11:06 server sshd[54798]: Failed password for root from 122.121.51.193 port 59928 ssh2
Mar 19 11:11:06 server sshd[54798]: Failed password for root from 122.121.51.193 port 59928 ssh2
Mar 19 11:11:07 server sshd[54798]: Failed password for root from 122.121.51.193 port 59928 ssh2
Mar 19 11:11:08 server sshd[54798]: Failed password for root from 122.121.51.193 port 59928 ssh2
Mar 19 11:11:09 server sshd[54798]: Failed password for root from 122.121.51.193 port 59928 ssh2
Mar 19 11:11:10 server sshd[54798]: Failed password for root from 122.121.51.193 port 59928 ssh2
Mar 19 11:11:10 server sshd[54798]: error: maximum authentication attempts exceeded for root from 122.121.51.193 port 59928 ssh2 [preauth]
Mar 19 11:11:10 server sshd[54798]: Disconnecting authenticating user root 122.121.51.193 port 59928: Too many authentication failures [preauth]
通常不建议允许 root 登录,特别是通过密码进行认证的 root 登录。禁止 root 的密码认证登录可以大大简化日志分析,尤其是消除了谁在尝试登录以及为什么的耗时问题。需要完全 root 级别访问权限的人可以通过 su(1)
执行一般活动,或者可以通过自定义 sudo(8)
或 doas(1)
条目为特定任务提供这些权限。
成功登录
默认情况下,服务器不会存储太多关于用户事务的信息,这其实是一件好事。当系统按预期运行时,了解这一点也是很重要的。以下是一个成功的 SSH 登录示例:
Mar 14 19:50:59 server sshd[18884]: Accepted password for fred from 192.0.2.60 port 6647 ssh2
使用密钥进行身份验证的示例,显示密钥指纹作为一个 SHA256 的 base64 哈希:
Mar 14 19:52:04 server sshd[5197]: Accepted publickey for fred from 192.0.2.60 port 59915 ssh2: RSA SHA256:5xyQ+PG1Z3CIiShclJ2iNya5TOdKDgE/HrOXr21IdOo
使用用户证书进行身份验证的成功示例。证书的标识符是 "foobar",序列号是 "9624"。在这个示例中,证书使用 ECDSA,密钥本身使用 Ed25519。证书与认证密钥具有不同的 SHA256 指纹。
May 15 16:28:17 server sshd[50140]: Accepted publickey for fred from 192.0.2.60 port 44456 ssh2: ECDSA-CERT SHA256:qGl9KiyXrG6mIOo1CT01oHUvod7Ngs5VMHM14DTbxzI ID foobar (serial 9624) CA ED25519 SHA256:fZ6L7TlBLqf1pGWzkcQMQMFZ+aGgrtYgRM90XO0gzZ8
在 OpenSSH 6.8 之前,密钥指纹是以十六进制 MD5 哈希的形式显示。
Jan 28 11:51:43 server sshd[5104]: Accepted publickey for fred from 192.0.2.60 port 60594 ssh2: RSA e8:31:68:c7:01:2d:25:20:36:8f:50:5d:f9:ee:70:4c
在 OpenSSH
6.3 之前,认证日志完全没有显示密钥指纹。
Jan 28 11:52:05 server sshd[1003]: Accepted publickey for fred from 192.0.2.60 port 20042 ssh2
这是一个通过密码认证的 SFTP 会话示例,使用服务器的 internal-sftp
子系统。该子系统的日志级别设置为 INFO
。
翻译:
Mar 14 20:14:18 server sshd[19850]: 接受了来自 192.0.2.60 的 fred 用户的密码认证,端口号为 59946,使用 ssh2 Mar 14 20:14:18 server internal-sftp[11581]: 会话为本地用户 fred 打开,来源地址为 [192.0.2.60]
以下是一个使用 RSA 密钥进行认证的成功 SFTP 登录示例。
Mar 14 20:20:53 server sshd[10091]: 接受了来自 192.0.2.60 的 fred 用户的公钥认证,端口号为 59941,使用 ssh2: RSA SHA256:LI/TSnwoLryuYisAnNEIedVBXwl/XsrXjli9Qw9SmwI Mar 14 20:20:53 server internal-sftp[31070]: 会话为本地用户 fred 打开,来源地址为 [192.0.2.60]
可以通过 xinetd 来记录更多的附加数据,例如连接时长。
SSH 证书认证的日志问题
通常,日志中不会提供关于哪个证书认证失败的详细信息,只会说明认证失败的原因。找出相关的账户或实际的证书可能需要一些调查。通常客户端不会透露任何信息,所有的调查必须在服务器端进行。
如果认证尝试被其他方式重新进行,例如使用密码,那么当连接被关闭时,日志会记录涉及的账户。这是因为在连接过程的初期,断开连接的进程 ID 是相同的,并且会包括账户名和源地址,从而提供一些线索,帮助解决问题。有时,日志中甚至会包含账户名。
May 5 16:31:38 server sshd[252]: 连接已关闭,认证用户为 fred,来源地址 192.0.2.60,端口 44470 [preauth] 然而,如果连接在没有进行其他认证尝试的情况下超时,则除了可能的时间外,日志中没有任何有用的信息。
May 5 16:33:00 server sshd[90593]: 致命错误:在认证前超时,来源地址 192.0.2.60,端口 44718
以下是一些常见的证书认证失败的日志条目。一个证书可能存在多个问题,但每次只会记录一个错误。
证书过期或尚未生效
尚未生效或已经过期的证书会在日志中记录原因,但不会指明涉及的账户或证书。
May 5 16:35:20 server sshd[252]: 错误:证书无效:已过期 上面是一个过期证书,下面是一个尚未生效的证书。
May 5 16:58:00 server sshd[90593]: 错误:证书无效:尚未生效
这两种事件都不会提供更多的信息。
有效证书但主体无效
与过期证书类似,关于实际账户或证书提供的信息非常少。在这种情况下,证书尝试用于错误的账户,账户不在证书的主体列表中。
May 5 17:29:52 server sshd[98884]: 错误:证书无效:名称不是列出的主体 May 5 17:29:56 server sshd[98884]: 连接已关闭,认证用户为 fred,来源地址 192.0.2.60,端口 45114 [preauth]
如果客户端故意关闭连接,连接关闭的日志条目可能会提供一些信息。
有效证书但来源地址无效
如果证书仅限于特定地址或主机名进行连接,那么如果连接来自不同的地址或主机,日志会记录错误,并标明不正确的源地址。
May 5 17:48:54 server sshd[2420]: 证书:尝试为 fred 使用有效证书,但来源地址不在允许的范围内(192.0.2.61)。 May 5 17:48:54 server sshd[2420]: 错误:证书选项拒绝
然而,无法直接识别出特定的证书。
记录 SFTP 文件传输日志
SFTP 文件传输可以通过设置 LogLevel 为 INFO 或 VERBOSE 来记录。SFTP 服务器的日志级别可以在 sshd_config(5)
文件中单独设置,而与通用的 SSH 服务器设置无关。
Subsystem internal-sftp -l INFO
默认情况下,SFTP 消息也会记录到 auth.log
中,但可以通过重新配置系统日志程序(通常是 rsyslogd(8)
或 syslogd(8)
)来将这些消息过滤到自己的文件中。有时,系统管理员通过更改日志设施代码来实现此操作,默认设施是 AUTH
。可用的选项包括 LOCAL0
到 LOCAL7
,以及较少使用的 DAEMON
和 USER
。
Subsystem internal-sftp -l INFO -f LOCAL6
如果新系统日志文件被分配了,记得在日志轮转中也考虑到它们。同样,Match
指令可以用于为特定连接更改日志级别。
以下日志摘录是通过使用日志级别 INFO 生成的。会话以打开开始,以关闭结束。括号中的数字是 SFTP 会话的进程 ID,是唯一能在日志中跟踪会话的方式。
Oct 22 11:59:45 server internal-sftp[4929]: 会话为本地用户 fred 打开,来源地址为 [192.0.2.33] ... Oct 22 12:09:10 server internal-sftp[4929]: 会话为本地用户 fred 关闭,来源地址为 [192.0.2.33]
以下是一个上传大小为 928 字节、名为 foo 的小文件到用户 'fred' 的家目录的 SFTP 示例。
Oct 22 11:59:50 server internal-sftp[4929]: 打开 "/home/fred/foo",标志为 WRITE, CREATE, TRUNCATE,模式为 0664 Oct 22 11:59:50 server internal-sftp[4929]: 关闭 "/home/fred/foo",读取字节数 0,写入字节数 928
在同一会话中,对目录 /var/www
的目录列表操作:
Oct 22 12:07:59 server internal-sftp[4929]: 打开目录 "/var/www" Oct 22 12:07:59 server internal-sftp[4929]: 关闭目录 "/var/www"
最后,以下是下载来自用户 'fred' 家目录中的同一个 928 字节的文件 foo 的 SFTP 示例。
Oct 22 12:08:03 server internal-sftp[4929]: 打开 "/home/fred/foo" 标志为 READ,模式为 0666 Oct 22 12:08:03 server internal-sftp[4929]: 关闭 "/home/fred/foo",读取字节数 928,写入字节数 0
成功的文件传输会通过一个关闭消息来记录。尝试下载(打开)不存在的文件时,会在其后单独记录一行 "No such file" 消息,而不是关闭消息。如果文件存在但用户没有权限读取,则会记录 "Permission denied" 消息。
日志记录 Chrooted SFTP
在 Chroot Jail 内使用内置的 SFTP 子系统(由 ChrootDirectory
定义)时,需要确保在监狱内存在 ./dev/log
节点。这可以通过让系统日志程序(如 syslogd(8)
)在启动时将额外的日志套接字添加到 chroot 目录内来完成。在某些系统上,只需在 /etc/rc.conf.local
或相应的启动脚本中添加更多标志(如 -u -a
/chroot/dev/log
)。
以下是使用密码登录到 chroot 监狱并使用 DEBUG3
日志级别记录的 SFTP 示例。日志显示了文件上传:
Jan 28 12:42:41 server sshd[26299]: Connection from 192.0.2.60 port 47366
Jan 28 12:42:42 server sshd[26299]: Failed none for fred from 192.0.2.60 port 47366 ssh2
Jan 28 12:42:44 server sshd[26299]: Accepted password for fred from 192.0.2.60 port 47366 ssh2
Jan 28 12:42:44 server sshd[26299]: User child is on pid 21613
Jan 28 12:42:44 server sshd[21613]: Changed root directory to "/home/fred"
Jan 28 12:42:44 server sshd[21613]: subsystem request for sftp
Jan 28 12:42:44 server internal-sftp[2084]: session opened for local user fred from [192.0.2.60]
Jan 28 12:42:58 server internal-sftp[2084]: open "/docs/somefile.txt" flags WRITE,CREATE,TRUNCATE mode 0644
Jan 28 12:42:58 server internal-sftp[2084]: close “/docs/somefile.txt” bytes read 0 written 400
请记住,SFTP 是一个独立的子系统,像文件创建模式一样,日志级别和日志设施在 sshd_config(5)
中与 SSH 服务器的设置是分开设置的:
Subsystem internal-sftp -l ERROR
客户端连接稳定性日志
当服务器的配置中设置了 ClientAliveInterval
时,服务器会定期探测已建立连接的客户端。在正常日志级别下,这些探测不会被记录,直到出现问题。
如果 ClientAliveInterval
超过了 ClientAliveCountMax
设置的次数,客户端将被正式断开连接,连接将被关闭。在默认的日志级别 INFO
下,会记录简短的消息,标明被断开的客户端。
Sep 6 14:42:08 eee sshd[83709]: packet_write_poll: Connection from 192.0.2.97 port 57608: Host is down
在 DEBUG
日志级别下,客户端对探测的响应会被记录,显示会话仍然连接。
Sep 6 14:27:52 eee sshd[9075]: debug1: Got 100/147 for keepalive
DEBUG2
和 DEBUG3
日志级别会提供更多关于连接的信息。然而,即使是 DEBUG3
日志级别,特定客户端的探测信息不会直接显示在日志消息中,需要通过守护进程的进程 ID 推测出客户端的身份。
Sep 6 14:30:59 eee sshd[73960]: debug2: channel 0: request keepalive@openssh.com confirm 1
Sep 6 14:30:59 eee sshd[73960]: debug3: send packet: type 98
Sep 6 14:30:59 eee sshd[73960]: debug3: receive packet: type 100
Sep 6 14:30:59 eee sshd[73960]: debug1: Got 100/22 for keepalive
当 ClientAliveCountMax
超过限制时,连接会在客户端最终未响应时断开。以下是设置为 DEBUG2
日志级别时的记录:
Sep 6 14:17:55 eee sshd[15780]: debug2: channel 0: request keepalive@openssh.com confirm 1
Sep 6 14:17:55 eee sshd[15780]: debug1: Got 100/22 for keepalive
Sep 6 14:18:37 eee sshd[15780]: debug2: channel 0: request keepalive@openssh.com confirm 1
Sep 6 14:18:37 eee sshd[15780]: packet_write_poll: Connection from 192.0.2.97 port 57552: Host is down
Sep 6 14:18:37 eee sshd[15780]: debug1: do_cleanup
Sep 6 14:18:37 eee sshd[48675]: debug1: do_cleanup
Sep 6 14:18:37 eee sshd[48675]: debug1: session_pty_cleanup: session 0 release /dev/ttyp0
ClientAliveInterval
和 ClientAliveCountMax
指令通常适用于所有连接到服务器的客户端。但是,它们可以在 Match
块中使用,从而仅适用于特定的连接。
撤销的密钥日志
如果使用 RevokedKeys
指令指定了一个撤销的公钥列表,sshd(8)
会在使用撤销的密钥进行访问尝试时记录日志。无论是使用纯文本公钥列表还是生成的二进制密钥撤销列表(KRL),日志记录的条目都是相同的。
如果允许密码认证,且用户尝试使用密码认证,则在密钥认证失败后,会记录密码认证的尝试。
Mar 14 20:36:40 server sshd[29235]: error: Authentication key RSA SHA256:jXEPmu4thnubqPUDcKDs31MOVLQJH6FfF1XSGT748jQ revoked by file /etc/ssh/ssh_revoked_keys
...
Mar 14 20:36:45 server sshd[29235]: Accepted password for fred from 192.0.2.10 port 59967 ssh2
如果不允许密码认证,sshd(8)
会在密钥认证失败后立即关闭连接。
Mar 14 20:38:27 server sshd[29163]: error: Authentication key RSA SHA256:jXEPmu4thnubqPUDcKDs31MOVLQJH6FfF1XSGT748jQ revoked by file /etc/ssh/ssh_revoked_keys
...
尝试撤销的密钥的帐户仍然是未知的,因此需要使用 ssh-keygen -lf
查找其指纹,并通过密钥的注释来读取该密钥。尽管如果有效的帐户在密钥认证失败后取消连接,仍会将常规消息发布到日志中。
Mar 14 20:44:04 server sshd[14352]: Connection closed by authenticating user fred 192.0.2.237 port 55051 [preauth]
...
如果客户端没有尝试登录,而是直接超时,则消息将只显示超时信息。
Mar 18 21:40:25 server sshd[9942]: fatal: Timeout before authentication for 198.51.100.236 port 53728
...
在客户端,如果尝试了撤销的密钥,将不会给出警告或错误。它会直接失败,然后尝试下一个密钥或方法。
暴力破解和不成功的攻击
在服务器连接到网络后,几乎立即看到失败的登录尝试是很常见的。暴力破解攻击(即机器不断尝试几个帐户以找到有效的密码)变得越来越少。这部分是因为像 Linux 的 NFables 和 BSD 的 PF 这样的包过滤器,可以限制单一主机的连接尝试次数和速率。服务器配置指令 MaxStartups
可以限制未认证的连接的最大数量。
Mar 18 18:54:44 server sshd[54939]: Failed password for root from 201.179.249.231 port 52404 ssh2
Mar 18 18:54:48 server sshd[54939]: Failed password for root from 201.179.249.231 port 52404 ssh2
Mar 18 18:54:49 server sshd[54939]: Failed password for root from 201.179.249.231 port 52404 ssh2
Mar 18 18:54:49 server sshd[54939]: error: maximum authentication attempts exceeded for root from 201.179.249.231 port 52404 ssh2 [preauth]
Mar 18 18:54:49 server sshd[54939]: Disconnecting authenticating user root 201.179.249.231 port 52404: Too many authentication failures [preauth]
...
请注意
,"authenticating user" 在 OpenSSH 7.5 及更高版本的日志中出现,当有效用户名被尝试时。在尝试无效用户名时,也会记录该信息。
Mar 18 18:55:05 server sshd[38594]: 无效用户 ubnt 来自 201.179.249.231,端口 52471
Mar 18 18:55:05 server sshd[38594]: 无效用户 ubnt 的密码验证失败,来自 201.179.249.231,端口 52471,ssh2
Mar 18 18:55:09 server sshd[38594]: 错误:无效用户 ubnt 的最大认证尝试次数已超出,来自 201.179.249.231,端口 52471,ssh2 [预认证]
Mar 18 18:55:09 server sshd[38594]: 断开连接:无效用户 ubnt,来自 201.179.249.231,端口 52471:认证失败次数过多 [预认证]
应对来自单台机器或网络的暴力破解攻击的方法是定制服务器主机的包过滤器,限制攻击,甚至临时封锁那些超出最大连接次数或速率的机器。可选地,您还应该联系攻击者的网络块所有者,提供攻击的 IP 地址、具体日期和时间。
目前常见的一种攻击是分布式攻击,攻击者利用大量被攻陷的机器,每台机器仅执行攻击的一个小部分。
应对“临死一击(Hail Mary)”攻击时,可以联系攻击者的网络块所有者。如果能提供具体的攻击时间和地址,带有日志中片段的表格信件就足够了。或者,网络或系统管理员团队可以合作汇集数据,识别并将参与攻击的被攻陷主机列入黑名单。
无效用户的“失败 none 认证”
SSH 协议指定了多种可能的认证方法[1],密码、键盘交互式认证和公钥认证是比较常见的。较不常见的一种认证方法是 none
,只有在服务器不需要进一步认证时才会成功,例如设置了 PermitEmptyPassword
并且帐户没有实际密码时[2]。一些 SSH 客户端,包括 OpenSSH,会首先尝试 none
认证,然后如果失败,继续使用剩下的认证方法来决定接下来该怎么做。
Aug 10 19:09:05 server sshd[93126]: 无效用户 admin 的 none 认证失败,来自 125.64.94.136,端口 27586,ssh2
换句话说,这是一种暴力破解攻击,尝试使用 none
认证方法。它只会成功访问那些明确设置为空密码且服务器已启用 none
认证方法以及 PermitEmptyPasswords
配置指令的帐户。大多数暴力破解攻击只会尝试密码认证,一些攻击者甚至会检查密码方法,如果不可用则放弃。其他攻击者可能会无意义地持续尝试,即使该方法不可用。
似乎来自 127.0.0.1、::1 或其他本地主机地址的连接
当通过反向隧道访问 SSH 服务器时,来自反向隧道的传入连接会显示为本地主机地址,通常是 127.0.0.1 或 ::1。
Mar 23 14:16:16 server sshd[9265]: 接受来自 127.0.0.1 端口 40426 的密码认证,用户为 fred,ssh2
如果反向隧道的另一端的端口是公开可访问的,它将被探测并可能遭到攻击。由于反向隧道的存在,攻击似乎也来自服务器自身的回环地址:
Mar 23 14:20:17 server sshd[5613]: 无效用户 cloud 来自 ::1,端口 57404
Mar 23 14:20:21 server sshd[5613]: 无效用户 cloud 的密码验证失败,来自 ::1,端口 57404,ssh2
Mar 23 14:20:26 server sshd[5613]: 无效用户 cloud 的密码验证失败,来自 ::1,端口 57404,ssh2
Mar 23 14:20:32 server sshd[5613]: 无效用户 cloud 的密码验证失败,来自 ::1,端口 57404,ssh2
Mar 23 14:20:35 server sshd[5613]: 连接被无效用户 cloud 断开,来自 ::1,端口 57404 [预认证]
因此,像 SSHGuard、Fail2Ban 或其他类似的入侵检测系统无法使用,因为回环地址会被隧道用作所有登录尝试的来源地址,不论它们的真实来源是什么。
一个部分的解决方案是将传入连接绑定到其他 IP 地址。回环接口需要一个额外的永久地址,即别名。该别名可以在建立反向隧道时分配:
$ ssh -R 2022:127.2.2.1:22 fred@vps.example.org
这样,所有通过该隧道进行的登录的源地址就会被指定。反向隧道使用时,日志中就会显示别名,而不是默认的回环地址:
Mar 23 18:00:13 server sshd[8525]: 无效用户 cloud 来自 127.2.2.1,端口 17271
Mar 23 18:00:15 server sshd[8525]: 无效用户 cloud 的密码验证失败,来自 127.2.2.1,端口 17271,ssh2
Mar 23 18:00:19 server sshd[8525]: 无效用户 cloud 的密码验证失败,来自 127.2.2.1,端口 17271,ssh2
Mar 23 18:01:23 server sshd[8525]: 无效用户 cloud 的密码验证失败,来自 127.2.2.1,端口 17271,ssh2
Mar 23 18:01:26 server sshd[8525]: 连接被无效用户 cloud 断开,来自 127.2.2.1,端口 17271 [预认证]
如果这些端口不需要对外开放,完全的解决方案是确保它们无法从外部访问。这可以通过在客户端使用反向隧道时不使用 -g
选项,或者将 sshd_config
中的 GatewayPorts
指令恢复为默认的 no
,或两者结合使用。系统内建的包过滤器也可以用于此。这样,即使转发的端口被关闭,仍然可以使用 ProxyJump
选项跳过跳跃主机并使用该设置进行 SSH 访问。然而,由于某些情况下这些端口需要对外界开放,这种方法并不总是可行的。
翻译:
客户端日志记录
OpenSSH 客户端通常将日志信息发送到标准错误(stderr)。可以使用 -y
选项将输出发送到系统日志,由 syslogd(8)
或类似的服务管理。客户端的日志详细级别可以通过更改 ssh_config(5)
中的 LogLevel
指令来增加或减少,日志设施则通过 SyslogFacility
指令进行更改。两个指令都需要使用 -y
运行时选项,否则不会起作用。
另外,除了使用 -y
选项,使用 -E
选项可以将日志输出发送到指定的文件,而不是标准错误(stderr)。在自动化脚本中运行 ssh(1)
时,使用系统日志或单独的日志文件非常有用。下面是一个连接到交互式 shell 的示例,使用正常级别的客户端日志记录:
$ ssh -l fred server.example.org
fred@server.example.org‘s password:
Last login: Thu Jan 27 13:21:57 2011 from 192.168.11.1
相同的连接,在第一层详细级别下会显示大量的调试信息,多出 42 行。
$ ssh -v -l fred server.example.org
OpenSSH_6.8, LibreSSL 2.1
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Connecting to server.example.org [198.51.100.20] port 22.
debug1: Connection established.
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_rsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_ecdsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_ed25519 type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fred/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_6.8
debug1: Remote protocol version 2.0, remote software version OpenSSH_6.7
debug1: match: OpenSSH_6.7 pat OpenSSH* compat 0x04000000
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: server->client aes128-ctr umac-64-etm@openssh.com none
debug1: kex: client->server aes128-ctr umac-64-etm@openssh.com none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:CEXGTmrVgeY1qEiwFe2Yy3XqrWdjm98jKmX0LK5mlQg
debug1: Host '198.51.100.20' is known and matches the ECDSA host key.
debug1: Found key in /home/fred/.ssh/known_hosts:2
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: Roaming not allowed by server
debug1: SSH2_MSG_SERVICE_REQUEST sent
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Trying private key: /home/fred/.ssh/id_rsa
debug1: Trying private key: /home/fred/.ssh/id_dsa
debug1: Trying private key: /home/fred/.ssh/id_ecdsa
debug1: Trying private key: /home/fred/.ssh/id_ed25519
debug1: Next authentication method: keyboard-interactive
debug1: Authentications that can continue: publickey,password,keyboard-interactive
debug1: Next authentication method: password
debug1: Authentication succeeded (password).
Authenticated to server.example.org ([198.51.100.20]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: client_input_channel_req: channel 0 rtype eow@openssh.com reply 0
debug1: channel 0: free: client-session, nchannels 1
debug1: fd 2 clearing O_NONBLOCK
Last login: Sat Mar 14 21:31:33 2015 from 192.0.2.111
使用最大详细级别 -vvv
进行相同的登录时,调试信息大约有 150 行。请记住,调试信息是发送到标准错误(stderr),而不是标准输出(stdout)。这将仅捕获会话内容到文件,调试信息只会显示在屏幕上,不会写入输出日志:
$ ssh -vvv -l fred somehost.example.org | tee ~/ssh-output.log
tee(1)
工具像一个 T 型管道,会将输出同时发送到标准输出和文件中。
以下命令会同时捕获调试信息和会话文本:
$ ssh -vvv -l fred somehost.example.org 2>&1 | tee ~/ssh-output.log
分别捕获客户端调试信息
常规的管道和重定向仅适用于标准输出,因此,如果未使用 -E
来捕获调试输出,则必须将标准错误(stderr)输出重定向到标准输出(stdout),以便同时捕获它与实际会话。这可以通过额外的重定向 2>&1
来实现。注意空格的存在与否。
运行时或即时更改客户端调试级别
在运行时,当建立新连接时,只需使用 -v
选项。
$ sftp -v -o "IdentityFile=~/.ssh/weblog.key_rsa" fred@server.example.org
客户端的调试详细级别可以像服务器端一样增加。
$ sftp -vvv -o "IdentityFile=~/.ssh/weblog.key_rsa" fred@server.example.org
额外的信息可以帮助准确了解发送给服务器或从服务器请求的数据。
在连接建立后,可以使用转义序列 ~v
和 ~V
动态调整调试级别。通过它们,可以在已建立的连接中实时改变客户端的日志级别。如果是从默认的 INFO
开始,增加日志级别时,客户端会按顺序提升至 VERBOSE
、DEBUG
、DEBUG2
和 DEBUG3
。相反,降低日志级别时,客户端会从 INFO
开始,依次下降到 ERROR
、FATAL
,最终为 QUIET
。
调试与故障排除
在故障排除时,服务器日志是最好的朋友。可能需要暂时提高日志级别以获取更多信息。修复问题后,也必须将日志级别恢复正常,以避免隐私问题或过度占用磁盘空间。
例如,SFTP 子系统的日志默认级别为 ERROR
,仅报告错误。要跟踪客户端的事务,可以将日志级别改为 INFO
或 VERBOSE
:
Subsystem internal-sftp -l INFO
警告:再次提醒,使用过高的日志级别会违反用户隐私,并且会占用大量磁盘空间,因此通常不应在生产环境中使用,除非调试问题时。提高日志级别的日志消息应当单独写入日志文件,确保在收集期间不会影响到生产环境的正常运行。
默认情况下,一些系统只会将常规消息发送到常规的系统日志文件,并忽略较高等级的消息。有些系统会默认保存所有消息。如果较高等级的系统日志消息没有出现在任何系统日志中,可能是前者的原因。无论哪种情况,都应该检查系统日志配置,并确保额外的消息只发送到单独的日志文件中,而不是与常规的系统日志混合。如果有必要,修改配置。这有助于保持日志的整洁,并保护隐私。系统日志设置可以在系统日志守护进程的配置文件中找到,具体的文件名取决于安装了什么软件,常见的文件名包括 syslog.conf(5) 和 rsyslog.conf(5)。请注意,下面的机器配置将 AUTH 设施的更详细的 DEBUG 消息发送到一个与常规 AUTH 消息不同的日志文件:
$ grep '^auth\.' /etc/syslog.conf
auth.info /var/log/authlog
auth.debug /var/log/authdebug
可以查看 syslog(3)
了解日志设施和日志级别。最好限制收集调试信息的时间,并在收集期间积极监控。如果调试信息运行了一段时间,尤其是如果在无人看管的情况下运行,即使是短时间,也要记得将特殊的日志文件添加到日志轮换计划中,以免占满分区。
Match
块可以进一步帮助设置特定情况的日志级别,避免所有内容都被高度记录。
此外,OpenSSH 的手册页面写得非常好,许多问题可以通过查找正确的手册页中的相关章节来解决。至少,快速浏览一下程序和配置的四个主要手册页面,熟悉其中的章节标题非常重要:
- ssh(1)
- ssh_config(5)
- sshd(8)
- sshd_config(5)
找到正确的章节后,详细阅读并熟悉其中的内容。其他 OpenSSH 手册页面也是如此,具体取决于活动。确保使用与您的系统相对应版本的 OpenSSH,并使用相应的手册页面,最好使用系统上安装的版本,以避免不匹配。在某些情况下,客户端和服务器的版本可能不同,因此需要分别查阅每个版本的手册页面。发布新版本时,查看 OpenSSH 的发布说明也是一个好主意。
除了下面提到的几个例外之外,特定的故障排除示例通常会在相关活动的“食谱”部分给出。例如,解决身份验证密钥问题的内容通常在“公钥身份验证”部分中。
调试使用 sudo(8)
的脚本、配置或密钥
通常,仅在编写和测试脚本、新配置、新密钥或三者同时进行时才需要更改日志级别。当使用 sudo(8)
时,特别重要的是要精确查看客户端发送的内容,以便将正确的模式输入到 /etc/sudoers
中以确保安全。使用最低的详细级别,调试输出中将显示客户端发送到远程服务器的确切字符串:
$ rsync -e "ssh -v -i /home/webmaint/.ssh/bkup_key -l webmaint" \
-a server.example.org:/var/www/ var/backup/www/
...
debug1: Authentication succeeded (publickey).
Authenticated to server.example.org ([192.0.2.20]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: Sending command: rsync --server --sender -vlogDtpre.if . /var/www/
receiving incremental file list
...
然后,sudoers
中需要类似以下内容,假设账户 'webmaint' 属于 'webmasters' 组:
%webmasters ALL=(ALL) NOPASSWD: /usr/local/bin/rsync --server \
--sender -vlogDtpre.if . /var/www/
相同的方法也可用于调试新的服务器配置或密钥登录。一旦一切设置好并按需运行,日志级别可以降低回 INFO
(用于 sshd(8)
)并设为 ERROR
(用于 internal-sftp
)。此外,一旦脚本在完全自动化模式下运行,可以设置客户端日志信息使用 syslog(3)
系统模块,而不是 stderr
,通过在启动时设置 -y
选项。
调试服务器配置
在调试模式下运行服务器会提供大量有关连接的信息,以及一些关于服务器配置的信息。服务器的调试级别(-d
)可以设置为一次(-d
)、两次(-dd
)或三次(-ddd
)。
$ /usr/sbin/sshd -d
请注意,在这种情况下,服务器不会分离并成为守护进程,因此它将在 SSH 连接终止时停止。必须重新启动服务器才能进行后续连接。虽然这种方式稍显麻烦,但它确保会话数据是唯一的,而不是多个会话的混合,避免了不同配置之间的冲突。作为替代,调试时可以使用 -e
选项将调试数据发送到 stderr
,以保持系统日志的整洁。
在 OpenSSH 的最近版本中,还可以将调试数据直接记录到系统日志中的一个单独文件中,避免将系统日志弄乱。从 OpenSSH 6.3 开始,-E
选项会将调试数据追加到特定的日志文件中,而不是发送到系统日志中。这便于在不干扰系统日志的情况下调试实时系统。
$ /usr/sbin/sshd -E /home/fred/sshd.debug.log
在 OpenSSH 的旧版本中,如果需要将输出保存到文件,同时又想在屏幕上实时查看,可以使用 tee(1)
:
$ /usr/sbin/sshd -ddd 2>&1 | tee /tmp/foo
这将通过捕获 sshd(8)
发送到 stderr
的内容,将输出保存到文件 foo
中。对于旧版本的 OpenSSH,这种方法有效,但上面提到的 -E
选项更为推荐。
如果服务器是远程的,且需要减少被锁定的风险,可以使用第二个 sshd(8)
实例,使用单独的配置文件并监听高端口进行配置文件实验,直到设置测试完毕。
$ /usr/sbin/sshd -dd -p 22222 -f /home/fred/sshd_config.test
还可以对配置文件进行扩展测试(-T
)。如果有语法错误,会进行报告,但记住,即使配置没有问题,也可能会导致锁定。扩展测试模式可以单独使用,也可以通过 -C
选项指定特定的连接参数进行测试。sshd(8)
将根据传递给它的参数处理配置文件并输出结果。特别有用的是,Match
指令的结果将被显示。因此,-T
选项可以与 -C
选项一起使用,以显示不同连接使用的配置。
在向 sshd(8)
传递特定连接参数进行评估时,user
、host
和 addr
是扩展测试的最小要求。以下命令将打印出如果用户 fred
从地址 192.0.2.15
尝试登录主机 server.example.org
时,将应用的配置:
$ /usr/sbin/sshd -T -C user=fred,host=server.example.org,addr=192.0.2.15
还可以传递另外两个参数,laddr
和 lport
,它们分别表示连接的服务器的 IP 地址和端口:
$ /usr/sbin/sshd -T -C user=fred,host=server.example.org,addr=192.0.2.15,laddr=192.0.2.2,lport=2222
这五个变量应该能够描述任何可能的传入连接。
调试客户端配置
有时在调试服务器配置时,也需要跟踪客户端配置。自 OpenSSH 6.8 起,-G
选项使得 ssh(1)
在评估 Host
和 Match
块之后打印出配置,并退出。这样可以查看客户端在特定连接中实际使用的配置选项:
$ ssh -G -l fred server.example.org
客户端配置有三种确定方式。第一种是通过运行时选项,第二种是通过账户自己的配置文件,最后是通过系统范围的客户端配置文件。优先级按此顺序进行,找到的第一个值将被使用。对于 sftp(1)
,选项也会传递给 ssh(1)
。
无效或过时的密码算法或 MAC
一个合适的客户端会显示失败的详细信息。例如,对于一个错误的消息认证码(MAC),当试图将一个不合适的 MAC(如 hmac-md5-96
)强加给服务器时,合适的客户端可能会显示如下信息:
no matching mac found: client hmac-md5-96 server umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
对于错误的密码算法,合适的客户端可能会显示如下内容,当试图将 arcfour
密码算法强加给服务器时:
no matching cipher found: client arcfour server chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
在某些情况下,调试客户端问题时,需要查看服务器日志。自 OpenSSH 6.7 起,已删除不安全的 MAC,而自 OpenSSH 7.2 起,已删除不安全的密码算法,但一些第三方客户端可能仍然尝试使用它们建立连接。在这种情况下,客户端可能不会提供太多信息,只会显示一个模糊的消息,说明服务器意外关闭了网络连接。然而,服务器日志会显示发生了什么:
fatal: no matching mac found: client hmac-sha1,hmac-sha1-96,hmac-md5 server hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160 [preauth]
更新的版本会为错误的 MAC 显示一个更简单的错误:
fatal: Unable to negotiate with 192.0.2.37 port 55044: no matching MAC found. Their offer: hmac-md5-96 [preauth]
对于错误的密码算法,服务器日志将如下报告:
fatal: Unable to negotiate with 192.0.2.37 port 55046: no matching cipher found. Their offer: arcfour [preauth]
服务器日志中的错误消息可能不会说明哪些 MAC 或密码算法实际可用。为此,可以使用扩展测试模式来显示服务器设置,特别是显示允许的 MAC 或密码算法。其最基本的用法是 -T
,例如 /usr/sbin/sshd -T | grep -E
'cipher|macs'
,不带其他选项。更多详细信息和选项,请参见上面关于“调试服务器配置”的部分。
一种解决方案是将客户端升级到能够处理正确的密码算法和 MAC 的版本。另一种选择是切换到一个能够处理现代密码算法或 MAC 的客户端。
调试基于密钥的认证
公钥认证失败的最常见原因似乎有两个:
- 在将公钥放入服务器的
authorized_keys
文件中时发生了篡改。 - 文件和目录的权限设置不正确,无论是在客户端还是服务器上。这些文件和目录包括密钥目录(通常是
~/.ssh/
)或其父目录,或者authorized_keys
文件,甚至是私钥本身。
截至目前,几乎所有在邮件列表和论坛中描述的基于密钥的认证失败问题都可以通过解决以下两种情况中的一种或两种来解决。因此,当遇到错误信息 "Permission denied (publickey,keyboard-interactive)" 或类似错误时,可以查看关于公钥认证的部分,然后查看 sshd(8)
的手册页面及其关于授权密钥的章节。通常,尽管不总是如此,当私钥的权限设置不正确时,会显而易见:
$ ssh -i ~/.ssh/fred-193.ed25519 fred@192.0.2.193
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0664 for '/home/fred/.ssh/fred-193.ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/fred/.ssh/fred-193.ed25519": bad permissions
除了在 authorized_keys
文件中篡改的公钥和权限错误外,极少数情况下,如果公钥和私钥文件不匹配且不是同一密钥对的成员,也会导致问题。正如公钥认证部分所提到的,公钥和私钥需要匹配并且属于同一密钥对。这是因为,即使在 SSH 客户端使用私钥进行加密操作之前,它也会先查看提议的私钥的文件名,然后发送与该文件名匹配的公钥(如果存在)。如果客户端上的公钥与服务器上的 authorized_keys
文件中的公钥不匹配,连接将被拒绝,并显示 "Permission denied (publickey,keyboard-interactive)" 或类似错误。这本身就是一个很好的理由,要求为密钥文件指定独特的描述性文件名。请注意,除了错误地管理客户端机器上的公钥文件外,通常还有其他原因导致相同的错误消息。
每对密钥的文件名必须保持有序,以确保内容匹配。长期的解决办法是更仔细地管理密钥及其文件名。短期的解决办法是删除有问题的公钥文件,或者使用私钥重新生成一个新的密钥,覆盖原来的密钥。再次强调,这是一种罕见的边缘情况,并不是该错误的常见原因。
SSH 认证失败次数过多
当认证代理中有多个密钥时,客户端会按不可预测的顺序尝试这些密钥。如果客户端在找到正确密钥之前先尝试了足够多的错误密钥,并在达到服务器的 MaxAuthTries
限制后,服务器自然会断开连接,并显示认证失败的错误信息:
"Received disconnect from 203.0.113.110 port 22:2: Too many authentication failures
Authentication failed."
增加详细程度时,被测试并拒绝的密钥也会显示出来:
$ ssh -v 203.0.113.110
...
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Offering RSA public key: /home/fred/.ssh/key.06.rsa
debug1: Authentications that can continue: publickey,keyboard-interactive
debug1: Offering RSA public key: /home/fred/.ssh/key.02.rsa
debug1: Authentications that can continue: publickey,keyboard-interactive
debug1: Offering RSA public key: /home/fred/.ssh/key.03.rsa
debug1: Authentications that can continue: publickey,keyboard-interactive
debug1: Offering RSA public key: /home/fred/.ssh/key.04.rsa
debug1: Authentications that can continue: publickey,keyboard-interactive
debug1: Offering RSA public key: /home/fred/.ssh/key.01.rsa
debug1: Authentications that can continue: publickey,keyboard-interactive
debug1: Offering RSA public key: /home/fred/.ssh/key.05.rsa
Received disconnect from 203.0.113.110 port 22:2: Too many authentication failures
Authentication failed.
每个密钥在代理中的注释会显示该密钥文件是否是用户在配置文件中指定的,或者是否作为运行时参数提供。客户端会优先使用配置中指定的,并且当前在代理中的密钥。然后它将按照提供的顺序依次尝试这些密钥。[3]
如果你遇到 "Too many authentication failures" 错误,以下是两种解决方法:
-
逐个删除密钥:可以使用
ssh-add(1)
的-d
选项逐个从代理中删除密钥,直到剩下正确的密钥。通过文件系统路径引用每个密钥,例如:ssh-add -d ~/.ssh/some.key.rsa
。由于要删除的私钥是通过相应的公钥在代理中查找的,因此必须同时存在匹配的公钥文件。如果没有匹配的公钥文件,则无法单独从认证代理中删除私钥。相反,可以使用-D
选项一次性删除所有密钥。但在频繁使用多个远程系统并且需要保持代理中有足够密钥的情况下,这种方法可能并不实用。 -
限制客户端只尝试特定的密钥:另一种解决方法,可能是最实用的方法,是使用
IdentitiesOnly
配置指令配合IdentityFile
配置指令,限制客户端仅尝试特定的密钥。后者明确指定了正确的密钥。两者都可以作为运行时选项或在客户端配置文件中添加。作为运行时选项,可以像这样使用:
$ ssh -o IdentitiesOnly=yes -i ~/.ssh/server14.example.org.rsa -l fred server14.example.org
或者,这两个选项可以像以下方式添加到客户端配置文件中:
Host server14 server14.example.org
HostName server14.example.org
IdentitiesOnly yes
IdentityFile /home/fred/.ssh/server14.example.org.rsa
User fred
这样,服务器可以通过短名称或完全限定的域名进行访问,具体取决于 Host
指令下列出的名称。
$ ssh server14
请记住,选项是从客户端配置文件中按第一个匹配原则选择的。因为第一个匹配的选项优先,因此具体规则必须位于更一般规则之前。
签名失败...代理拒绝操作
如前所述,客户端文件或目录权限不正确是尝试基于密钥的认证时常见的失败原因。然而,并不是所有错误都能明确指出这一点。以下是一个误导性的错误消息,实际上是由权限错误引起的:
$ ssh -i ~/.ssh/key-ed25519 fred@server.example.org
sign_and_send_pubkey: signing failed for ED25519 "/home/fred/.ssh/key-ed25519" from agent: agent refused operation
fred@server.example.org: Permission denied (publickey).
解决方案是确保没有其他帐户可以读取或写入私钥,并且其他用户也不应能写入 .ssh
目录或其父目录。
调试 Chrooted SFTP 账户
最常见的问题似乎是目录权限错误。chroot
目录及其上级所有目录必须由 root 拥有,并且不能被其他用户或组写入。即使这些目录的组成员身份不一定是 root,但如果它们不是 root,那么它们也不能对组可写。未使用正确的所有权会导致无法使用受影响的账户登录。登录尝试时的错误信息如下所示(客户端侧):
$ sftp fred@192.0.2.206
fred@192.02.206's password:
packet_write_wait: Connection to 192.0.2.206: Broken pipe
Couldn't read packet: Connection reset by peer
服务器端的错误信息则更为清晰:
Aug 4 23:52:38 server sshd[7075]: fatal: bad ownership or modes for chroot directory component "/home/fred/"
检查 chroot
目标目录及其上级所有目录的权限。如果其中任何一个不正确,必须修正它,以确保它由 root 拥有并且不能被其他人写入。有许多方法可以修正权限,以下是两种设置 chroot
权限的方式:
一种快速修复方法是将目录的所有权和组成员都更改为 root,同样适用于所有 chroot
目标目录以上的目录。
$ ls -lhd /home/ /home/fred/
drwxr-xr-x 3 root root 4.0K Aug 4 20:47 /home/
drwxr-xr-x 8 root root 4.0K Aug 4 20:47 /home/fred/
这在 ChrootDirectory
指令设置为 %h
时有效,但在添加文件或目录时会有一些缺点,问题会很快显现。
另一种更简单的修复方法是更改账户的家目录和 ChrootDirectory
指令。安排账户的家目录位于一个由 root 拥有的唯一目录下,例如使用用户名作为目录名:
$ ls -lhd /home/ /home/fred/ /home/fred/fred/
drwxr-xr-x 3 root root 4.0K Aug 4 20:47 /home/
drwxr-x--- 3 root fred 4.0K Aug 4 20:47 /home/fred/
drwxr-x--- 8 fred fred 4.0K Aug 4 20:47 /home/fred/fred/
然后将账户的 chroot
目录设置为上级目录,并结合 SFTP
服务器的 -d
选项,使用用户名令牌作为起始目录。
ChrootDirectory /home/%u
ForceCommand internal-sftp -d %u
这样,当账户连接时,它只会看到自己的目录,而无法访问系统的其他部分。
调试 RC 脚本干扰 SFTP 会话
如果有任何额外的数据通过标准输入(stdin)在客户端或服务器端传输,SFTP 连接将中断。常见的错误是如果 /etc/ssh/sshrc
或 ~/.ssh/rc
向标准输出(stdout)发送了任何内容,而不是保持安静。那时,服务器上的输出会通过 stdin 被客户端接收,但不符合正确的协议,导致客户端断开连接。因此,即使在使用 RC 脚本的情况下,服务器的响应也必须保持 8 位干净,否则会发生错误:
$ sftp server.example.org
Received message too long 1400204832
这条消息将是主要线索。增加 SFTP 客户端的详细程度(-v
)并不会提供更多相关信息。
此外,服务器上的标准日志只会显示客户端断开连接,而不会提供断开原因。在较高的日志级别下,可能会注意到一些额外的读取操作和相应的丢弃,但仅此而已。以下是记录在 DEBUG3 级别的日志示例,显示了这种情况。
...
debug2: subsystem request for sftp by user fred
debug1: subsystem: exec() /usr/libexec/sftp-server
Starting session: subsystem 'sftp' for fred from 198.51.100.38 port 37446 id 0
...
debug2: channel 0: read 13 from efd 12
debug3: channel 0: discard efd
debug2: channel 0: read 12 from efd 12
debug3: channel 0: discard efd
debug2: channel 0: read 15 from efd 12
debug3: channel 0: discard efd
debug2: channel 0: read 18 from efd 12
debug3: channel 0: discard efd
...
同样,RC 脚本在使用 SFTP 时不得产生任何标准输出(stdout),否则会破坏连接。如果 RC 脚本产生了输出,它必须被重定向到系统日志、文件或标准错误(stderr)中,而不是标准输出(stdout)。常规的交互式 SSH 连接不会受到 stdout 的干扰,客户端只会显示发送的内容。有关更多信息,请查看 sshd(8)
的手册页中的 "SSHRC" 部分。
这个限制适用于任何其他通过标准输入和标准输出运行的 SSH 服务部分,例如 ProxyJump 或 ProxyCommand 的某些用法。因此,潜在的干扰还可能出现在使用 LocalCommand
时,它指定在成功连接到服务器后在本地机器上执行的命令。任何来自 LocalCommand
的输出也需要重定向到标准错误(stderr)。如果 LocalCommand
干扰了 ProxyJump
,连接会在使用 stdout 时似乎挂起。
调试 SSH 代理拥有正确的私钥但未使用它的情况
在旧版本的 OpenSSH 中,公钥必须在私钥加载到代理时可用。如果没有公钥,代理将无法使用私钥,除非做其他安排。一个典型的症状是,通过运行时参数指定密钥能够工作,但通过代理指定相同的密钥却无法正常工作。
升级到较新的 OpenSSH 版本是一个更好的选择。否则,解决方法是将私钥指定为运行时参数或在 ssh_config(5)
文件中指定,客户端将找到对应名称的公钥文件。重要的是,客户端仍然会使用代理中的密钥,但会使用指定的匹配公钥文件,因此私钥文件可以什么都不包含,甚至可以为空。
$ ssh -i some_key_ed25519 fred@server.example.org
然而,如果不希望私钥在文件系统中可访问,或者私钥只在代理中可用而不是直接从文件系统中访问,则可以直接指定公钥。
$ ssh -i some_key_ed25519.pub fred@server.example.org
另一种方式是使用 IdentityFile
配置指令在 ssh_config(5)
中指定密钥。如果公钥文件缺失,可以使用 ssh-keygen(1)
的 -y
选项从私钥中重新生成它。
SSH 客户端错误信息及常见原因
以下是一些客户端错误信息及其常见原因的总结[4]。这些错误列表及原因并不全面,原因仅触及一些常见问题。真正的解决办法是检查实际日志,特别是服务器端日志。服务器日志文件通常位于 /var/log/auth.log
,或者是类似名称的文件。在某些情况下,你可能会在 /var/log/secure
文件中找到相关信息。
无与名称关联的地址:
$ ssh nonesuch.example.org
ssh: Could not resolve hostname nonesuch.example.org: no address associated with name
目标的主机名在 DNS 中不存在。是否拼写正确?
操作超时:
$ ssh 198.51.100.89
ssh: connect to host 198.51.100.89 port 22: Operation timed out
没有与该 IP 地址关联的系统,或者数据包过滤器导致了问题。
连接超时:
$ ssh 198.51.100.89
ssh: connect to host 198.51.100.89 port 22: Connection timed out
无法连接。目标机器可能已经断开网络连接,或者所在的网络无法访问。
连接被拒绝:
$ ssh www.example.org
ssh: connect to host www.example.org port 22: Connection refused
目标系统存在,但在尝试的端口上没有可用的 SSH 服务。是否正确配置了目标?可能是错误的系统,SSH 没有运行,SSH 可能监听在另一个端口,或者数据包过滤器阻止了连接。
权限被拒绝:
$ ssh www.example.org
fred@www.example.org: Permission denied (publickey,keyboard-interactive).
通常是由于错误的用户名、错误的认证方法、错误的 SSH 密钥或 SSH 证书,或者目标系统上授权密钥文件的权限问题。如果是基于 SSH 密钥的认证问题,参见《公共密钥认证》章节以获取更全面的介绍。如果是 SSH 证书的问题,请参见相应的基于证书的认证章节。也可能是目标服务器的设置问题,如 AllowGroups
、DenyGroups
或类似的配置指令。这些问题只能通过检查 sshd(8)
的日志输出来识别和解决。
找不到匹配的主机密钥类型:
$ ssh www.example.org
Unable to negotiate with 198.51.100.89 port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss
该服务器的 SSH 守护进程非常过时。请联系系统管理员进行升级,并尽快解决问题。可以使用 -v
选项查看更详细的信息。
$ ssh -v fred@example.org
...
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: diffie-hellman-group-exchange-sha256
debug1: kex: host key algorithm: (no match)
Unable to negotiate with 198.51.100.89 port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss
在上述示例中,问题出在服务器,它使用了过时的密钥交换算法。再次强调,解决方案是更新过时的软件。如果必须长时间使用特定的操作系统版本,可以考虑获取 SSH 守护进程的回溯版本。许多 GNU/Linux 发行版甚至提供了专门的回溯仓库。
如果你想查看客户端支持哪些密钥交换算法,可以使用 -Q
选项:
$ ssh -Q kex
diffie-hellman-group1-sha1
diffie-hellman-group14-sha1
diffie-hellman-group14-sha256
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha1
diffie-hellman-group-exchange-sha256
ecdh-sha2-nistp256
ecdh-sha2-nistp384
ecdh-sha2-nistp521
curve25519-sha256
curve25519-sha256@libssh.org
sntrup761x25519-sha512@openssh.com