跳转到内容

OpenSSH/日志和故障排除

100% developed
来自 Wikibooks,开放世界中的开放书籍
(从 OpenSSH/Logging 重定向)

OpenSSH 客户端和服务器都提供了很多关于日志写入位置和收集多少信息的选项。

记录的前提是拥有一个使用网络时间协议 (NTP) 或等效服务的准确系统时钟,该服务提供与世界其他地区的持续时间同步。日志中时间戳越准确,在机器之间或站点或服务提供商之间协调取证的速度就越快。如果您必须联系外部方,例如服务提供商,通常只有在非常精确的时间情况下才能取得进展。

服务器日志

[编辑 | 编辑源代码]

默认情况下,sshd(8) 使用日志级别 INFO 和系统日志工具 AUTH 将日志信息发送到系统日志。因此,查找来自 sshd(8) 的日志数据的位置在 /var/log/auth.log 中。这些默认值可以使用 SyslogFacilityLogLevel 指令覆盖。以下是授权日志中典型的服务器启动条目。

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) 获得它来执行一般活动。或者,对于需要 root 级别访问权限的特定任务,可以通过为 sudo(8)doas(1) 创建的自定义条目来授予这些权限。请注意,在这些情况下,只应允许特定服务和程序,而不应允许 blanket 访问,这是一种在 sudo(8) 中经常出现的错误配置。或者,可以使用一个使用 forced-commands-only 创建的单一用途密钥,因为有些人认为,提供额外的特权升级方式,例如 su(1)sudo(8)doas(1),比通过与特定功能绑定在一起的密钥或证书仔细提供远程 root 访问权限更危险。

成功登录

[编辑 | 编辑源代码]

默认情况下,服务器不会存储太多关于用户事务的信息。这是一件好事。认识到系统正在按预期运行也是一件好事。因此,以下是一个成功的 SSH 登录示例

Mar 14 19:50:59 server sshd[18884]: Accepted password for fred from 192.0.2.60 port 6647 ssh2

以下是一个使用密钥进行身份验证的示例。它以 base64 显示密钥指纹作为 SHA256 哈希。

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

在 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

在 6.3 之前的 OpenSSH 老版本中,密钥指纹完全没有出现在身份验证日志中。

Jan 28 11:52:05 server sshd[1003]: Accepted publickey for fred from 192.0.2.60 port 20042 ssh2

以下是一个使用服务器的内部 sftp 子系统进行 SFTP 会话的密码身份验证示例。该子系统的日志记录设置为 INFO。

Mar 14 20:14:18 server sshd[19850]: Accepted password for fred from 192.0.2.60 port 59946 ssh2
Mar 14 20:14:18 server internal-sftp[11581]: session opened for local user fred from [192.0.2.60]

以下是一个使用 RSA 密钥进行身份验证成功登录 SFTP 的示例。

Mar 14 20:20:53 server sshd[10091]: Accepted publickey for fred from 192.0.2.60 port 59941 ssh2: RSA SHA256:LI/TSnwoLryuYisAnNEIedVBXwl/XsrXjli9Qw9SmwI
Mar 14 20:20:53 server internal-sftp[31070]: session opened for local user fred from [192.0.2.60]

可以使用 xinetd 记录其他数据,例如连接持续时间。

记录 SSH 证书身份验证问题

[编辑 | 编辑源代码]

通常,不会提供太多关于哪个证书失败的信息,只会提供为什么它失败了身份验证的信息。找到相关的帐户或实际证书可能需要一些侦查工作。通常不会公开任何客户端信息,并且所有调查都必须在服务器端进行。

如果通过其他方式再次尝试进行身份验证,例如密码,那么在连接关闭时,会记录一个条目,指出涉及哪个帐户。这是因为在连接序列的早期,断开连接的进程 ID 是相同的,并且会包含帐户名称和源地址,从而为寻找解决方案提供一些线索。有时甚至会包含帐户名称。

May  5 16:31:38 server sshd[252]: Connection closed by authenticating user fred 192.0.2.60 port 44470 [preauth]

但是,如果连接在没有首先通过其他方式进行任何其他身份验证尝试的情况下超时,那么除了可能的时间之外,将没有任何信息可供参考。

