OpenSSH
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
中设置,并可以全局应用到该帐户的所有连接,或者通过 Host
或 Match
块选择性地应用于特定连接。
确保不活跃的交互式会话超时
如果服务器在未达到 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)
还可以在触发规则时使用 twist
或 spawn
运行脚本。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 地址的过滤或 AllowUsers
和 DenyUsers
指令。
使用 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 信号来重新加载配置。