11. 第10章:网络应用与服务

隧道(Tunneling)

隧道是将一个网络连接封装到另一个网络连接中进行传输的过程。使用 SSH 隧道 X Window System 连接的优势在于:SSH 会自动为你设置显示环境,并在隧道内加密 X 数据。

SSH 也有一些缺点。例如,建立 SSH 连接时,你需要远程主机的公钥,而获取公钥的方式不一定安全(尽管你可以手动检查以确认没有被欺骗)。如需了解多种加密方法的概述,请参阅 Jean-Philippe Aumasson 所著的《严肃密码学:现代加密实用入门》(No Starch Press, 2017)。两本深入的 SSH 书籍是 Michael W. Lucas 的《SSH Mastery: OpenSSH, PuTTY, Tunnels, and Keys》第2版(Tilted Windmill Press, 2018)以及 Daniel J. Barrett、Richard E. Silverman 和 Robert G. Byrnes 的《SSH, The Secure Shell: The Definitive Guide》第2版(O’Reilly, 2005)。

公钥密码学(PUBLIC-KEY CRYPTOGRAPHY)

我们一直使用“公钥”这个术语而未提供太多背景,因此让我们后退一步,简单讨论一下,以防你对此不熟悉。直到20世纪70年代,加密算法都是对称的,要求消息的发送者和接收者拥有相同的密钥。破解代码就是窃取密钥的问题;拥有密钥的人越多,密钥被泄露的机会就越多。但有了公钥密码学,就有了两个密钥:公钥和私钥。公钥可以加密消息,但不能解密;因此,谁拥有这个密钥并不重要。只有私钥才能解密来自公钥的消息。在大多数情况下,保护私钥更容易,因为只需要一份副本,并且不需要传输。

超越加密的另一个应用是身份验证;存在一些方法可以验证某人持有某个公钥对应的私钥,而无需传输任何密钥。

10.3.2 sshd 服务器

运行 sshd 服务器以允许远程连接到你的系统需要配置文件和主机密钥。大多数发行版将配置文件保存在 /etc/ssh 配置目录中,如果你安装它们的 sshd 软件包,它们会尝试为你正确配置所有内容。(服务器配置文件名为 sshd_config,很容易与客户端的 ssh_config 设置文件混淆,因此要小心。)

你通常不需要更改 sshd_config 中的任何内容,但检查一下也无妨。该文件由键值对组成,如下片段所示。

Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key

# 开头的行是注释,许多注释表示各种参数的默认值,正如你从这个片段中看到的。sshd_config(5) 手册页包含参数及其可能值的描述,但以下是最重要的几个:

  • HostKey file:使用 file 作为主机密钥。(主机密钥将在后面描述。)
  • PermitRootLogin value:如果 value 设置为 yes,则允许超级用户通过 SSH 登录。将 value 设置为 no 可禁止此操作。
  • LogLevel level:使用 syslog 级别 level 记录消息(默认为 INFO)。
  • SyslogFacility name:使用 syslog 设施 name 记录消息(默认为 AUTH)。
  • X11Forwarding value:如果 value 设置为 yes,则启用 X Window System 客户端隧道。
  • XAuthLocation path:指定系统上 xauth 工具的位置。如果没有此路径,X 隧道将无法工作。如果 xauth 不在 /usr/bin 中,请将 path 设置为 xauth 的完整路径名。

创建主机密钥

OpenSSH 有几个主机密钥集。每个集有一个公钥(带有 .pub 文件扩展名)和一个私钥(无扩展名)。

WARNING

不要让任何人看到私钥,即使在你自己的系统上也是如此,因为如果有人获得了它,你就会面临入侵者的风险。

SSH 版本 2 有 RSA 和 DSA 密钥。RSA 和 DSA 是公钥密码算法。密钥文件名见表 10-1。

表 10-1:OpenSSH 密钥文件

文件名密钥类型
ssh_host_rsa_key私钥 RSA
ssh_host_rsa_key.pub公钥 RSA
ssh_host_dsa_key私钥 DSA
ssh_host_dsa_key.pub公钥 DSA

创建密钥涉及一个数值计算,同时生成公钥和私钥。通常你不需要创建密钥,因为 OpenSSH 安装程序或你的发行版安装脚本会为你完成,但如果你计划使用像 ssh-agent 这样无需密码提供身份验证服务的程序,你需要知道如何操作。要创建 SSH 协议版本 2 的密钥,请使用 OpenSSH 附带的 ssh-keygen 程序:

# ssh-keygen -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key
# ssh-keygen -t dsa -N '' -f /etc/ssh/ssh_host_dsa_key