May  5 16:33:00 server sshd[90593]: fatal: Timeout before authentication for 192.0.2.60 port 44718

以下是一些常见的基于证书的登录尝试失败日志条目的示例。证书可能存在不止一个问题,但一次只会记录一个错误。

过期或尚未生效的证书

[编辑 | 编辑源代码]

尚未生效或已过期的证书会记录一个关于原因的条目,但不会记录相关帐户或证书。

May  5 16:35:20 server sshd[252]: error: Certificate invalid: expired

以上是已过期的证书,以下是尚未生效的证书。

May  5 16:58:00 server sshd[90593]: error: Certificate invalid: not yet valid

两种类型的事件都不会提供更多信息。

有效证书但主体无效

[编辑 | 编辑源代码]

与已过期的证书一样,关于实际帐户或证书的信息很少。这里,证书使用错误的帐户进行尝试,该帐户未在证书的主体中列出。

May  5 17:29:52 server sshd[98884]: error: Certificate invalid: name is not a listed principal
May  5 17:29:56 server sshd[98884]: Connection closed by authenticating user fred 192.0.2.60 port 45114 [preauth]

如果客户端故意关闭连接,连接关闭条目中可能包含一些信息。

有效证书但源地址无效

[编辑 | 编辑源代码]

如果证书被限制为仅从特定地址或主机名连接,则如果连接来自不同的地址或主机,日志会抱怨,并识别出错误的源地址。

May  5 17:48:54 server sshd[2420]: cert: Authentication tried for fred with valid certificate but not from a permitted source address (192.0.2.61).
May  5 17:48:54 server sshd[2420]: error: Refused by certificate options

但是,将无法直接识别特定的证书。

记录 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]: session opened for local user fred from [192.0.2.33]
...
Oct 22 12:09:10 server internal-sftp[4929]: session closed for local user fred from [192.0.2.33]

以下是一个将名为 foo 的 928 字节的小文件上传到用户 'fred' 的主目录的 SFTP 上传示例。

Oct 22 11:59:50 server internal-sftp[4929]: open "/home/fred/foo" flags WRITE,CREATE,TRUNCATE mode 0664
Oct 22 11:59:50 server internal-sftp[4929]: close "/home/fred/foo" bytes read 0 written 928

以及同一个会话中在 /var/www 目录中的目录列表。

Oct 22 12:07:59 server internal-sftp[4929]: opendir "/var/www"
Oct 22 12:07:59 server internal-sftp[4929]: closedir "/var/www"

最后,以下是从用户 'fred' 的主目录中下载同一个名为 foo 的 928 字节小文件的示例。

Oct 22 12:08:03 server internal-sftp[4929]: open "/home/fred/foo" flags READ mode 0666
Oct 22 12:08:03 server internal-sftp[4929]: close "/home/fred/foo" bytes read 928 written 0

成功传输将用 close 消息记录。尝试下载(打开)不存在的文件将用一行 sent status No such file 消息代替 close 消息。存在但用户无权读取的文件将生成 sent status Permission denied 消息。

记录 Chrooted SFTP

[编辑 | 编辑源代码]

在由 ChrootDirectory 定义的 chroot 监狱中使用内置的 sftp-subsystem 进行记录,需要在监狱内部存在 ./dev/log 节点。这可以通过在启动时让系统日志记录器,例如 syslogd(8),在 chrooted 目录中添加额外的日志套接字来实现。在一些系统上,这只需要在 /etc/rc.conf.local 或等效的启动脚本中添加更多标志,例如“-u -a /chroot/dev/log”。

以下是一个使用 SFTP-subsystem 的 DEBUG3 日志级别登录 chroot 监狱并使用密码的 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 是一个独立的子系统,与文件创建模式一样,日志级别和日志设施是独立于 SSH 服务器在 sshd_config(5) 中设置的。

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 [email protected] 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 [email protected] 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 [email protected] 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

ClientAliveIntervalClientAliveCountMax 指令通常适用于连接到服务器的所有客户端。但是,它们可以在 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]
...

这可能提供一些线索,并允许使用简短的 AWK 脚本进行过滤,如果所有消息都在同一个日志文件中。

