MaxStartups

通过指定三个由冒号分隔的值 start:rate:full,可以启用随机早期丢弃。未认证连接的数量达到 start 所指定的值后,sshd(8) 将开始按 rate 指定的百分比拒绝新连接。随着未认证连接数量接近 full 所指定的限制,拒绝连接的比例将线性增加,直到达到 100%。此时,所有新的连接尝试将被拒绝,直到积压的连接数量下降。

例如,如果在 sshd_config(5) 中设置 MaxStartups 5:30:90,那么当有 5 个新的连接等待认证时,服务器将开始丢弃 30% 的新连接。当未认证连接数量增加到 90 时,100% 的连接将被丢弃。

默认设置中,full 的值已增加到 100 个待处理连接,以使其更难因攻击或负载过重而遭受拒绝服务。因此,新的默认值是 10:30:100

另外,如果没有通过网络层的包过滤器或类似轮询 DNS 等技巧来管理传入连接,可以在 SSH 服务器本身限制连接数量。将 MaxStartups 设置为一个整数值将设置一个硬性限制,限制 SSH 守护进程的最大并发未认证连接数。

MaxStartups 10

当有更多连接时,它们将被丢弃,直到某个连接的认证成功或 LoginGraceTime 过期。旧的默认值为 10。

防止会话超时

SSH 会话中可以追踪两个连接:网络的 TCP 连接和加密的 SSH 会话。某些隧道和 VPN 可能并不始终处于活动状态,因此存在会话超时甚至 TCP 会话在路由器或防火墙上超时的风险。网络连接可以通过 TCPKeepAlive 来追踪,但它并不能准确表示实际 SSH 连接的状态。然而,它是一个有用的指示,能反映网络状态。客户端或服务器可以通过心跳保持加密连接的活动状态。

客户端,如果全局配置没有设置,可以使用 ServerAliveInterval 来选择服务器保持活动心跳的秒数,并使用 ServerAliveCountMax 来设置在加密 SSH 会话被认为已关闭之前,允许错过的最大客户端消息数。

ServerAliveInterval  15
ServerAliveCountMax  4

在服务器端,ClientAliveInterval 设置客户端保持活动心跳的秒数,ClientAliveCountMax 设置在加密 SSH 会话被认为已关闭之前,允许错过的最大客户端消息数。如果在此期间没有发送或接收任何数据,sshd(8) 会通过加密通道发送消息请求客户端的响应。如果在 sshd_config(5) 中将 ClientAliveInterval 设置为 15 秒,并将 ClientAliveCountMax 设置为 4,则未响应的 SSH 客户端将在大约 60 秒后(= 15 x 4 秒)被断开连接。

ClientAliveInterval  15
ClientAliveCountMax  4

如果使用了基于时间的 RekeyLimit,但时间限制比 ClientAliveInterval 心跳的间隔短,那么较短的重新密钥限制将用于心跳间隔。

这种原理与服务器端类似。同样,这可以在 ~/.ssh/config 中设置,并可以全局应用到该帐户的所有连接,或者通过 HostMatch 块选择性地应用于特定连接。

确保不活跃的交互式会话超时

如果服务器在未达到 ClientAliveInterval 选项配置的超时时没有断开空闲的 SSH 帐户,则可以通过设置 shell 的 TMOUT 变量来解决这个问题。当设置 TMOUT 时,它指定了 shell 在关闭前等待输入的秒数,从而终止 SSH 会话。请注意,这意味着按下回车键,因为单纯的输入字符不足以防止超时,必须输入完整的一行才会重置计时器。

在服务器端,可以检查 SSH_CONNECTION 变量的存在,通常除非当前处于 SSH 会话中,否则该变量为空,以此区分 SSH 会话和本地 shell。如果帐户允许更改超时设置,则可以将以下内容添加到帐户的个人配置文件中(如 ~/.profile):

if [ "$SSH_CONNECTION" != "" ]; then
    # 10分钟
    TMOUT=600
    export TMOUT
fi

如果不允许帐户更改此设置,则必须将其放在全局配置文件中,并将其设置为只读,例如可以放在 /etc/profile.d/ 下的某个位置:

if [ "$SSH_CONNECTION" != "" ]; then
    # 10分钟
    TMOUT=600
    readonly TMOUT
    export TMOUT
fi

上述示例适用于 Bourne shell、其衍生版本以及一些其他 shell。某些 shell 可能有其他选项可用,例如在 tcsh(1) 中找到的实际自动登出变量。

TCP Wrappers,也叫 tcpd(8)

从 OpenSSH 6.7 开始,sshd(8) 不再支持 TCP Wrappers(也称为 tcpd(8))。因此,本节内容仅适用于 6.6 及更早版本。可以使用的替代选项包括 PF [1]、ipf、NFTables 甚至旧版的 IPTables。特别是,OpenSSH 服务器Match 指令支持按 CIDR 地址进行过滤。可以使用这些替代方案,并记住“等效的安全控制”这一短语,这可以帮助避免由安全审计员引起的麻烦,特别是他们的工作表上可能仍然保留了“tcpwrappers”这一复古选项。