SSH 服务器和客户端还使用一个名为 ssh_known_hosts 的密钥文件来存储来自其他主机的公钥。如果你打算基于远程客户端的身份进行身份验证,服务器的 ssh_known_hosts 文件必须包含所有受信任客户端的公钥。了解密钥文件在更换机器时会很方便。全新安装新机器时,你可以从旧机器导入密钥文件,以确保用户在连接到新机器时不会遇到密钥不匹配的情况。

启动 SSH 服务器

尽管大多数发行版都附带 SSH,但它们通常默认不启动 sshd 服务器。在 Ubuntu 和 Debian 上,新系统未安装 SSH 服务器;安装其软件包会创建密钥、启动服务器,并将服务器启动添加到引导配置中。

在 Fedora 上,sshd 默认已安装但处于关闭状态。要在启动时启动 sshd,请按如下方式使用 systemctl

# systemctl enable sshd

如果你希望立即启动服务器而不重启,请使用:

# systemctl start sshd

Fedora 通常在首次启动 sshd 时创建任何缺失的主机密钥文件。

如果你运行的是其他发行版,可能不需要手动配置 sshd 的启动。但是,你应该知道有两种启动模式:独立(standalone)和按需(on-demand)。独立服务器要常见得多,只需以 root 身份运行 sshd 即可。sshd 服务器进程将其 PID 写入 /var/run/sshd.pid(当然,当由 systemd 运行时,它也会通过其 cgroup 进行跟踪,如第 6 章所述)。

作为替代方案,systemd 可以通过套接字单元按需启动 sshd。这通常不是一个好主意,因为服务器偶尔需要生成密钥文件,而这个过程可能会花费很长时间。

10.3.3 fail2ban

如果你在机器上设置了 SSH 服务器并将其开放到互联网,你会很快发现不断的入侵尝试。如果你的系统配置正确并且没有选择愚蠢的密码,这些暴力破解攻击不会成功。然而,它们会令人烦恼、消耗 CPU 时间,并毫无必要地弄乱你的日志。

为了防止这种情况,你希望设置一种机制来阻止重复的登录尝试。在撰写本文时,fail2ban 软件包是最流行的方法;它只是一个监视日志消息的脚本。当在某个时间范围内看到来自一个主机的特定数量的失败请求时,fail2ban 会使用 iptables 创建一条规则来拒绝来自该主机的流量。经过一个指定的时间(在此期间该主机可能已经放弃了连接尝试),fail2ban 会移除该规则。

大多数 Linux 发行版都提供带有为 SSH 预配置默认值的 fail2ban 软件包。

10.3.4 SSH 客户端

要登录到远程主机,请运行:

$ ssh remote_username@remote_host

如果你的本地用户名与 remote_host 上的用户名相同,则可以省略 remote_username@。你还可以像以下示例所示,将管道与 ssh 命令结合使用,该示例将目录 dir 复制到另一台主机:

$ tar zcvf - dir | ssh remote_host tar zxvf -

全局 SSH 客户端配置文件 ssh_config 应该位于 /etc/ssh,与你的 sshd_config 文件位置相同。与服务器配置文件一样,客户端配置文件也有键值对,但通常不需要更改它们。

使用 SSH 客户端最常见的问题发生在你本地的 ssh_known_hosts.ssh/known_hosts 文件中的 SSH 公钥与远程主机上的密钥不匹配时。错误的密钥会导致如下错误或警告:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
38:c2:f6:0d:0d:49:d4:05:55:68:54:2a:2f:83:06:11.
Please contact your system administrator.
Add correct host key in /home/user/.ssh/known_hosts to get rid of this 
message.
1 Offending key in /home/user/.ssh/known_hosts:12
RSA host key for host has changed and you have requested
strict checking.
Host key verification failed.

这通常只是意味着远程主机的管理员更改了密钥(这通常发生在硬件或云服务器升级时),但如果你不确定,与管理员核实一下也无妨。无论如何,上面的消息告诉你错误的密钥位于用户 known_hosts 文件的第 12 行 ¹。

如果你怀疑有恶意行为,请删除有问题的行或用正确的公钥替换它。

SSH 文件传输客户端

OpenSSH 包含文件传输程序 scpsftp,旨在替代旧的、不安全的程序 rcpftp。你可以使用 scp 将文件传输到远程机器或从远程机器传输到你的机器,也可以在一台主机和另一台主机之间传输。它的工作方式类似于 cp 命令。以下是一些示例。

将文件从远程主机复制到当前目录:

$ scp user@host:file .

将文件从本地机器复制到远程主机:

$ scp file user@host:dir

将文件从一台远程主机复制到第二台远程主机:

$ scp user1@host1:file user2@host2:dir

sftp 程序的工作方式类似于已过时的命令行 ftp 客户端,使用 getput 命令。远程主机必须安装 sftp-server 程序,如果远程主机也使用 OpenSSH,你可以预期这一点。

NOTE

如果你需要比 scpsftp 提供的更多功能和灵活性(例如,如果你经常传输大量文件),请查看第 12 章中描述的 rsync

非 Unix 平台的 SSH 客户端

所有流行的操作系统都有 SSH 客户端。你应该选择哪一个?PuTTY 是一个良好的基本 Windows 客户端,包含安全文件复制程序。macOS 基于 Unix 并包含 OpenSSH。

10.4 systemd 之前的网络连接服务器:inetd/xinetd

在 systemd 及其第 6.3.7 节介绍的套接字单元广泛使用之前,存在少数几种服务器,它们提供了构建网络服务的标准方法。许多次要的网络服务在连接需求上非常相似,因此为每种服务单独实现独立服务器可能效率低下。每个服务器必须单独配置以处理端口监听、访问控制和端口配置。网络应用与服务 279 页

大多数服务以相同方式执行这些操作;只有在服务器接受连接后,通信处理方式才有所不同。

简化服务器使用的一种传统方法是使用 inetd 守护进程——一种旨在标准化网络端口访问以及服务器程序与网络端口之间接口的超级服务器。启动 inetd 后,它会读取其配置文件,然后监听该文件中定义的网络端口。当新的网络连接进来时,inetd 会将一个新启动的进程附加到该连接上。

一个名为 xinetd 的更新版本提供了更简便的配置和更好的访问控制,但 xinetd 几乎已完全被 systemd 取代。不过,在旧系统或未使用 systemd 的系统上,你仍可能看到它。

TCP Wrappers: tcpd、/etc/hosts.allow 和 /etc/hosts.deny

在诸如 iptables 等底层防火墙普及之前,许多管理员使用 TCP Wrapper 库和守护进程来控制对网络服务的访问。在这种实现中,inetd 运行 tcpd 程序,该程序首先检查传入连接以及 /etc/hosts.allow/etc/hosts.deny 文件中的访问控制列表。tcpd 程序记录连接,如果它认为传入连接没问题,则将其交给最终的服务程序。你可能会遇到仍在使用 TCP Wrapper 系统的系统,但我们不会详细讨论它,因为它已基本不再使用。

10.5 诊断工具

让我们来看一些在应用层进行探测时很有用的诊断工具。有些工具会深入传输层和网络层,因为应用层的一切最终都会映射到那些较低层。

如第 9 章所述,netstat 是一个基本的网络服务调试工具,可以显示许多传输层和网络层统计信息。表 10-2 回顾了一些用于查看连接的有用选项。

表 10-2:netstat 有用的连接报告选项

选项描述
-t打印 TCP 端口信息
-u打印 UDP 端口信息
-l打印监听端口
-a打印所有活动端口
-n禁用名称查找(加快速度;如果 DNS 不可用也很有用)
-4, -6将输出限制为 IP 版本 4 或 6

280 页 第 10 章

10.5.1 lsof

在第 8 章中,你了解到 lsof 不仅能跟踪打开的文件,还能列出当前正在使用或监听端口的程序。要获得这些程序的完整列表,请运行:

# lsof -i

当你以普通用户身份运行此命令时,它只显示该用户的进程。当你以 root 身份运行时,输出应类似于如下内容,显示各种进程和用户:

COMMAND     PID     USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
rpcbind     700     root    6u  IPv4    10492      0t0  UDP *:sunrpc ①
rpcbind     700     root    8u  IPv4    10508      0t0  TCP *:sunrpc (LISTEN)
avahi-dae   872    avahi   13u  IPv4 21736375      0t0  UDP *:mdns ②
cupsd      1010     root    9u  IPv6 42321174      0t0  TCP ip6-localhost:ipp (LISTEN) ③
ssh       14366    juser    3u  IPv4 38995911      0t0  TCP thishost.local:55457-> ④
    somehost.example.com:ssh (ESTABLISHED)
chromium- 26534    juser    8r  IPv4 42525253      0t0  TCP thishost.local:41551-> ⑤
    anotherhost.example.com:https (ESTABLISHED)