$ awk '/revoked by file/ {
        pid[$5]++; key[$5]=$9; hash[$5]=$10; next;
    }
    pid[$5] && /closed by authenticating user/ {
        print key[$5], hash[$5], $10, $11;
        delete key[$5]; delete hash[$5]; delete pid[$5];
    }' /var/log/authlog

同样,如果客户端没有尝试登录而只是超时,那么消息就会说就是这样。

Mar 18 21:40:25 server sshd[9942]: fatal: Timeout before authentication for 198.51.100.236 port 53728
...

在客户端方面,如果尝试使用被吊销的密钥,不会有任何警告或错误提示。它只会失败,并尝试下一个密钥或方法。

暴力攻击和 Hail Mary 攻击

[编辑 | 编辑源代码]

在服务器连接到网络后,几乎立即看到登录尝试失败是相当常见的。暴力攻击,即一台机器对几个帐户进行猛烈攻击,试图找到有效的密码,正在变得越来越少。部分原因是,像 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]
...

请注意,从 OpenSSH 7.5 开始,当尝试使用有效的用户名时,日志中会显示“authenticating user”。当尝试使用无效的用户名时,也会记录下来。

...
Mar 18 18:55:05 server sshd[38594]: Invalid user ubnt from 201.179.249.231 port 52471
Mar 18 18:55:05 server sshd[38594]: Failed password for invalid user ubnt from 201.179.249.231 port 52471 ssh2
Mar 18 18:55:09 server sshd[38594]: error: maximum authentication attempts exceeded for invalid user ubnt from 201.179.249.231 port 52471 ssh2 [preauth]
Mar 18 18:55:09 server sshd[38594]: Disconnecting invalid user ubnt 201.179.249.231 port 52471: Too many authentication failures [preauth]
...

处理来自单个机器或网络的暴力攻击的方法是,自定义服务器主机的包过滤器以限制攻击,甚至可以暂时阻止超过最大连接数量或速率的机器。可选地,还可以将攻击者的网络块所有者与 IP 地址以及攻击的确切日期和时间联系起来。

在撰写本文时,一种常见的攻击方式是,攻击分散在大量被入侵的机器上,每台机器只在攻击服务器中扮演很小的角色。

为了处理 Hail Mary 攻击,请联系攻击者的网络块所有者。如果攻击给出了确切的时间和地址,那么带有日志中复制粘贴内容的格式化信件就足够了。或者,网络或系统管理员团队可以协同工作,收集数据以识别和列入黑名单参与攻击的被入侵主机。

对无效用户的“Failed None”

[编辑 | 编辑源代码]

SSH 协议指定了许多可能的身份验证方法[1]passwordkeyboard-interactivepublickey 方法比较常见。一种不太为人知的身份验证方法是 none,它只有在服务器不需要进一步身份验证的情况下才能成功,例如,如果设置了 PermitEmptyPassword,并且帐户实际上没有密码[2]。一些 SSH 客户端,包括 OpenSSH 的客户端,首先会请求 none 身份验证,然后使用剩余的可能身份验证方法列表来决定如果该方法不起作用,接下来应该怎么做。

...
Aug 10 19:09:05 server sshd[93126]: Failed none for invalid user admin from 125.64.94.136 port 27586 ssh2
...

换句话说,这是一种尝试 none 身份验证方法的暴力攻击。这是一种攻击,它只会进入明确设置了空密码,并且还专门设置了允许访问的帐户,即在服务器上启用了 none 身份验证方法和 PermitEmptyPasswords 配置指令。大多数暴力攻击只尝试 password 身份验证,其中一些甚至会检查 password 方法,如果该方法不可用,就会放弃。其他攻击者可能会无休止地攻击,即使该方法不可用。

来自 127.0.0.1、::1 或其他 localhost 地址的连接

[编辑 | 编辑源代码]

当通过反向隧道访问 SSH 服务器时,传入的连接将显示为来自 localhost 地址,通常是 127.0.0.1::1

Mar 23 14:16:16 server sshd[9265]: Accepted password for fred from 127.0.0.1 port 40426 ssh2

如果反向隧道的另一端端口可公开访问,它将被探测并可能受到攻击。由于反向隧道,攻击也将看起来来自服务器自己的回环地址。