tcpd(8) 程序曾是用于访问控制的实用工具,控制进入 Internet 服务的请求。它用于与可执行文件一一对应的服务,如 sshd(8),并且这些服务已被编译为与 tcpd(8) 进行交互。它首先检查白名单(/etc/hosts.allow),然后检查黑名单(/etc/hosts.deny)来批准或拒绝访问。任何给定连接尝试匹配的第一个规则都会被应用。在 /etc/hosts.deny 中,默认设置为阻止访问,如果在 /etc/hosts.allow 中没有匹配的规则:

sshd: ALL

除了访问控制,tcpd(8) 还可以在触发规则时使用 twistspawn 运行脚本。spawn 启动另一个程序作为 tcpd(8) 的子进程。例如,在 /etc/hosts.allow 中:

sshd: .example.org : allow
sshd: .example.com : spawn \
    /bin/date -u +"%%F %%T UTC from %h" >> /var/log/sshd.log : allow

其中,变量 %h 扩展为连接客户端的主机名或 IP 地址。hosts_access(5) 手册页包含了所有可用变量的完整描述。由于示例中使用的程序 date 也使用相同的符号表示变量,转义字符(%)必须使用两个百分号(%%)来转义,以便 tcpd(8) 忽略它,并将其正确传递给 date

twist 会用另一个程序替换请求的服务。它有时用于蜜罐,但也可以用于任何其他用途。例如,在 /etc/hosts.deny 中:

sshd: .example.org : deny
sshd: ALL : twist /bin/echo "Sorry, fresh out." : deny

使用 TCP Wrappers 时,首先会搜索白名单 /etc/hosts.allow,然后搜索黑名单 /etc/hosts.deny。第一个匹配的规则会被应用。如果没有找到适用的规则,则默认允许访问。由于 TCP Wrappers 不再推荐使用,最好使用其他更好的替代方案。参见 sshd_config(5) 中的 Match 指令,关于 CIDR 地址的过滤或 AllowUsersDenyUsers 指令。

使用 TCP Wrappers 仅允许特定子网的连接

TCP Wrappers 曾允许通过将 sshd(8) 设置为仅监听本地地址,拒绝所有外部连接。为了使用 TCP Wrappers 实现这一点,可以在 /etc/hosts.deny 中添加一行,阻止所有连接:

sshd: ALL

然后在 /etc/hosts.allow 中为特定 IP 范围(使用 CIDR 表示法)或域名添加一个例外:

sshd: 192.0.32.0/20

同样,使用这种方法也可以限制仅允许本地访问(127.0.0.0/8),在 /etc/hosts.allow 中添加一行:

sshd: 127.0.0.0/8

最佳实践是从所有来源阻止访问,然后逐步放开例外。请注意,如果使用域名而不是 IP 范围,则 DNS 条目必须已正确设置,并且 DNS 本身必须可访问。然而,以上内容仅具有历史意义。如今,使用 sshd_config(5) 进行相应设置,并通过 OpenSSH 服务器Match 指令或操作系统本身的包过滤器来实现这些限制,比使用 TCP Wrappers 更为合适。

扩展互联网服务守护进程(xinetd)

扩展互联网服务守护进程(xinetd(8))可以提供多种访问控制。这包括但不限于远程主机的名称、地址或网络,以及访问的时间。它可以限制每个服务的请求数量,并且在负载超过某一限制时停止服务。

xinetd 作为超级守护进程监听传入请求,并根据需要启动 sshd(8)。因此,首先需要停止 sshd(8) 作为独立守护进程运行。这可能需要修改 System V 初始化脚本或 Upstart 配置文件。然后为 SSH 服务创建一个 xinetd 配置文件,通常该文件会放在 /etc/xinetd.d/ssh 中。-i 参数非常重要,它告诉 sshd(8) 程序是通过 xinetd 启动的。

以下是一个例子:

service ssh
{
    socket_type     = stream
    protocol        = tcp
    wait            = no
    user            = root
    server          = /usr/sbin/sshd
    server_args     = -i
    per_source      = UNLIMITED
    log_on_failure  = USERID HOST
    access_times    = 08:00-15:25
    banner          = /etc/banner.inetd.connection.txt
    banner_success  = /etc/banner.inetd.welcome.txt  
    banner_fail     = /etc/banner.inetd.takeahike.txt

    # log_on_success  = PID HOST DURATION TRAFFIC EXIT
    # instances       = 10
    # nice            = 10
    # bind            = 192.168.0.100
    # only_from       = 192.168.0.0
    # no_access       = 192.168.54.0
    # no_access       += 192.168.33.0
}

最后,通过向 xinetd(8) 发送 SIGHUP 信号来重新加载配置。

最后修改: 2025年01月19日 星期日 21:47