此示例输出显示了服务器和客户端程序的用户和进程 ID:从顶部的传统 RPC 服务 ①,到 avahi 提供的多播 DNS 服务 ②,再到支持 IPv6 的打印机服务 cupsd ③。最后两项显示客户端连接:一个 SSH 连接 ④ 和 Chromium 浏览器的一个安全 Web 连接 ⑤。由于输出可能非常冗长,通常最好应用过滤器(如下一节所述)。

lsof 程序与 netstat 类似,它会尝试将找到的每个 IP 地址反向解析为主机名,这会减慢输出速度。使用 -n 选项可禁用名称解析:

# lsof -n -i

你也可以指定 -P 来禁用 /etc/services 端口名称查找。

按协议和端口过滤

如果你在寻找特定端口(例如,你知道某个进程正在使用特定端口,并且想知道该进程是什么),请使用以下命令:

# lsof -i:port

完整语法如下:

# lsof -iprotocol@host:port

网络应用与服务 281 页

protocol@host:port 参数都是可选的,它们将相应地过滤 lsof 输出。与大多数网络实用程序一样,主机和端口可以是名称或数字。例如,如果你只想查看 TCP 端口 443(HTTPS 端口)上的连接,请使用:

# lsof -iTCP:443

要根据 IP 版本进行过滤,请使用 -i4(IPv4)或 -i6(IPv6)。你可以将其作为单独选项添加,也可以将其与更复杂的过滤器一起添加(例如,-i6TCP:443)。

你可以使用 /etc/services 中的服务名称(如 -iTCP:ssh)代替数字。

按连接状态过滤

一个特别方便的 lsof 过滤器是按连接状态过滤。例如,要仅显示在 TCP 端口上监听的进程,请输入:

# lsof -iTCP -sTCP:LISTEN

此命令可让你很好地了解系统上当前正在运行的网络服务器进程。但是,由于 UDP 服务器不监听且没有连接,你必须使用 -iUDP 来查看运行的客户端和服务器。这通常不是问题,因为你的系统上可能不会有太多 UDP 服务器。

10.5.2 tcpdump

你的系统通常不会关注未发往其 MAC 地址之一的网络流量。如果需要确切地看到网络上传输的内容,tcpdump 会将你的网络接口卡置于混杂模式,并报告经过的每个数据包。不带参数输入 tcpdump 会产生如下输出,其中包括一个 ARP 请求和一个 Web 连接:

# tcpdump
tcpdump: listening on eth0
20:36:25.771304 arp who-has mikado.example.com tell duplex.example.com
20:36:25.774729 arp reply mikado.example.com is-at 0:2:2d:b:ee:4e
20:36:25.774796 duplex.example.com.48455 > mikado.example.com.www: S 
3200063165:3200063165(0) win 5840 <mss 1460,sackOK,timestamp 38815804[|tcp]> 
(DF)
20:36:25.779283 mikado.example.com.www > duplex.example.com.48455: S 
3494716463:3494716463(0) ack 3200063166 win 5792 <mss 1460,sackOK,timestamp 
4620[|tcp]> (DF)
20:36:25.779409 duplex.example.com.48455 > mikado.example.com.www: . ack 1 win 
5840 <nop,nop,timestamp 38815805 4620> (DF)
20:36:25.779787 duplex.example.com.48455 > mikado.example.com.www: P 
1:427(426) ack 1 win 5840 <nop,nop,timestamp 38815805 4620> (DF)
20:36:25.784012 mikado.example.com.www > duplex.example.com.48455: . ack 427 
282 10
win 6432 <nop,nop,timestamp 4620 38815805> (DF)
20:36:25.845645 mikado.example.com.www > duplex.example.com.48455: P 
1:773(772) ack 427 win 6432 <nop,nop,timestamp 4626 38815805> (DF)
20:36:25.845732 duplex.example.com.48455 > mikado.example.com.www: . ack 773 
win 6948 <nop,nop,timestamp 38815812 4626> (DF)
9 packets received by filter
0 packets dropped by kernel

你可以通过添加过滤器来告诉 tcpdump 更具体的信息。你可以根据源和目标主机、网络、以太网地址、网络模型中许多不同层的协议等进行过滤。tcpdump 识别的众多数据包协议包括 ARP、RARP、ICMP、TCP、UDP、IP、IPv6、AppleTalk 和 IPX 数据包。例如,要告诉 tcpdump 仅输出 TCP 数据包,请运行:

# tcpdump tcp

要查看 Web 数据包和 UDP 数据包,请输入:

# tcpdump udp or port 80 or port 443