Mar 23 14:20:17 server sshd[5613]: Invalid user cloud from ::1 port 57404
Mar 23 14:20:21 server sshd[5613]: Failed password for invalid user cloud from ::1 port 57404 ssh2
Mar 23 14:20:26 server sshd[5613]: Failed password for invalid user cloud from ::1 port 57404 ssh2
Mar 23 14:20:32 server sshd[5613]: Failed password for invalid user cloud from ::1 port 57404 ssh2
Mar 23 14:20:35 server sshd[5613]: Connection closed by invalid user cloud ::1 port 57404 [preauth]

因此,像 SSHGuard 或 Fail2Ban 或其他类似入侵检测系统之类的常规对策无法使用,因为localhost地址被隧道用于所有登录尝试,无论其真实来源。

部分解决方案是将传入连接绑定到不同的 IP 地址。回环接口需要一个额外的永久地址(别名)来实现。这个别名可以在建立反向隧道时分配。

$ ssh -R 2022:127.2.2.1:22 [email protected]

这样,它将为通过该隧道传入的所有登录指定源地址。因此,以这种方式,当使用该反向隧道时,别名将显示在日志中,而不是默认的回环地址。

Mar 23 18:00:13 server sshd[8525]: Invalid user cloud from 127.2.2.1 port 17271
Mar 23 18:00:15 server sshd[8525]: Failed password for invalid user cloud from 127.2.2.1 port 17271 ssh2
Mar 23 18:00:19 server sshd[8525]: Failed password for invalid user cloud from 127.2.2.1 port 17271 ssh2
Mar 23 18:01:23 server sshd[8525]: Failed password for invalid user cloud from 127.2.2.1 port 17271 ssh2
Mar 23 18:01:26 server sshd[8525]: Connection closed by invalid user cloud 127.2.2.1 port 17271 [preauth]

如果端口不需要对开放的互联网可用,一个完整的解决方案就是确保它们从外部不可访问。这可以通过在创建反向隧道时不在客户端使用-g选项,或者通过将sshd_config(5)中的GatewayPorts指令设置为默认值no,或同时使用两者来实现。系统的内置数据包过滤器也可以使用。然后,即使将转发端口从外部关闭,ProxyJump选项仍然可以用来跳过跳跃主机并使用设置进行 SSH 访问。然而,由于有时需要这些端口对外部世界可访问,因此这种方法并非总是可行的。

客户端日志记录

[edit | edit source]

OpenSSH 客户端通常将日志信息发送到stderr-y选项可用于将输出发送到系统日志,由syslogd(8)或类似程序管理。可以通过更改ssh_config(5)中的LogLevel指令来增加或减少客户端日志详细程度,并通过SyslogFacility指令更改日志设备。两者都需要使用-y运行时选项,如果没有它则不起作用。

或者,除了使用-y选项之外,使用-E选项将日志输出发送到指定的日志文件,而不是stderr。在运行ssh(1)的自动化脚本时,使用系统日志或独立的日志文件将非常有用。以下是一个连接到交互式 shell 并使用正常级别客户端日志记录的示例。

$ ssh -l fred server.example.org    
[email protected]‘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 [email protected] none
debug1: kex: client->server aes128-ctr [email protected] 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 [email protected]
debug1: Entering interactive session.
debug1: client_input_global_request: rtype [email protected] want_reply 0
debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: client_input_channel_req: channel 0 rtype [email protected] 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 形管,将输出发送到两个方向,一个到stdout,一个到文件。

以下命令将同时捕获调试信息和会话文本。

$ ssh -vvv -l fred  somehost.example.org  2>&1  | tee ~/ssh-output.log

分别捕获客户端调试信息

[edit | edit source]

常规管道和重定向仅适用于stdout,因此,如果未使用-E捕获调试输出,则必须将stderr上的输出发送到 stdout,如果要同时捕获它和实际会话。这通过额外的重定向2>&1来捕获stderr来完成。注意空格或空格的缺失。

在运行时或动态更改客户端调试级别

[edit | edit source]

在运行时,建立新连接时,只需使用-v选项。

