OpenSSH服务器 sshd 是高度可配置的,允许你限制谁可以连接到服务器,这些用户可以采取什么行动,以及它允许什么行动。每个现代类Unix操作系统都安装了 sshd 作为基本操作系统的一部分。
我们将学习运行 sshd 的一些基础知识,并继续学习各种全局配置选项。本书相关章节将讨论更具体的选项。
第三章:OpenSSH服务器sshd是否在运行?配置 sshd备用配置文件和端口验证 sshd_config 更改调试 sshd(8) 配置 sshd(8)设置主机密钥网络选项横幅和登录信息身份验证选项验证DNS登录尝试系统管理功能更改加密算法有多少未经身份验证的连接?限制用户或组的访问Root SSH 访问令牌Chrooting 用户填充Chroot分配Chroot 目录选择要chroot的用户调试Chroot保护SSH服务器
从客户端测试服务器是否运行可访问的SSH守护进程的最简单方法是尝试登录服务器。虽然这在一切正常的情况下都很好,但连接失败意味着客户端或服务器可能会被破坏,或者你可能在在中间有一个数据包过滤器。SSH通常在TCP端口22上运行。使用 netcat(1) 查看是否可以访问守护进程。
xxxxxxxxxx$ nc -v devio.us 22Connection to devio.us 22 port [tcp/ssh] succeeded!SSH-2.0-OpenSSH_7.0^C当你通过原始TCP连接时,sshd 会返回一个横幅(banner),显示SSH协议的版本、SSH服务器软件和软件版本。此主机使用OpenSSH7.0版提供的SSH协议2。
如果你没有得到类似的结果,也许 sshd 没有运行,或者遇到了数据包过滤器。
从服务器检查 sshd 进程是否在运行:
xxxxxxxxxx$ ps ax | grep sshd626 - Is 0:00.03 /usr/sbin/sshd31960 - Is 0:00.38 sshd: mwlucas [priv] (sshd)44387 - S 0:05.75 sshd: mwlucas@pts/0 (sshd)此主机显示三个 sshd(8) 进程。
OpenSSH通过权限分离提高了安全性,本章结尾的“保护SSH服务器”中对此进行讨论。如果有人故意禁用了权限分离,并且运行 sshd 不安全,你将看不到无权限的会话。
【你需要造成身体伤害,直到特权分离重新开启。】
如果 sshd 没有运行,请通过操作系统配置工具启用它。
大多数操作系统将 sshd 作为独立服务器运行,不需要任何命令行参数。配置 sshd 的常用方法是通过 /etc/ssh/sshd_config 中的关键字。不过,在开始处理该文件中的更改之前,你应该知道如何测试和调试它们。
OpenSSH使调试 sshd 配置变得尽可能简单。无论是否调试,你都必须是root才能运行 sshd 。最简单的调试方法是备用配置文件、备用端口和调试模式。
假设你想编辑 sshd_config ,但需要确保更改按预期工作。-f 命令行参数告诉 sshd(8) 使用备用配置文件:
xxxxxxxxxx# /usr/sbin/sshd -f sshd_config.test注意,我使用 sshd 的完整路径执行了此调试配置。OpenSSH的 sshd 在接受连接时会自动重新执行,它需要完整的路径才能执行。
如果你没有给出完整的路径,你会得到一个错误,形如 sshd re-exec requires execution with an absolute path.
只有一个 sshd 实例可以连接到特定的TCP端口。你的测试 sshd 进程可能无法启动,因为它无法绑定到端口22。你可以编辑 sshd_config.test 来为你的测试进程分配另一个端口,但在将其转移到生产环境时,你必须重新编辑该文件,我们都知道这正是停机报告中突出显示的一点。相反,重写配置的TCP端口,并使用 -p 参数分配一个新端口:
xxxxxxxxxx# /usr/sbin/sshd -f sshd_config.test -p 2022测试进程现在正在2022端口上监听。(注意,-p 不能覆盖将 sshd 绑定到端口和地址的 ListenAddress 关键字,请参阅本章后面的”网络选项“)。
通过在命令行上设置备用配置文件和端口,你可以测试新配置、批准它并将其投入生产,确信在进行最终的未经测试的更改时没有破坏文件。(请注意,我从未以这种方式破坏过系统。)如论如何,请保存你的原始 sshd_config ,以防你的更改导致测试未暴露的问题。
完成测试后,记得终止测试 sshd 进程。
也许你想做一个小的改变,认为你不需要进行全面的测试。您可以要求 sshd(8) 使用 -t 标志验证配置文件和所有密钥文件:
xxxxxxxxxx# sshd -t/etc/ssh/sshd_config: line 112: Bad configuration option: ExposeAuthInfo/etc/ssh/sshd_config: terminating, 1 bad configuration options此主机上安装sshd版本太旧,无法支持 ExposeAuthInfo 关键字,或者操作系统打包程序故意删除了该选项。
sshd(8) -d 标志告诉sshd在前台调试模式下运行,而不与控制终端分离。在调试模式下,sshd 只能处理一个登录请求——不,一次不能处理一个请求。它处理一次登录或登录尝试,然后退出。不要在生产中这样做;在备用端口上运行它。调试会实时显示 sshd 进程所做的一切,就像这样:
xxxxxxxxxx# /usr/sbin/sshd -p 2022 -ddebug1: sshd version OpenSSH_7.5, OpenSSL 1.0.2l-freebsd 25 May 2017debug1: private host key #0: ssh-rsaSHA256:N+faE/OyKhlho8MR8Vw3uhdo75aiuhYotnP/gOOe82Edebug1: private host key #1: ecdsa-sha2-nistp256 SHA256:Q1buYGtWowrN1/8g/EaTEMQr+69h+/Pai3xI4LXN0c8debug1: private host key #2: ssh-ed25519 SHA256:0TCTf0jZUxzu8dahNrLmuKu19T0BkruI4e3mPOjVInEdebug1: rexec_argv[0]='/usr/sbin/sshd'debug1: rexec_argv[1]='-p'debug1: rexec_argv[2]='2022'debug1: rexec_argv[3]='-d'debug1: Bind to port 2022 on ::.debug1: Server TCP RWIN socket size: 65536Server listening on :: port 2022.debug1: Bind to port 2022 on 0.0.0.0.debug1: Server TCP RWIN socket size: 65536Server listening on 0.0.0.0 port 2022.调试会话从您的 sshd(8) 版本的标识信息开始——在本例中,是使用OpenSSL 1.0.2l构建的OpenSSH 7.5,作为FreeBSD的一部分。然后,我们看到使用RSA2、ECDSA和ED25519加载了三个私钥。守护进程解析其参数并绑定到端口。
如果守护进程无法启动,它会在这里非常清楚地说明原因。你可能需要阅读手册页或进行一些互联网搜索来找出错误的含义,但你会知道确切的问题。
使用SSH客户端连接到此服务器,当服务器和客户端就加密协议达成一致、用户尝试进行身份验证以及协商X转发等各种SSH功能时,您将获得数百行调试输出。我不会引导您完成这样的会话,因为输出因客户端、身份验证方法以及请求和提供的SSH功能而异。
如果SSH有问题,请在调试模式下运行服务器,连接客户端,然后读取输出。大多数情况下,sshd 会准确地告诉你问题所在。
调试完成后,退出客户端。sshd(8) 进程将自行清理并退出。您还可以通过点击 CTRL-C 来随意终止 sshd 并抛出客户端。
如果单个 -d 不能提供足够的细节,请添加多个倍数以增加详细程度。运行 /usr/sbin/sshd -dd 应该会打消你的好奇心。如果没有,添加更多的 -d ,直到你不再好奇。
sshd(8)本章讨论一些普遍有用的 sshd(8) 选项。大多数 sshd_config 选项出现在它们最有用的章节中,也就是说,影响X转发的选项出现在【第8章 X转发】中,而证书选项出现在【第14章 证书颁发机构】中。
您的操作系统附带的OpenSSH版本可能不支持本书中描述的所有关键字。我是基于OpenSSH 7.6编写的。一些操作系统要么发布旧版本,要么出于自身原因故意删除功能。如果配置选项在您的服务器上不起作用,请查阅操作系统文档或咨询供应商(如果你不喜欢供应商的回答,可以更大声地、带着恶意地提问)。
【FreeBSD14.1是OpenSSH 9.7版,FreeBSD15是OpenSSH 10.0版。】
HostKey 关键字给出了保护私钥的文件的完整路径。每种支持的加密算法都使用单独的文件:
xxxxxxxxxxHostKey /usr/local/etc/ssh/ssh_host_rsa_keyHostKey /usr/local/etc/ssh/ssh_host_ecdsa_keyHostKey /usr/local/etc/ssh/ssh_host_ed25519_key默认文件以其包含的密钥类型命名。文件 ssh_host_rsa_key 包含一个RSA密钥, ssh_host_ed25519_key 是一个ED25519密钥,以此类推。这不是强制性的——OpenSSH会找出文件中的密钥类型,并在适当的情况下加载它——但这绝对是最佳实践。将RSA密钥放在以ED25519命名的文件中会让所有人感到困惑。
不同的操作系统对丢失的关键文件的处理方式不同。BSD风格和基于RedHat的系统会自动创建丢失的密钥文件。许多Linux系统要求系统管理员手动创建丢失的密钥文件,但将密钥创建到其常用的系统管理工具中。例如,当你运行 dpkg-reconfigure openssh-server 时,基于Debian的系统会创建丢失的密钥文件。
【第七章 SSH密钥】介绍了使用OpenSSH的本机工具创建主机密钥。
你可以控制 sshd(8) 如何使用网络,从IP版本到一直到TCP端口:
xxxxxxxxxxPort 22AddressFamily anyListenAddress 0.0.0.0ListenAddress ::Port关键字控制 sshd 使用的TCP端口。互联网标准要求SSH在端口22上运行。一些组织使用不同的SSH端口来提高安全性。在不寻常的端口上运行SSH实际上并不能帮助保护SSH,但它会减少SSH破解蠕虫的登录尝试次数,如本章后面的”保护SSH服务器“所述。它还可以让你避开特别无效的防火墙。用 -p 覆盖命令行上的 Port 关键字。
AddressFamily 是指sshd使用的TCP/IP版本。
inet inet6 any ,表示无论哪种协议都可以一些操作系统修补 sshd(8) 以支持非TCP/IP协议,如流控制传输协议(Stream Control Transmission Protocol,SCTP)。
许多主机都具有多个IP地址。默认情况下,sshd 会监听所有这些设备上的传入请求。如果要限制 sshd 附加到的IP地址,请使用 ListenAddress 关键字。
0.0.0.0 表示所有IPv4的地址:: 则表示所有IPv6地址(某些操作系统使用 :: 表示所有IPv4和IPv6地址,因为它们为什么只允许您打开IPv6服务?)
每个 ListenAddress 都有一个IP地址作为参数,但你可以根据需要使用任意多个 ListenAddress 关键字。明确列出你希望SSH服务器接收连接的每个IP地址。
如果一个主机有许多IP地址,而你只想阻止其中几个的SSH访问,建议使用数据包过滤阻止流量,这比使用许多 ListenAddress 语句更容易。
你还可以通过在 ListenAddress 语句中指定端口,使用 ListenAddress 在特定IP地址上添加其他端口,比如:
xxxxxxxxxxListenAddress 0.0.0.0ListenAddress 192.0.2.8:2222我们的第一个 ListenAddress ,0.0.0.0 ,告诉 sshd 监听这台机器上的所有地址,默认端口22。因此我们将在所有地址上获得端口22。第二个 ListenAddress 使 sshd 也监听地址 192.168.2.8 上的端口 2222 上的连接。每个地址都可以有自己的 ListenAddress 语句。
xxxxxxxxxxListenAddress 192.0.2.8:2222ListenAddress 192.0.2.9:25ListenAddress 192.0.2.10:80三个不同的地址,每个地址都有不同的端口。请注意,让 sshd 监听SMTP和HTTP端口是不明智地,但OpenSSH的设计并不是为了防止你做通常不明智的事情。如果你被困在一个幼稚的防火墙后面,除了端口80和443外,什么都阻止,那么在这些端口上运行 sshd 可以让你避开防火墙。
【规避公司防火墙对你就业的影响留给读者练习】
许多系统管理员希望用户在登录前向其显示一条消息。这被称为横幅(banner)。SSH协议不要求客户端显示横幅。服务器可以提供横幅,但你不能保证用户会看到它。ssh(1) 和PuTTY都会显示横幅。将关键字 Banner 设置为文件的完整路径:
xxxxxxxxxxBanner /etc/ssh/banner请注意,如果横幅确实有效,它可能会干扰通过SSH运行的自动化流程。在某些地方,横幅可以作为对入侵者的法律通知。(请注意,我不知道有谁通过使用这样的横幅警告被成功起诉,但这是法律。)选择你喜欢的头痛。
如果用户使用公钥进行身份验证,并且客户端确实显示了横幅,则登录将继续。在登录完成之前,没有人会看到你的法律部门关于登录主机的措辞精细的警告。
你可以可靠地显示当天的系统消息 /etc/motd 。不过,此消息在客户端经过身份验证后才会出现,因此它可能不符合你的需求。默认情况下,关键字 PrintMotd 设置为 yes ,但你可以将其关闭:
xxxxxxxxxxPrintMotd yes在使用可插拔身份验证模块(Pluggable Authentication Modules,PAM)的系统上,PAM模块可能负责打印 /etc/motd 。如果你在启用或禁用 /etc/motd 的显示时遇到问题,请检查你的PAM配置。
用户登录后,sshd 会打印用户上次登录的时间以及登录的位置。要关闭此功能,请将 PrintLastLog 设置为 no :
xxxxxxxxxxPrintLastLog yes虽然这似乎没有必要,但我强烈建议让 PrintLastLog 保持打开状态。如果用户看到他们之前的登录来自国外或在荒谬的时间,可以提醒管理员注意入侵。
在默认的OpenSSH安装中,用户可以在2分钟内尝试单个SSH会话中登录6次。你应该在几乎所有地方使用公钥身份验证(第7章,“SSH密钥”),但即使是有密码的用户也应该能够在20秒内错误地键入密码。你可以更改尝试的时间和次数。
LoginGradeTime 关键字控制 sshd 给用户多长时间进行身份验证。如果会话连接到 sshd 达到设定时长却没有成功进行身份验证,则连接终止。此关键字的值可以使用 s 表示秒、m 表示分钟、h 表示小时,例如:
xxxxxxxxxxLoginGraceTime 2m你还可以控制用户在使用 MaxAuthTries 的单个连接中尝试身份验证的次数。默认为 6 次:
xxxxxxxxxxMaxAuthTries 6如果用户在单个会话中允许的尝试达到 MaxAuthTries 设置的值的一半失败时,sshd 会记录一次失败。身份验证尝试包括公钥身份验证和密码。MaxAuthTries 失败后,用户必须启动新的SSH会话并重试。
我通常的失败程序是六次登录失败,然后记住我在这台机器上有一个不同的用户名。当我采纳【第5章 SSH客户端】中关于更改用户名的建议,并像【第7章 SSH密钥】中那样在任何地方安装我的公钥时,这个问题就消失了。
“Login failed from boss’s computer”这样的日志消息会让你叹气。类似“Login succeeded from Hacker Haven Nation”的日志消息应触发警报。IP地址的所有者控制该地址的反向DNS。控制其IP地址反向DNS的入侵者可以将明显的主机名更改为公司内部的某个名称。为了防止这种攻击,sshd 可以验证针对转发DNS条目的连接尝试。
xxxxxxxxxxUseDNS no当此值设置为 yes 时,每次客户端连接时,sshd 都会查找源IP的主机名,然后查找主机名的IP地址。如果DNS名称不匹配,sshd 将拒绝连接。
假设入侵者控制了其IP地址192.0.2.99的反向DNS(reverse DNS)。他在你的组织中为他提供了一个主机名,如 dhcp12.mwl.io ,并连接到你的SSH服务器。你的SSH服务器向其DNS服务器请求 dhcp12.mwl.io 的IP地址。如果该DNS条目不存在,或指向了其他IP, sshd 将拒绝连接。
如果DNS失败,sshd 会在允许连接之前等待完整的DNS超时。
UseDNS要求你的DNS整洁(tidy)、连贯(coherent)且正确(correct)。看起来合理,但实际使用会遇到很多问题,比如入侵者可以污染服务器的DNS缓存,DNS检查将无济于事;如果你是家庭用户,你的ISP可能会控制你连接上的反向DNS。此外,DNS检查可能会增加系统负载。如果你同时为数百或数千名SSH用户提供服务,那么负载可能会更大。当DNS失败时,失败的DNS检查将减慢所有SSH登录的速度。最后,许多IPv6站点还没有配置反向DNS,在可预见的未来也不会配置。
综上,不建议启用 UseDNS 。
告诉 sshd(8) 在哪里用 PidFile 关键字存放其进程ID文件。不要乱动。许多管理工具愚蠢地(foolishly)使用PID文件。
xxxxxxxxxxPidFile /var/run/sshd.pid此文件是在 sshd(8) 降低其权限之前编写的,因此它可以由root拥有。如果要禁止写入PID文件,可将 PidFile 设置为 none 。
sshd(8) 进程通过 syslogd 进行日志记录,默认为 AUTH 功能和 INFO 级别。使用 SyslogFacility 和 LogLevel 关键字控制这些设置:
xxxxxxxxxxSyslogFacility AuthLogLevel INFOSyslogFacility 关键字接收任何 syslog 设置。有关设置列表,可查看 syslogd(8) 的文档。
syslogd 不仅使用 LogLevel 来确定向哪里发送日志消息,sshd(8) 还使用它来确定向 syslogd 发送什么:
QUIET:不记录
FATAL:仅记录 sshd(8) 死亡事件
ERROR:出问题了才报告
INFO:记录问题,以及客户登录和注销的事件
VERBOSE:记录所有不侵犯隐私的细节,包括用于身份验证的公钥指纹
DEBUG1、DEBUG2、DEBUG3:此类级别发送的数据足以侵犯用户隐私。调试消息被发送到syslogd。
大多数默认日志记录系统都无法捕捉到这种程度的细节;你需要配置你的配置以捕获所有这些详细信息。
此外,不要使用传统的未加密 syslogd 在开放网络上发送调试数据。
你可能会在配置中找到关键字 Cipher 和 Mac 。它们不会出现在OpenSSH提供的 sshd_config 中,但一些操作系统会添加它们。这些设置允许你更改服务器支持的加密方法。
不要乱搞这些设置,只会伤到你自己。
某些组织,最常见的是政府,只要求使用经过批准的加密算法。最著名的是美国的FIPS标准。这些组织有非常具体的文件,要求如何配置SSH以符合要求。
OpenSSH通过启动一个单独的进程来处理每个传入的连接,避免了线程编程的麻烦。对运行此类程序的主机的常见拒绝服务攻击是启动一大堆客户端连接,直到服务器耗尽所有资源并崩溃。OpenSSH通过 MaxStartups 选项避这个问题。
MaxStartups 允许你设置多个与SSH守护进程同时进行的未经身份验证的连接。一旦有这么多连接试图进行身份验证,sshd 将不会接受另一个连接,直到现有连接失败或现有未经身份验证的连接的 LoginGraceTime 到期。像 10 这样的简单值可以保护服务器,但不允许你登录以尝试防御正在进行的攻击。
更好的选择是使用随机早期丢弃(Random Early Drop,RED),这是一种长期被网络工程师用来避免拥塞的协议。DOS攻击并不完全是网络拥塞,但它与网络拥塞有一系列共同特征。RED通过设置节流限制来工作。一旦传入连接超过下限,sshd 就会给每个后续传入连接一个被完全拒绝的机会。拒绝连接的可能性会增加,直到未经身份验证的连接数量达到上限,此时所有连接都会被拒绝。使用RED意味着攻击者需要向SSH服务器投入大量资源,以确保系统管理员无法进入。这并没有是攻击变得不那么烦人,但它确实未系统管理员(和合法用户)在攻击期间提供了登录的机会。
通过指定下限、拒绝连接的初始机会和上限阈值,为 sshd 配置RED。默认值为10、30、100。
xxxxxxxxxxMaxStartups 10:30:100这意味着 sshd 最多可以同时接受10个未经身份验证的连接。第11个同时未经身份验证的连接有30%的可能性被拒绝。连接被拒绝的几率线性增加,直到达到100的上限阈值,此时所有连接都被拒绝。
使用RED意味着,如果你在DOS攻击期间继续尝试连接,你有机会能登录进去。
本章末尾的“保护SSH服务器”中详细讨论了如何防御 sshd 。
许多网络应用程序依赖于底层操作系统的用户帐户。人们通过网页或专有客户端使用应用程序,但从未真正通过SSH连接到主机。如果运输部门的Fred需要访问企业资源规划系统来打印他的运输标签,而ERP系统需要一个底层用户帐户,那么主机需要Fred的帐户。这不是理想的做法,但这是现实。如果您负责此类应用程序,请配置主机,以便此类用户无法登录到服务器。
OpenSSH通过 DenyUsers、AllowUsers、DenyGroups 和 AllowGroups 选项支持用户限制。这些选项采用逗号分隔的用户或组列表作为参数,并安装特定顺序进行处理。第一次匹配成功后忽略后续匹配。
DenyUsers 列表中的用户不能通过SSH登录,即使稍后在 AllowUsers 或 AllowGroups 中列出。AllowUsers 列表中的用户可以通过SSH登录,除非 DenyUsers 中明确禁止。DenyGroups 中列出的组的用户无法通过SSH登录,除非 AllowUsers 语句特别允许。这允许你为用户设置例外。AllowGroups 中列出的组的用户可以通过SSH登录。此外,AllowUsers 或 AllowGroups 条目的存在意味着没有其他人可以登录。系统拒绝所有未明确允许的人通过SSH登录。
这些限制在第一场比赛中起作用。语句按顺序处理,当用户匹配规则时,规则立即应用,处理停止。
困惑吗(confused)?以下一些示例中,我们的主机有四个用户:backup、mwlucas、pkdick 和 jgballard ,他们分组如下:
xxxxxxxxxxwheel: mwlucasstaff: mwlucas, pkdick, jgballardsupport: pkdick, mwlucasbilling: jgballard虽然这些是小团体,但这些原则适用于任何规模的团体。
计费应用程序需要系统帐户,但用户不需要通过SSH访问。如果我只想阻止计费部门的用户通过SSH登录,我可以使用 DenyUsers :
xxxxxxxxxxDenyUsers jgballard所有未列出的用户仍然具有SSH访问权限。不过,当我从该部门添加另一个用户时,我必须明确地将他们添加到 DenyUsers 中。阻止群组访问会更好:
xxxxxxxxxxDenyGroups billing通过这一条声明,我可以将用户添加到 billing 组,他们自动无法在我珍贵的虚拟终端上获取他们的资金。
AllowGroups 语句的存在意味着只有该组的成员才能登录。在BSD系统上,wheel 是系统管理员的组。Ubuntu对管理员组做了一些类似的事情,但我是BSD的人(BSD guy),所以你可以得到我的偏好。要只允许系统管理员通过SSH登录,请使用 AllowGroups 。
xxxxxxxxxxAllowGroups wheelwheel 组中的任何人都可以登录。虽然我没有明确禁止其他人登录,但用户 backup、pkdick 和 jgballard 不在 wheel 组,所以他们不能登录。
我是 wheel 组的唯一成员,我可以明确地列出自己:
xxxxxxxxxxAllowUsers mwlucas但我希望最终能得到帮助。当那一天到来时,我必须为我的新系统管理员创建一个帐户,并将它们添加到我所有计算机上的 AllowUsers 语句中。我会忘记其中之一。尽可能使用组。
支持团队可以访问其他主机。我有一个特定的系统,禁止某个人登录。在这里,我阻止了该用户,但允许该组登录。
xxxxxxxxxxDenyUsers pkdickAllowGroups support这表明“第一场比赛获胜”(first match wins)。用户 pkdick 立即被拒绝,该决定是最终决定。其他用户可以继续执行 AllowGroups 语句。你可以在Raspberry Pi的内置 pi 帐户上使用此设置。
一些应用程序,如配置正确的 rsync ,需要具有SSH访问权限的帐户。这需要一个具有公钥身份验证的用户帐户(【第七章:SSH密钥】)。这些帐户可能很危险。虽然你可以限制用户在使用密钥进行身份验证时可以运行的帐户,但你不希望来自随机主机的 rsync 连接,也不希望具有shell访问权限的用户能够通过编辑他拥有的文件来绕过限制。你可以使用这些 Allow和Deny 选项,通过在用户后面添加 @ 和IP地址来限制用户的来源:
xxxxxxxxxxAllowUsers backup@192.0.2.0/24AllowGroups supportsupport 组中的用户可以从任何地方登录,用户 backup 可以从IP在 192.0.2.0 和 192.0.2.255 之间的任何主机登录。所有其他用户都被拒绝。
通过合理的组成员资格和周到的允许和拒绝选项,您几乎可以以任何需要的方式限制登录访问。如有疑问,请给予帐户最低级别的权限,让用户和程序完成所需的任务。
有时,你似乎必须允许用户、系统管理员或应用程序以 root 身份通过SSH登录系统。在几乎所有的环境中,这都是一个非常糟糕的主意。当用户必须以普通用户身份登录,然后更改为 root 时,系统会记录用户的帐户,提供责任和归属。以 root 身份登录会破坏审计跟踪。许多服务器程序最初由 root 启动,使用户帐户友好的环境更改可能会传播到这些程序的环境中,从而中断服务。
如果用户需要root级别的访问权限,则总是有 su(1) 。或者 sudo ,或者 pfexec ,或者任何数量的权限管理工具。基于SSH的编排系统,如Ansible,支持所有这些程序。特别是sudo可以配置为通过SSH代理进行身份验证,这样用户的凭据就永远不会暴露给服务器。
某些环境,特别是大型的基于云的服务器场,被设计为不仅可以以 root 身份登录,而且更可取。这些环境需要公钥身份验证,并记录用于验证每个会话的密钥。本书将在【第14章 证书颁发机构】中介绍设置。
OpenSSH使用 PermitRootLogin 关键字控制以root身份的直接登录。默认情况下,如果使用公钥身份验证,sshd 允许直接root登录。
xxxxxxxxxxPermitRootLogin prohibit-passwordprohibit-password(禁止密码)选项与较旧的选项相同,但 without-password 选项令人困惑。用户可以以root身份登录,只要他们不使用密码。一旦进入公钥身份验证,就没有任何东西禁止用户将他们的密钥添加到允许使用root帐户的密钥列表中。我们建议不要使用 prohibit-password 。
将 PermitRootLogin 设置为 no 不允许root用户直接登录。大多数操作系统默认设置此选项。
如果必须允许远程root登录,请考虑将 PermitRootLogin 设置为 forced-commands-only 。第十二章讨论了 ForceCommand 选项,允许你将必须以root身份运行的自动化任务限制为仅执行某些命令。
通过SSH以root身份登录几乎总是意味着你解决了错误的问题。退一步,寻找其他方法来实现你的真正目标。
sshd_config 中的某些关键字可以使用表示某些变量的标记和符号。令牌(token)使这些关键字更加灵活。当我们讨论可以使用令牌的关键字时,我们将讨论令牌的使用,但从一开始,你就需要一眼能认出它们。在下一节中,我们将在构建chroot时使用令牌,然后贯穿本书。
所有令牌都以百分号(%)开头。最简单的标记是 %% ,它代表一个实际的百分号。如果你的文件路径中有 % ,可以使用这种方式。
%u 代表用户名%h 代表用户的主目录大多数其他令牌仅在非常特殊的情况下使用,即,使用不太常见的函数。我们会根据需要触及它们,但这些是每个人都必须知道的。sshd_config(5) 手册页列出了所有令牌可供参考。
有时用户需要访问命令提示符或特定程序,但你不希望用户访问其主目录之外的任何内容。用户无法逃脱的目录称为 chroot 。(chroot对SFTP也很有用,如第6章所述,但这需要更少的配置)OpenSSH支持使用 ChrootDirectory 选项对用户进行chroot操作。
xxxxxxxxxxChrootDirectory none默认情况下,sshd 不chroot用户。
populating——填充
chroot用户无法访问chroot之外的任何内容。你创建的任何chroot都不会有设备节点、shell或其他程序,除非你将它们放置在那里。当你的受限用户登录时,sshd 将无法找到shell或主目录,并立即断开它们的连接。要授予chroot用户shell访问权限,你必须至少在chroot目录上设置权限,为被监禁的用户创建主目录,创建设备节点,并安装shell。
只有当用户需要shell访问权限时,才需要填充chroot。如果用户仅通过SFTP获得文件副本访问权限,则第六章中讨论的 ForceCommand 关键字比填充的chroot更可取。
chroot目录必须由root拥有,并且不可由受限用户写入,就像你不允许非特权用户写入主机的根目录一样。如果受限用户可以写入chroot目录,sshd将不允许他们登录。
用户的主目录(如 /etc/passwd 中所示)预计在chroot中可用。如果用户 pkdick 的主目录列为 /home/pkdick ,并且他被chroot到 /usr/prisonroot ,则必须创建目录 /usr/prisonroot/home/pkdick 。这个目录应该由用户拥有,就像普通的主目录一样,并且应该包含任何必要的点文件【疑似 .shrc 之类的配置文件?】。
在chroot中创建一个设备节点目录。如果chroot目录为 /usr/prisonroot ,则需要 /usr/prisonroot/dev 。现在,你需要用设备节点填充它。chroot不需要完整的设备节点,但大多数chroot应用程序至少需要 /dev/random 、 /dev/stdin、 /dev/stdout、 /dev/stderr、 /dev/tty 和 /dev/zeor 。创建设备节点的方法因操作系统而异。OpenBSD和许多Linux使用shell脚本 /dev/MAKEDEV ,而FreeBSD和许多商业类Unix系统使用设备文件系统。检查你的操作系统,了解chroot需要哪些设备节点以及如何创建它们。一些操作系统包含易于填充chroot的工具。
最后,用户需要一个shell。将静态链接的shell复制到chroot的 /bin 目录中。还可以复制用户需要的任何其他程序的静态版本。如果你想使用动态链接程序,你还必须复制任何必要的文件。
使用 ChrootDirectory 选项建立chroots:
xxxxxxxxxxChrootDirectory /home/djm这适用于单个用户帐户,或者如果所有SSH用户都有相同的chroot目录,但这是令牌发挥作用的地方。
如果chroot目录路径包含文字百分号,请使用 %% 标记。此例中,我们chroot进入目录 /home/disk%1/djm :
xxxxxxxxxxChrootDirectory /home/disk%%1/djm%h 宏展开到用户的主目录,就像 /etc/passwd 中所指定的一样。
xxxxxxxxxxChrootDirectory %h登录时,djm 被锁定到 /home/djm 。请注意,他需要在这个目录中有一个chrooted主目录,所以你需要创建 /home/djm/home/djm 。
%u 宏展开为用户的用户名。这允许你在中央chroot目录下为一组用户分配唯一的主目录。
xxxxxxxxxxChrootDirectory /usr/prisonroot/%u您需要分别填充每个用户的chroot。
你可以chroot所有人,但这会使你的系统管理员难以进行维护。很可能你只想chroot一部分用户。使用 Match 语句有选择地chroot用户:
xxxxxxxxxx…ChrootDirectory none…Match Group billingChrootDirectory %h如果你的大多数用户都被chroot,请反转(reverse)默认设置,只允许你的系统管理员完全访问:
xxxxxxxxxx…ChrootDirectory %h…Match Group wheelChrootDirectory none选择对您的环境有意义的方法。
chroot很难管理,因为它们通常缺乏完整的用户空间。如果chroot的用户无法登录,请在调试模式下运行 sshd,连接到终端窗口。让chroot的用户尝试登录,并查看调试输出;你可能会看到问题。常见的问题包括缺少设备节点、目录权限不正确或缺少shell。
任何面向互联网的服务器都会受到很多随机的探测。蠕虫、脚本小子和其他各种渣滓都想闯入你的电脑。如果没有别的,有人想在上面运行IRC机器人。你如何保护你的SSH服务?
有些人建议更改 sshd 使用的TCP端口。这是一个通过隐蔽性实现安全的完美例子,但隐蔽性是行不通的。扫描仪不断探测所有互联网连接的IP地址的所有端口,它们非常擅长找出哪个端口上运行的是什么服务。更改端口可能会为你赢得几分钟的时间来对抗专用入侵者,但现在不是了。更改端口可以减少日志中的随机噪声量,增加你注意到实际问题的几率。
你也会在互联网上看到有人建议使用不同的协议横幅,这是个糟糕的建议。当你使用 netcat 连接到SSH守护进程时,你将看到协议横幅。横幅标识服务器的类型。所有SSH服务器都略有不同,可能需要特殊的客户端设置。SSH客户端使用协议横幅来检测与服务器可靠连接所需要的任何怪癖。如果将协议横幅从SSH-2.0-OpenSSH_7.0更改为SSH-2.0-ParanoidWhackJob,会剥夺客户端可靠连接所需的信息。
你可以考虑附加解决方案来阻止重复连接但无法进行身份验证的IP地址,例如 fail2ban 和黑名单。实现这些的细节因平台而异,但它们值得考虑。
在某种程度上,sshd(8) 通过特权分离(privilege separation)来保护自己。只有一小部分服务以root权限运行。大多数服务器以无特权用户身份运行。这意味着,如果入侵者成功入侵服务器守护进程,他只能对你的系统造成有限的损害。这仍然很烦人,但不是毁灭性的。
此外,sshd(8) 通过沙箱(sandbox)限制了无特权进程。沙箱限制了 sshd 在用户进行身份验证之前可以调用哪些系统调用。OpenSSH支持几种不同的沙箱方法,从苹果的 sandbox(7) 到Linux的 seccomp(2) 。如果操作系统不提供任何其他沙箱方法,sshd 会使用 rlimit 将打开的文件和子进程的设置为零。
与所有面向互联网的服务一样,降低SSH服务风险的一个简单方法是减少可以访问它的IP地址的数量。OpenSSH尊重TCP包装器(/etc/hosts.allow)。如果你的服务器或网络有数据包过滤器,请使用它。通过只允许授权的IP地址访问你的SSH服务器,你可以阻止绝大多数攻击者。
然而,保护服务器的最有效方法是禁用密码,只允许通过密钥登录。我们在【第7章 SSH密钥】中介绍了通过密钥的访问。
当我们介绍特定功能时,我们将回到配置 sshd ,但现在让我们谈谈服务器密钥。