关键字 or 指定左侧或右侧的条件可以为真以通过过滤器。类似地,and 关键字要求两个条件都为真。

NOTE

如果你需要进行大量数据包嗅探,请考虑使用 tcpdump 的 GUI 替代品,例如 Wireshark。

原语

在前面的示例中,tcpudpport 80 是称为原语的基本过滤器元素。最重要的原语列在表 10-3 中。

表 10-3:tcpdump 原语

原语数据包规范
tcpTCP 数据包
udpUDP 数据包
ipIPv4 数据包
ip6IPv6 数据包
port port发往/来自端口 port 的 TCP 和/或 UDP 数据包
host host发往或来自主机 host 的数据包
net network发往或来自网络 network 的数据包

网络应用与服务 283 页

运算符

前面使用的 or 是一个运算符。tcpdump 可以使用多个运算符(如 and!),并且你可以将运算符分组在括号中。如果你打算认真使用 tcpdump,请务必阅读 pcap-filter(7) 手册页,特别是描述原语的部分。

WARNING

使用 tcpdump 时要小心。本节前面显示的 tcpdump 输出仅包含数据包 TCP(传输层)和 IP(互联网层)头部信息,但你也可以让 tcpdump 打印完整的数据包内容。尽管现在大多数重要的网络流量都已通过 TLS 加密,但你不应窥探网络,除非你拥有该网络或已获得许可。

10.5.3 netcat

如果你需要比 telnet host port 这样的命令更灵活地连接到远程主机,请使用 netcat(或 nc)。netcat 可以连接到远程 TCP/UDP 端口、指定本地端口、监听端口、扫描端口、将标准 I/O 重定向到网络连接和从网络连接重定向标准 I/O,等等。要使用 netcat 打开到某个端口的 TCP 连接,请运行:

$ netcat host port

当对方结束连接时,netcat 会终止,这可能会令人困惑,如果你将标准输入重定向到 netcat,因为在发送数据后你可能无法取回提示符(与几乎任何其他命令管道相反)。你可以随时按 CTRL-C 结束连接。(如果你希望程序根据标准输入流终止网络连接,请尝试使用 sock 程序代替。)

要监听特定端口,请运行:

$ netcat -l port_number

如果 netcat 成功监听到端口,它将等待一个连接;建立连接后,它将打印来自该连接的输出,并将任何标准输入发送到该连接。

以下是有关 netcat 的其他说明:

  • 默认情况下,调试输出很少。如果出现问题,netcat 会静默失败,但它会设置适当的退出代码。如果你需要更多信息,请添加 -v(“verbose”,详细)选项。
  • 默认情况下,netcat 客户端尝试使用 IPv4 和 IPv6 连接。但在服务器模式下,netcat 默认使用 IPv4。要强制使用协议,请对 IPv4 使用 -4,对 IPv6 使用 -6
  • -u 选项指定 UDP 而非 TCP。

284 页 第 10 章

10.5.4 端口扫描

有时你甚至不知道网络上机器提供了哪些服务,也不知道哪些 IP 地址在使用中。网络映射器(Nmap)程序扫描一台机器或一组机器上的所有端口,查找开放端口,并列出它找到的端口。大多数发行版都有一个 Nmap 软件包,或者你可以从 http://www.insecure.org/ 获取。(有关 Nmap 的所有功能,请参阅 Nmap 手册页和在线资源。)

当列出你自己机器上的端口时,通常有助于至少从两个点运行 Nmap 扫描:从你自己的机器和从另一台机器(可能在你本地网络之外)。这样做将让你了解防火墙阻止了什么。

⚠ 警告

WARNING

如果他人控制了你想要用 Nmap 扫描的网络,请事先征得许可。网络管理员会监控端口扫描,并且通常会禁用运行扫描的机器的访问权限。

运行 nmap host 对主机进行通用扫描。例如:

$ nmap 10.1.2.2
Starting Nmap 5.21 ( http://nmap.org ) at 2015-09-21 16:51 PST
Nmap scan report for 10.1.2.2
Host is up (0.00027s latency).
Not shown: 993 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
25/tcp   open  smtp
80/tcp   open  http
111/tcp  open  rpcbind
8800/tcp open  unknown
9000/tcp open  cslistener
9090/tcp open  zeus-admin
Nmap done: 1 IP address (1 host up) scanned in 0.12 seconds

如上所示,有许多服务处于开放状态,其中大部分在多数发行版中默认并未启用。实际上,这里唯一通常默认开启的是端口 111,即 rpcbind 端口。

Nmap 也支持通过 IPv6 扫描端口(添加 -6 选项)。这是识别不支持 IPv6 的服务的一个便捷方法。

10.6 远程过程调用 (Remote Procedure Calls)

上一节扫描结果中的 rpcbind 服务是怎么回事?RPC 代表远程过程调用(Remote Procedure Call),是一个位于应用层底部的系统。它旨在帮助程序员更容易地构建客户端/服务器网络应用程序,即客户端程序调用在远程服务器上执行的函数。每种类型的远程服务器程序由分配的程序编号标识。

RPC 实现使用 TCP 和 UDP 等传输协议,并且需要一个特殊的中间服务来将程序编号映射到 TCP 和 UDP 端口。这个服务器叫做 rpcbind,任何想要使用 RPC 服务的机器上都必须运行它。

要查看计算机上有哪些 RPC 服务,运行:

$ rpcinfo -p localhost

RPC 是一种似乎永远不会消亡的协议。网络文件系统(NFS)和网络信息服务(NIS)都在使用 RPC,但在独立机器上它们完全不是必需的。不过,每当你认为已经消除了所有对 rpcbind 的需求时,总会有其他东西冒出来,比如 GNOME 中的文件访问监视器(FAM)支持。

10.7 网络安全 (Network Security)

由于 Linux 在 PC 平台上是一种非常流行的 Unix 变体,尤其因为它广泛用于 Web 服务器,因此它吸引了许多试图侵入计算机系统的不速之客。第 9.25 节讨论了防火墙,但那并不是安全问题的全部。

网络安全吸引了两种极端的人——那些真正喜欢入侵系统的人(无论出于乐趣还是利益),以及那些想出复杂保护方案并热衷于击退试图入侵系统的人。(这也可能非常有利可图。)幸运的是,你不需要知道太多就能保证系统安全。以下是一些基本经验法则:

  • 尽可能少运行服务 — 入侵者无法入侵系统中不存在的服务。如果你知道某个服务是什么但并未使用它,不要因为“以后可能用到”就把它打开。
  • 用防火墙尽可能多地阻挡 — Unix 系统有许多你可能不知道的内部服务(例如 RPC 端口映射服务器的 TCP 端口 111),世界上其他任何系统都不应该知道它们。因为许多不同类型的程序监听各种端口,跟踪和监管系统上的服务可能非常困难。为了防止入侵者发现你系统上的内部服务,请使用有效的防火墙规则,并在路由器上安装防火墙。
  • 跟踪你向互联网提供的服务 — 如果你运行 SSH 服务器、Postfix 或类似服务,请保持软件更新并及时接收安全警报。(参见 10.7.2 节获取一些在线资源。)
  • 服务器使用“长期支持”发行版 — 安全团队通常把工作重点放在稳定的、受支持的发行版上。例如 Debian Unstable 和 Fedora Rawhide 这样的开发和测试版获得的关注要少得多。
  • 不要向不需要的人提供你系统上的账户 — 从本地账户获取超级用户访问权限比远程入侵容易得多。事实上,鉴于大多数系统上存在庞大的软件基础(以及随之而来的漏洞和设计缺陷),获得 shell 提示符后很容易获得系统的超级用户权限。不要以为你的朋友知道如何保护他们的密码(或一开始就选择好的密码)。
  • 避免安装可疑的二进制包 — 它们可能包含特洛伊木马。

以上是保护自己的实际做法。但为什么这样做很重要?针对 Linux 机器的网络攻击主要有三种:

  • 完全入侵 (Full compromise) — 意味着获得机器的超级用户权限(完全控制)。入侵者可以通过尝试服务攻击(如缓冲区溢出利用)实现,或者通过攻破保护薄弱的用户账户,然后尝试利用编写糟糕的 setuid 程序来实现。
  • 拒绝服务攻击 (DoS attack) — 阻止机器提供网络服务,或强制计算机以其他方式发生故障而无需任何特殊访问权限。通常,DoS 攻击只是网络请求的洪水,但也可能是利用服务器程序缺陷导致崩溃。这类攻击更难防范,但更容易应对。
  • 恶意软件 (Malware) — Linux 用户基本上对电子邮件蠕虫和病毒等恶意软件免疫,仅仅是因为他们的电子邮件客户端不会愚蠢到运行邮件附件中的程序。但 Linux 恶意软件确实存在。避免从未听说过的网站下载和安装可执行软件。

10.7.1 典型漏洞 (Typical Vulnerabilities)

有两种基本类型的漏洞需要担心:直接攻击和明文密码嗅探。

  • 直接攻击试图以不太隐蔽的方式接管机器。最常见的一种是定位系统上未受保护或以其他方式易受攻击的服务。这可能简单到默认不进行身份验证的服务,例如没有密码的管理员账户。一旦入侵者获得系统上一个服务的访问权限,他们就能利用它来试图攻破整个系统。过去,一种常见的直接攻击是缓冲区溢出利用,即粗心的程序员没有检查缓冲区数组的边界。内核中的地址空间布局随机化(ASLR)技术和其他地方的防护措施在一定程度上缓解了这个问题。
  • 明文密码嗅探攻击捕获以明文形式在线上传输的密码,或使用从多次数据泄露中填充的密码数据库。一旦攻击者获得你的密码,游戏就结束了。从那时起,攻击者必然试图在本地获得超级用户权限(如前所述,这比远程攻击容易得多),试图将机器用作攻击其他主机的中间人,或两者兼而有之。

NOTE

如果你需要运行一个原生不支持加密的服务,请尝试 Stunnel(http://www.stunnel.org/),这是一个类似 TCP 包装器的加密包装包。Stunnel 特别擅长包装那些你通常会使用 systemd socket 单元或 inetd 激活的服务。

有些服务由于实现和设计糟糕,成为长期攻击目标。你应该始终停用以下服务(它们现在都已经相当过时,并且大多数系统默认很少激活):

  • ftpd — 无论出于何种原因,所有 FTP 服务器似乎都漏洞缠身。此外,大多数 FTP 服务器使用明文密码。如果你需要将文件从一台机器移动到另一台机器,请考虑基于 SSH 的解决方案或 rsync 服务器。
  • telnetd, rlogind, rexecd — 所有这些服务都以明文形式传递远程会话数据(包括密码)。除非你有 Kerberos 启用的版本,否则请避免使用它们。

10.7.2 安全资源 (Security Resources)

以下是三个很好的安全资源:

  • SANS Institute (http://www.sans.org/) 提供培训、服务、免费的每周时事通讯(列出当前最重要的漏洞)、示例安全策略等。
  • 卡内基梅隆大学软件工程学院的 CERT 分部 (http://www.cert.org/) 是查找最严重问题的好地方。
  • Insecure.org — 黑客兼 Nmap 创建者 Gordon “Fyodor” Lyon (http://www.insecure.org/) 的项目,是获取 Nmap 以及指向各种网络漏洞测试工具的链接的地方。它比许多其他网站更开放、更具体地讨论漏洞。

如果你对网络安全感兴趣,应该学习关于**传输层安全(TLS)及其前身安全套接层(SSL)**的所有内容。这些用户态网络层通常被添加到网络客户端和服务器中,通过使用公钥加密和证书来支持网络交易。一本好的指南是 Davies 的《Implementing SSL/TLS Using Cryptography and PKI》(Wiley, 2011)或 Jean-Philippe Aumasson 的《Serious Cryptography: A Practical Introduction to Modern Encryption》(No Starch Press, 2017)。

10.8 展望 (Looking Forward)

如果你有兴趣动手尝试一些复杂的网络服务器,非常常见的是 Apache 或 nginx Web 服务器以及 Postfix 电子邮件服务器。特别是,Web 服务器安装简单,大多数发行版都提供软件包。如果你的机器位于防火墙或 NAT 路由器后面,你可以随意试验配置而无需担心安全问题。

在过去的几章中,我们逐渐从内核空间转向用户空间。本章讨论的少数实用工具(如 tcpdump)是与内核交互的。本章其余部分描述了套接字如何弥合内核传输层与用户空间应用层之间的差距。这是更高级的材料,主要对程序员感兴趣,如果你愿意,可以直接跳到下一章。

10.9 网络套接字

现在我们要转换视角,看看进程如何完成从网络读取数据和向网络写入数据的工作。对于已经建立的网络连接,进程读写数据相当容易:只需要一些系统调用,你可以在 recv(2)send(2) 手册页中了解它们。从进程的角度来看,最重要的或许是在使用这些系统调用时如何访问网络。在 Unix 系统中,进程使用套接字 (socket) 来标识与网络通信的时机和方式。套接字是进程通过内核访问网络的接口;它们代表了用户空间与内核空间之间的边界。套接字也经常用于进程间通信 (IPC)。

进程需要以不同的方式访问网络,因此存在不同类型的套接字。例如,TCP 连接由流式套接字 (stream socket, SOCK_STREAM) 表示(从程序员的角度看),而 UDP 连接由数据报套接字 (datagram socket, SOCK_DGRAM) 表示。

建立网络套接字可能有些复杂,因为需要在特定时机考虑套接字类型、IP 地址、端口和传输协议。然而,在所有初始细节处理完毕后,服务器会使用某些标准方法来处理来自网络的传入流量。图 10-1 的流程图展示了许多服务器如何处理传入流式套接字的连接。

flowchart TD
    A[原始进程] --> B{检测到传入连接?}
    B -->|是| C[accept() 接受连接]
    C --> D[创建新的读写套接字]
    D --> E[fork() 创建子进程]
    E --> F[父进程继续监听<br>使用监听套接字]
    E --> G[子进程处理连接<br>使用 accept 创建的新套接字]

图 10-1:接受和处理传入连接的一种方法

请注意,此类服务器涉及两种套接字:一种用于监听,另一种用于读写。主进程使用监听套接字 (listening socket) 来寻找来自网络的连接。当新连接到来时,主进程使用 accept() 系统调用来接受连接,这会创建专用于该连接的读写套接字。接下来,主进程使用 fork() 创建一个新的子进程来处理该连接。最后,原始套接字仍作为监听器,继续代表主进程寻找更多连接。

进程建立了特定类型的套接字后,它可以根据套接字类型与之交互。这就是套接字的灵活性所在:如果需要更改底层传输层,不必重写所有发送和接收数据的部分;主要只需要修改初始化代码。

推荐读物

如果你是程序员,想学习如何使用套接字接口,W. Richard Stevens、Bill Fenner 和 Andrew M. Rudoff 合著的《Unix 网络编程 卷 1》(第 3 版,Addison-Wesley Professional, 2003)是经典指南。卷 2 也涵盖了进程间通信。

10.10 Unix 域套接字

使用网络设施的应用程序不一定涉及两台独立的主机。许多应用程序采用客户端-服务器或点对点机制构建,其中同一台机器上运行的进程使用进程间通信 (IPC) 来协商需要完成的工作以及由谁完成。例如,回想一下,systemdNetworkManager 等守护进程使用 D-Bus 来监控和响应系统事件。

进程可以使用常规的 IP 网络通过本地回环地址(127.0.0.1::1)相互通信,但它们通常使用一种特殊的套接字——Unix 域套接字 (Unix domain socket) 作为替代方案。当进程连接到 Unix 域套接字时,其行为与网络套接字几乎完全相同:它可以在套接字上监听和接受连接,你甚至可以在不同的套接字类型之间进行选择,使其行为类似于 TCP 或 UDP。

重要说明

请记住,Unix 域套接字不是网络套接字,背后也没有网络。你甚至不需要配置网络即可使用它。Unix 域套接字也不必绑定到套接字文件。进程可以创建未命名的 Unix 域套接字,并将地址共享给另一个进程。

开发人员喜欢将 Unix 域套接字用于 IPC,原因有二。第一,它们允许选择使用文件系统中的特殊套接字文件来控制访问权限,因此任何无权访问套接字文件的进程都无法使用它。而且由于不涉及网络交互,它更简单,也更不易受到常规网络入侵。例如,你通常会在 /var/run/dbus 中找到 D-Bus 的套接字文件:

$ ls -l /var/run/dbus/system_bus_socket 
srwxrwxrwx 1 root root 0 Nov  9 08:52 /var/run/dbus/system_bus_socket

第二,由于 Linux 内核在处理 Unix 域套接字时无需经过网络子系统的众多层次,因此性能通常要好得多。

为 Unix 域套接字编写代码与支持普通网络套接字差别不大。由于其优势显著,一些网络服务器同时提供通过网络和 Unix 域套接字进行通信的方式。例如,MySQL 数据库服务器 mysqld 可以接受来自远程主机的客户端连接,但它通常也会在 /var/run/mysqld/mysqld.sock 处提供一个 Unix 域套接字。

你可以使用 lsof -U 查看系统当前使用的 Unix 域套接字列表:

# lsof -U
COMMAND     PID       USER   FD   TYPE     DEVICE SIZE/OFF     NODE NAME
mysqld    19701      mysql   12u  unix 0xe4defcc0      0t0 35201227 /var/run/mysqld/mysqld.sock
chromium- 26534      juser    5u  unix 0xeeac9b00      0t0 42445141 socket
tlsmgr    30480    postfix    5u  unix 0xc3384240      0t0 17009106 socket
tlsmgr    30480    postfix    6u  unix 0xe20161c0      0t0    10965 private/tlsmgr
--snip--

该列表会相当长,因为许多应用程序广泛使用未命名的套接字,在 NAME 输出列中显示为 socket