$ sftp -v -o "IdentityFile=~/.ssh/weblog.key_rsa" [email protected]

客户端上的调试详细程度可以像服务器一样增加。

$ sftp -vvv -o "IdentityFile=~/.ssh/weblog.key_rsa" [email protected]

额外的信息可能有助于查看确切发送到服务器或服务器请求的内容。

事后,一旦建立连接,转义序列~v~V可以用来动态增加或减少详细程度。通过它们,可以在已建立的连接中更改客户端的详细程度。当增加时,客户端会将其日志级别提升到 VERBOSE、DEBUG、DEBUG2 和 DEBUG3,依此类推,如果从默认的 INFO 开始。相反,当降低日志级别时,如果从默认的 INFO 开始,客户端会下降到 ERROR、FATAL,最后到 QUIET。

调试和故障排除

[edit | edit source]

服务器日志是您在故障排除时最好的朋友。可能需要暂时提高那里的日志级别以获取更多信息。然后,还需要在问题解决后将它们恢复到正常状态,以避免隐私问题或过度使用磁盘空间。

例如,SFTP 子系统日志记录默认设置为 ERROR,只报告错误。要跟踪客户端执行的事务,请将日志级别更改为 INFO 或 VERBOSE。

Subsystem internal-sftp  -l INFO

注意:再次,使用提高的日志级别会侵犯用户的隐私,还会占用大量磁盘空间,因此通常不应在解决更改后用于生产环境。提高的日志消息应该在收集期间真正发送到单独的日志文件。

默认情况下,某些系统只将普通消息发送到常规系统日志文件,而忽略提高的日志消息。有些系统默认情况下会保存所有消息。如果提高的系统日志消息未显示在任何系统日志中,则前者可能是原因。无论哪种情况,请检查系统日志配置,并确保额外的日志消息只发送到单独的日志文件,而不是与常规系统日志混合。如有必要,请更改配置。这有助于保持日志整洁,同时保护隐私。系统日志设置在系统日志守护进程的配置文件中找到,确切的名称会因安装的软件而异,但常见的名称是syslog.conf(5)和 rsyslog.conf(5)。请注意,下面的机器配置将更详细的 DEBUG 消息(用于 AUTH 设备)发送到与常规 AUTH 消息不同的日志文件。

$ grep '^auth\.' /etc/syslog.conf 
auth.info                                               /var/log/authlog
auth.debug                                              /var/log/authdebug

请参阅syslog(3)了解日志设备和日志级别。最好限制收集调试信息的时间,并在收集信息时积极监控。但是,如果它运行了任何时间长度,特别是如果它无人看管即使是短时间,请务必记住将特殊日志文件添加到日志轮换计划中,以防止它填满分区。

Match 块可以通过为特定情况设置日志级别来进一步帮助,避免所有内容都以高强度记录的情况。

此外,OpenSSH 的手册页写得非常好,很多时候可以通过在正确的手册页中找到正确的部分来解决问题。至少,重要的是要浏览一下程序及其配置的四个主要手册页,并熟悉至少部分标题。

然后,一旦在手册页中找到正确的部分,请详细查看它,并熟悉其内容。其他 OpenSSH 手册页也是如此,具体取决于活动。请确保使用适用于您的系统的 OpenSSH 版本以及相应的手册页,最好是安装在系统上的手册页,以避免不匹配。在某些情况下,客户端和服务器将使用不同的版本,因此必须分别查找每个版本的手册页。在发布新版本时,查看 OpenSSH 的发行说明也是一个好主意。

除了下面的一些例外,故障排除的具体示例通常在与特定活动相关的食谱部分中给出。因此,例如,对身份验证密钥进行的排序问题将在公钥身份验证本身部分完成。

调试使用 sudo(8) 的脚本、配置或密钥

[edit | edit source]

通常,只有在编写和测试脚本、新配置、一些新密钥或同时进行所有这三项操作时才需要更改日志级别。在使用 **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 [email protected]
debug1: Entering interactive session.
debug1: Sending command: rsync --server --sender -vlogDtpre.if . /var/www/
receiving incremental file list
...

假设帐户 'webmaint' 属于组 'webmasters',那么 **sudoers** 需要类似以下的内容。

