保护 SSH 的三把锁
简介
如果需要远程访问计算机并启用了 Secure Shell (SSH) 连接,黑客就会尝试突破您的防线并控制您的计算机,您必须接受这个事实。尽管不能保证计算机不会被 “黑客” 占领,但是一些简单的解决方案有助于保护 SSH,可以让攻击困难一些。本文讨论三种技术:
把 SSH 的标准端口改为不常用的值并增强 SSH 配置,从而挡住最简单的攻击。
定义有限的用户列表,只允许这些用户登录。
完全隐藏允许 SSH 访问的事实,要求根据特殊的 “敲门” 序列识别有效用户。
要想应用这些技术,需要能够访问根账户。另外,可能必须安装一些包,需要配置防火墙和路由器(如果有路由器的话),打开和关闭特定的端口并把数据包转发到您的计算机。
加强保护
“隐匿产生安全” 这个概念尽人皆知而且受到大家的嘲笑,因为采用隐匿的方式,希望没人了解您的方法,这只是一厢情愿的想法。但是,在某些场景中,隐匿一点儿会有帮助。尽管简单的措施无法阻止下定决心的黑客,但是至少能够挡住那些 “脚本小子”,他们的脚本往往水平很一般。
人人都知道 SSH 连接的标准端口是 22。因此,为了让计算机更安全,应该采取的第一个措施是把端口改为另一个不常用的非标准端口号,比如 22960。1024 以上的号码通常都可以使用,但是应该查阅参考资料以避免导致问题。这一修改对您的影响仅仅是必须使用下面的命令连接计算机:
ssh -p 22960 your.machine.url
为了实现这个小措施,只需在 /etc/ssh/sshd_config 文件中做简单的修改。编辑此文件(必须作为根用户),寻找 Port 22 行,把端口号改为您选择的号码(如果这一行以镑符 [#] 开头,表示它被注释掉了,那么应该取消注释标志)。保存文件,用 /etc/init.d/sshd restart 命令重新启动 SSH。应该在防火墙上打开您选择的端口并关闭端口 22。
但是,还可以更进一步。编辑配置文件,在其中包含 清单 1 所示的行。注意,其中一些行可能已经存在,但是可以把它们注释掉。
清单 1. 通过修改 SSH 配置文件简便地增强安全性
Port 22960
LoginGraceTime 30
MaxAuthTries 3
Protocol 2
PermitRootLogin no
LoginGraceTime 允许一次登录花费 30 秒;如果用户花费的时间超过 30 秒,就不允许他访问,必须重新登录。MaxAuthTries 把错误尝试的次数限制为 3 次,3 次之后拒绝登录尝试。上面的 Protocol 2 行禁止使用比较弱的协议。最后一行不允许任何人作为根用户登录,这会让黑客攻击更困难。还可以使用 DenyUsers、AllowUsers、DenyGroups 和 AllowGroups 选项实现其他限制。这些修改不会显著增强计算机的安全性,但是只尝试强力攻击标准端口 22 的一般脚本会失败,不会造成损害。无论如何,这是向正确的方向迈出了第一步。在本文后面,我们将使用更安全的方法,不仅修改端口号,而且完全隐藏它。
谁可以进入?
对于大多数人,PAM 是一种罐装的烹调油。但是作为 Linux® 安全术语,PAM 代表可插入身份验证模块(Pluggable Authentication Modules)。这些模块提供额外的身份验证规则,保护对计算机的访问。
首先讨论一个基本问题:究竟为什么要使用 PAM?如果每个程序不得不定义自己的身份验证逻辑,就会很混乱。如何确定所有应用程序都实现了相同的测试和检查?如果需要额外的控制手段,那么怎么办?难道要重新编写所有程序吗?在计算机科学领域,有时候可以用额外的一层解决所有问题,至少在安全方面是这样。如果一个程序需要验证用户的身份,它可以调用 PAM API。这个 API 负责执行在 PAM 配置文件中指定的所有检查。这种方法还允许方便地修改身份验证规则,所有感知 PAM 的程序都会自动地应用新规则,不需要修改它们的代码。如果希望使用某种生物学检查(比如虹膜扫描器或指纹采集器),而且生产商提供了 PAM,就可以方便地设置它。在配置文件中包含模块调用,所有应用程序就可以使用这个设备了。
配置 PAM
PAM 提供四个安全领域的特性,但是应用程序不太可能同时需要所有这些方面。例如,passwd 命令只需要下面列表中的第三组:
account 处理账户限制。对于有效的用户,允许他做什么?
auth 处理用户识别 — 例如,通过输入用户名和密码。
password 只处理与密码相关的问题,比如设置新密码。
session 处理连接管理,包括日志记录。
在 /etc/pam.d 目录中为将使用 PAM 的每个应用程序创建一个配置文件,文件名与应用程序名相同。例如,login 命令的配置文件是 /etc/pam.d/login。
必须定义将应用哪些模块,创建一个动作 “堆”。PAM 运行堆中的所有模块,根据它们的结果允许或拒绝用户的请求。还必须定义检查是否是必需的。最后,other 文件为没有特殊规则的所有应用程序提供默认规则。
optional 模块可以成功,也可以失败;PAM 根据模块是否最终成功返回 success 或 failure。
required 模块必须成功。如果失败,PAM 返回 failure,但是会在运行堆中的其他模块之后返回。
requisite 模块也必须成功。但是,如果失败,PAM 立即返回 failure,不再运行其他模块。
sufficient 模块在成功时导致 PAM 立即返回 success,不再运行其他模块。
配置文件的结构很简单。可以包含注释,注释以散列符 (#) 开头;通过在换行处加上反斜杠 (\),可以把长的行分为多行。行有三个字段:领域 (account、auth、password 或 session)、控制标志(optional、required、requisite 或 sufficient)、将运行的模块的路径和参数。注意,第二个字段可以更复杂;更多信息见 参考资料。另外,可以使用 include 规则以包含其他文件中的规则,比如 auth include common-account。
特殊的 /etc/pam.d/other 文件是 “默认的” 配置文件(见 清单 2),其中的规则自动地应用于没有自己的配置文件的所有应用程序。为了确保安全,应该快速检查 /etc/pam.d 目录,把您不使用的所有配置文件改为其他名称(这样就会使用 other 配置)。如果认为确实需要某个应用程序,那么只需把配置文件改回原来的名称。默认配置通常拒绝所有请求(通过使用 pam_deny.so 模块)并警告管理员(通过 pam_warn.so 模块),让管理员解决问题。
标准的 “other” 配置文件为没有自己的配置文件的所有应用程序提供安全的默认规则(拒绝所有请求)。
清单 2. 标准的 “other” 配置文件
account required pam_deny.so
auth required pam_deny.so
auth required pam_warn.so
password required pam_deny.so
password required pam_warn.so
session required pam_deny.so
如果把 pam_deny.so 替换为 pam_unix.so,就应用标准的身份验证方法(输入用户名和密码)。如果您不关心安全性,那么使用 pam_permit.so,这会允许任何请求!
一些可用方法
尽管没有标准的模块列表,但是所有发行版都包含以下模块中的大多数。请检查驻留模块的 /lib/security 或 /usr/lib/security 目录。对于 64 位操作系统,用 lib64 替换 lib。如果需要更多信息,可以尝试执行 man the.name.of.the.module,而不要直接执行它;PAM 不是可执行的二进制代码。
pam_access 根据 /etc/security/access.conf 文件允许或拒绝访问。稍后将使用此模块决定允许哪些用户登录。
pam_cracklib 和 pam_pwcheck 检查新密码的强度。
pam_deny 和 pam_permit 是基本模块,分别拒绝或允许访问。
pam_echo 向用户显示指定文件的内容。
pam_lastlog 向用户显示他上一次登录的日期和时间。
pam_ldap.so 让用户根据 LDAP 服务器进行身份验证,提供跨网络的集中式身份验证。
pam_limits 模块允许指定系统资源限制,限制在 /etc/security/limits.conf 文件中定义。
pam_listfile 提供根据一个文件的内容允许或拒绝服务的另一种方法。
pam_mail 检查用户是否有未处理的邮件。
pam_motd 向用户显示 “message of the day” 文件。
如果 /etc/nologin 文件存在,pam_nologin 阻止所有登录。
pam_rootok 允许根用户访问,不执行进一步检查。/etc/pam.d/su 中常常包含这个模块;必需的行是 auth sufficient pam_rootok.so。根用户可以作为任何用户操作,不需要提供密码。
pam_succeed_if 检查账户的特定属性,比如是否是某个组的成员。
pam_time 可以根据 /etc/security/time.conf 中的规则限制对服务的访问。
pam_unix(或 pam_unix2)提供基于 /etc/passwd 和 /etc/shadow 文件的传统 UNIX® 身份验证。
pam_userdb 根据一个 Berkeley 数据库执行身份验证。
pam_warn 在系统日志中记录信息。
pam_wheel 只向 wheel 组的成员提供根访问权;必需的行是 auth required pam_wheel.so。
关于其他模块和编写自己的模块的信息,请查阅 参考资料。现在,使用 PAM 决定谁可以登录您的计算机。
用 PAM 限制访问
现在,我们来使用 PAM 限制谁可以连接您的服务器。必须编辑 /etc/pam.d/sshd 文件,让它像清单 3 这样。
清单 3. 在 sshd PAM 文件中添加 pam_access.so
#%PAM-1.0
account include common-account
account required pam_access.so
auth include common-auth
auth required pam_nologin.so
password include common-password
session include common-session
在 sshd PAM 文件中添加 pam_access.so,就可以轻松地定义谁可以使用 SSH 连接您的计算机。pam_access.so 模块实现基于 /etc/security/access.conf 文件的安全控制,见清单 4。
清单 4. 通过使用 pam_access.so,定义谁可以或不可以使用 SSH
+ : ALL : 192.168.1.
+ : jack : ALL
+ : jill : ALL
- : ALL : ALL
第一行允许任何用户 (ALL) 从内部网络登录。后两行允许用户 jack 和 jill 从任何地方访问服务器。最后一行拒绝其他任何用户从其他任何地方访问。允许多个用户访问的另一种方法是使用 pam_listfile.so,这需要创建一个允许访问的用户列表(例如 /etc/ssh_users)。在 /etc/pam.d/sshd 文件中添加以下行:
auth required pam_listfile.so item=user sense=allow
file=/etc/ssh_users onerr=fail
这还没有完。必须修改 /etc/ssh/sshd_config 文件,让它使用 PAM。在此文件中添加 UsePAM yes 行,重新启动 sshd 守护进程,这样就行了!
究竟是否有门?
即使应用了前两节中的方法,无论您怎么预防,黑客仍然会尝试穿越您系统中任何开放的门户。改变 SSH 端口号对于经验丰富的黑客只能造成小小的麻烦。限制允许访问的用户会有帮助,但前提是没有用户落入黑客或社会工程攻击的圈套而泄露密码。无论如何,只要您的系统中有门,就会吸引黑客。
增强计算机安全性的最后一种方案是最激进的:关闭打开的端口,这会让任何攻击都无法攻破您的计算机。只向能够提供 “秘密敲门暗号” 的用户开放所需的端口,让用户能够输入密码并访问计算机。
这种技术称为端口敲门,适用于需要访问不向公众开放的服务器的用户。服务器可以关闭所有端口,直到用户提供一个秘密的敲门序列 (序列很容易实现,而且需要的资源不多)。
打开秘密端口之后,应用常用的安全机制(比如密码或证书)。只需在防火墙级上提供一个额外的安全层,需要秘密端口的所有服务就会正常工作。
这种方法的要点在于关闭所有端口并监视外部连接尝试。当识别出预定义的尝试序列(称为敲门序列 )时,可以执行打开端口等操作,让外部的用户能够进来。敲门序列的复杂程度由您决定,从简单的列表(比如依次尝试 TCP 端口 7000、UDP 端口 7100 和 TCP 端口 7200)到一次性序列集合都可以。(按密码学术语来说,一次性序列与 “一次一密” 相似,这是已知最安全的加密方法。)外面的用户必须知道使用 SSH 所需的端口号和密码,还必须知道打开端口并启用密码所需的敲门序列。如果没有这个序列,连接尝试就会静悄悄地失败。
这为什么是非常安全的方案?因为有 65,535 个端口(见 参考资料)。即使考虑到已经分配的端口,仍然有超过 60,000 个可用端口。如果敲门序列只包含四次 “敲门”,黑客要想通过强力攻击猜出序列,就必须测试大约 13,000,000,000,000,000,000 个序列(13 后面 18 个零)。这样的攻击显然不太可能奏效!当然,强力攻击或胡乱猜测并不是猜出正确序列的惟一方法。因此,不要只使用单一安全方法;而是使用一系列安全层来增加攻击的难度。
必须安装敲门守护进程 knockd;它监视敲门序列,当发现有效的序列时执行相应的操作。如果愿意,可以从头构建它,但是大多数(如果不是所有的话)发行版中都有这个包。最好使用包管理工具安装它。例如,在 OpenSUSE 中,可以使用
Yast2 或通过执行 sudo zypper install knockd 安装它。在 Ubuntu 中可以使用 sudo apt-get install knockd,在 Debian 中使用 sudo aptitude install knockd。用发行版的软件安装工具搜索 knockd 往往能够找到它。
安装这个包之后,必须编辑 /etc/knockd.conf 文件以指定端口敲门规则,然后启动守护进程。为了完成所需的设置,必须了解您的防火墙的工作方式。例如,在 OpenSUSE 中,可以使用 清单 5 这样的设置。
清单 5. 针对 OpenSUSE 防火墙设计的示例配置文件
[opencloseSSH]
sequence= 7000,8000,9000
tcpflags= syn
seq_timeout= 15
cmd_timeout= 30
start_command= /usr/sbin/iptables -s %IP% -I input_ext 1 -p tcp --dport 22960 -j ACCEPT
stop_command= /usr/sbin/iptables -s %IP% -D input_ext -p tcp --dport 22960 -j ACCEPT
这个示例在用户依次在端口 7000、8000 和 9000 上敲门之后启用 SSH 访问。在启动 knockd 之前,关闭端口 22960 并尝试远程登录。这个尝试应该会失败,见清单 6。
清单 6. 如果禁用 SSH 访问而且不启动敲门守护进程,登录尝试会失败
> ssh the.url.for.your.site -p 22960 -o ConnectTimeout=15
ssh: connect to host the.url.for.your.site port 22960: Connection timed out
现在,使用 sudo /etc/init.d/knockd start 或 sudo knockd -d 启动端口敲门守护进程(这两个命令是等效的),然后再试一下;端口敲门序列要求在端口 7000、8000 和 9000 上敲门。必须在 15 秒内完成这个序列。识别出序列之后端口打开,必须在 30 秒内登录。否则,端口再次关闭。
为了检验这个过程,回到您的远程机器上并登录。这一次提供所需的敲门序列,见 清单 7。注意,在安装 knockd 时通常也会安装 knock 命令。如果不是这样,只需用发行版的包管理工具搜索它。
清单 7. 提供所需的敲门序列之后登录成功
> knock the.url.for.your.site 7000
> knock the.url.for.your.site 8000
> knock the.url.for.your.site 9000
> ssh the.url.for.your.site -p 22960 -o ConnectTimeout=10
Password:
如果提供了错误的敲门序列(或根本没有敲门),会收到 “Connection timed out” 消息,SSH 端口仍然完全关闭,看不出它是存在的。
敲门配置
/etc/knockd.conf 文件有一个一般选项小节 options,希望使用的每个敲门序列各有一个小节。选项可以是大写、小写或大小写混合形式。
在默认情况下,knockd 监视 eth0 接口。要想使用另一个接口(例如 eth1),可以包含 Interface=eth1 行。注意,只使用设备名而不是设备的完整路径。
如果希望启用日志记录,可以通过包含 useSyslog 行使用标准的 Linux 日志文件,也可以通过包含 LogFile=/the/full/path/to/your/file 使用自己的文件。但是,应该认识到日志记录是一个漏洞;如果黑客获得了日志,入侵者就会掌握端口敲门序列。
如果希望能够检查 knockd 是否仍然在运行,那么包含 PidFile=/the/full/path/to/a/PID/file。这个守护进程的进程 ID (PID) 将存储在这个文件中。应该通过一个 cron 任务定期检查 knockd 是否仍然在运行并在需要时重新启动它。注意,当这个守护进程停止运行时,系统是安全的;所有端口关闭,不可访问。在守护进程重新启动之前,用户无法登录。
可以让 knockd 监听多个序列并以不同方式响应各个序列。在前面的示例中,让 knockd 打开 SSH 端口;可以简单地启用 HTTP 端口,让用户能够访问 web 服务器,也可以运行特定的进程。在配置文件中,每个序列都有相应的小节。
使用 sequence 定义敲门序列,比如 7000,8000,9000。在默认情况下,敲门使用 TCP,但是可以添加 UDP 以增加复杂性,比如 7000,8000:udp,9000。
除了使用固定的序列之外,还可以指定一个包含 “一次性序列” 的文件,这些序列在使用之后就会删除,不能再次使用。指定这种序列的方法如下:
one_time_sequences=/the/full/path/to/a/sequences/file
使用任何文本编辑器创建此文件;其中每行包含一个序列(按照上面所示的格式)。应该在远程计算机上保存此文件的拷贝以便记住如何登录。
可以指定应该扫描哪些到达的 TCP 数据包,丢弃不与 ACK、FIN、PSH、RST、SYN 或 URG 标志匹配的数据包。对于 SSH 连接,应该使用 TCPFlags=SYN。
可以用 Seq_Timeout=seconds.to.wait 指定完成一个序列的最大时间。如果在此时间内没有输入完整的序列,就不会识别出它,访问被拒绝。
可以用 Cmd_Timeout=seconds.to.wait 指定在识别出序列之后用户执行第二个命令的最大时间。如果提供了敲门序列的用户没有快速地输入下一个命令(例如登录),端口会再次关闭。
最重要的参数是 Start_command=some.command.to.execute,它指定成功地识别出敲门序列之后要执行的命令或脚本。如果需要引用敲门者的 IP 地址(例如为了允许从他的计算机连接您的计算机),可以使用 %IP%。在运行时,它会替换为正确的值。在上面的示例中指定:
/usr/sbin/iptables -s %IP% -I input_ext 1 -p tcp --dport 22960 -j ACCEPT
iptables 向提供敲门序列的 IP 地址上的用户开放端口 22960。
另一个重要的参数是 Stop_command=some.command.to.execute;当超过 Cmd_timeout 时间之后,执行它指定的命令或脚本。
在这里,因为只希望打开或关闭端口 22960,所以使用单一命令就够了。如果需要更复杂的操作,可以通过调用脚本执行所需的任何操作 — 操作甚至可以完全不涉及打开端口。可以触发任何操作,比如运行进程或执行备份。当然,了解要使用的命令可能有点儿难度。例如,因为我运行 OpenSUSE,它提供自己的防火墙前端,所以我不得不通过查看 iptables -l 的输出了解应该执行哪个命令来打开或关闭端口 22960。
对于 knockd 本身,有几个选项需要考虑:
-c:允许指定默认配置文件 /etc/knockd.conf 之外的另一个配置文件
-d:让 knockd 作为后台守护进程运行,这是标准运行方式
-D:提供输出调试消息
-h:提供关于语法和选项的帮助
-i:允许指定默认的 eth0 接口之外的其他接口
-l:为日志项启用 DNS 查找 — 这是一种不好的做法,因为这强迫计算机使用 DNS 通信流,会产生漏洞
-v:提供更详细的消息和解释
-V:提供程序的版本号
最后,可以使用多种方法产生敲门序列本身,编写 knock 命令是最简单的方法。
以下命令在 TCP 端口 7000 上敲门:
knock the.url.for.your.site 7000
以下命令在 UDP 端口 8000 上敲门:
knock the.url.for.your.site -u 8000
或
knock the.url.for.your.site 8000:udp
-h 参数提供这个命令的帮助。