%webmasters ALL=(ALL) NOPASSWD: /usr/local/bin/rsync --server \
--sender -vlogDtpre.if . /var/www/

相同的方法可用于调试新的服务器配置或密钥登录。一旦将设置调整为按需运行,就可以将日志级别设置降低回 **INFO** 用于 sshd(8),并降低回 **ERROR** 用于 **internal-sftp**。此外,一旦脚本被设置为在完全自动模式下运行,客户端日志信息可以通过在启动时设置 **-y** 选项,使用 syslog(3) 系统模块代替 **stderr**。

调试服务器配置

[编辑 | 编辑源代码]

在调试模式下运行服务器会提供大量有关连接的信息,以及少量有关服务器配置的信息。服务器的调试级别 (**-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) 进行评估时,userhostaddr 是扩展测试所需的最小值。以下将打印出如果用户 fred 尝试从地址 192.0.2.15 登录到主机 server.example.org 时将应用的配置。

$ /usr/sbin/sshd -T -C user=fred,host=server.example.org,addr=192.0.2.15

还可以传递两个参数,laddrlport。它们分别指的是服务器的 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 [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1

对于错误的密码,合适的客户端可能会在尝试将 arcfour 密码强加到服务器上时显示以下内容。

no matching cipher found: client arcfour server [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]

有时在对客户端问题进行故障排除时,需要查看服务器日志。在 OpenSSH 6.7 中删除了不安全的 MAC,在 OpenSSH 7.2 中删除了不安全的密码,但一些第三方客户端可能仍会尝试使用它们来建立连接。在这种情况下,客户端可能不会提供太多信息,而只会显示一个含糊的提示,即服务器意外地关闭了网络连接。但是,服务器日志将显示发生了什么。

fatal: no matching mac found: client hmac-sha1,hmac-sha1-96,hmac-md5 server [email protected],[email protected],[email protected],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** 文件,或者私钥本身。

截至撰写本文时,看起来邮件列表和论坛上描述的几乎所有基于密钥的身份验证失败都是通过解决这两种情况中的任何一种或两种来解决的。因此,在遇到错误消息“权限被拒绝 (publickey,keyboard-interactive)”或类似消息时,请参阅关于 公钥身份验证 的部分。然后,请参阅 sshd(8) 的手册页及其关于授权密钥的部分。通常,虽然不总是这样,但私钥权限不正确时会很明显。

$ ssh -i ~/.ssh/fred-193.ed25519 [email protected]
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         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** 中的公钥不匹配,则连接将被拒绝,并显示错误“权限被拒绝 (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]

如果您看到“身份验证失败次数过多”错误,则有两种解决方案。

解决此错误的一种方法是使用 **-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 [email protected]
sign_and_send_pubkey: signing failed for ED25519 "/home/fred/.ssh/key-ed25519" from agent: agent refused operation
[email protected]: Permission denied (publickey).

解决此问题的方法是确保任何其他帐户都无法读取或写入私钥,也无法写入 **.ssh** 目录或其父目录。

调试 Chroot 的 SFTP 帐户

[编辑 | 编辑源代码]

最常见的问题似乎是目录权限错误。 Chroot 目录及其所有上层目录必须由 root 拥有,并且任何其他用户或组都不可写入。 即使这些目录的组成员资格不需要是 root,但如果其中任何一个不是 root,则它也不能被组写入。 未能使用正确的拥有权会导致无法使用受影响的帐户登录。 尝试登录时出现的错误在客户端侧看起来像这样。

$ sftp [email protected]
[email protected]'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** 发送任何数据,而不是保持静默。 在这种情况下,输出(即服务器上的 **stdout**)将由客户端在 **stdin** 上接收,但不匹配任何正确的协议,因此导致客户端断开连接。 因此,即使在使用 RC 脚本的情况下,来自服务器的响应也必须保持 8 位清洁,否则将发生错误。

$ sftp server.example.org
Received message too long 1400204832

这一条消息将是主要线索。 使用 **-v** 提高 SFTP 客户端的详细程度不会提供更多相关信息。

此外,服务器上的标准日志只会显示客户端断开连接,而不会提供任何原因。 在较高的日志级别,可能会注意到一些无关的读取和相应的丢弃,但仅此而已。 以下是在 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
...

同样,在使用 SFTP 时,任何 RC 脚本都不能在 **stdout** 上产生任何输出,否则会破坏连接。 如果 RC 脚本确实产生了输出,则必须将其重定向到系统日志、文件或发送到 **stderr** 而不是 **stdout**。 常规的交互式 SSH 连接不会因使用 **stdout** 而受到干扰,客户端只会显示发送的内容。 有关更多信息,请参阅 **sshd(8)** 手册页的“SSHRC”部分。

同样的限制适用于通过 **stdin** 和 **stdout** 运行的 SSH 服务的任何其他部分,例如 **ProxyJump** 或 **ProxyCommand** 的某些用法。 因此,潜在干扰的另一个示例是在使用客户端的 **LocalCommand** 指定在成功连接到服务器后在本地机器上执行的命令时。 来自它的任何输出也需要重定向到 **stderr**。 如果 **LocalCommand** 最终干扰了 **ProxyJump**,则连接将在 **stdout** 被使用时似乎挂起。

调试 SSH 代理拥有正确的私钥但不使用它时的情况

[编辑 | 编辑源代码]

在旧版本的 OpenSSH 中,在将私钥加载到代理时,公钥也必须在客户端可用。 如果它不可用,则代理将无法使用私钥,除非进行其他安排。 这种情况的一个症状是,虽然通过运行时参数指定密钥有效,但通过代理使用相同的密钥则无效。

升级到较新的 OpenSSH 版本是一个更好的选择。否则,一种变通方法是将私钥指定为客户端的运行时参数或在 ssh_config(5) 文件中,客户端会从那里找到相应命名的公钥文件。重要的是,客户端仍将使用代理中的密钥,但使用指定的匹配公钥文件,因此私钥文件不必包含任何内容,甚至可以为空。

$ ssh -i some_key_ed25519 [email protected]

但是,如果不想在文件系统上访问私钥,或者私钥只在代理中,而不在文件系统上可用,那么可以改为直接指定公钥。

$ ssh -i some_key_ed25519.pub [email protected]

无论哪种方式,另一种方法是使用IdentityFile配置指令在 ssh_config(5) 中命名密钥。如果包含公钥的文件丢失,可以使用-y选项使用 ssh-keygen(1) 从私钥重新生成。

SSH 客户端错误消息和常见原因

[edit | edit source]

作为以上内容的重述,下面是一些客户端错误消息,以及 [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
[email protected]: Permission denied (publickey,keyboard-interactive).

这通常是由于用户名错误、身份验证方法错误、SSH 密钥或 SSH 证书错误,或者目标系统上授权密钥文件的文件权限错误造成的。如果是基于 SSH 密钥的身份验证问题,请参阅关于 公钥身份验证 的章节以获得更全面的介绍。如果它是 SSH 证书的问题,请参阅相应的 基于证书的身份验证 章节。它也可能是目标系统上的AllowGroupsDenyGroups或类似配置指令的服务器端设置问题。所有这些可能性只有通过检查 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 [email protected]
...
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

在上面的 shell 会话中,服务器急需更新,如所示它尝试使用的过时密钥交换算法。再次强调,解决方案是更新过时的软件。如果某个特定旧版本的特定操作系统必须使用更长时间,请查看是否可以获取 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
[email protected]
[email protected]

 

  1. "RFC 4252: The Secure Shell (SSH) Authentication Protocol". IETF. 2006. Retrieved 2021-08-10.
  2. Tucker, Darren (2021-08-10). "Re: ssh authlog: Failed none for invalid user". OpenBSD. https://marc.info/?l=openbsd-misc&m=162858437916966&w=2. Retrieved 2021-08-10. 
  3. Tucker, Darren (2019-04-01). "IdentityFile vs IdentitiesOnly". openssh-unix-dev mailing list. https://lists.mindrot.org/pipermail/openssh-unix-dev/2019-April/037700.html. Retrieved 2019-04-04. 
  4. "SSH Troubleshooting Guide". IT Tavern. 2023-01-17. Retrieved 2023-01-26.
华夏公益教科书