服务器加固

作者:Greg Bledsoe

服务器加固。仅仅这个词语就能让人联想到将软钢淬炼成坚不可摧的刀刃,或者将软泥土在窑中烧制,制成经久耐用的硬化容器。的确,服务器加固非常类似这样。将一台未受保护的服务器暴露在互联网上,就像在你游泳的海洋中放入鱼饵——很快你就会看到一群兴奋的鲨鱼在你周围盘旋,结果不太可能是好的。每个人都知道这一点,但有时在截止日期的压力下,更不用说来自商业利益的不可避免的推动,他们优先考虑那些更直接可见且能增加利润的事情,因此很难跟上你需要缓解的威胁,更不用说使用最佳技术来做到这一点了。这就是偷工减料的方式——这些偷工减料增加了我们遭受灾难的风险。

这并非完全不可原谅。系统管理员必须是万事通,而安全只是必须考虑的责任之一,而且不是最有可能引起直接痛苦的责任。即使在拥有专门安全人员的组织中,那些致力于安全性的部门也经常花费时间来跟上最新漏洞的细节,并且可能不如那些深入维护堆栈的人员那样了解他们正在保护的堆栈。各个部门组织越专业化和多元化,每个部门与大局的隔离就越严重。没有大局观,就更难在安全性和功能性之间做出明智的权衡。由于深入而全面的技术堆栈知识以及它所服务的业务对于做好安全工作是必要的,因此有时看起来几乎毫无希望。

一项真正全面的服务器加固工作将超出单篇文章的范围,甚至超出单本(非常厚的)书的范围,但并非一切都失去了。诚然,由于技术所处的环境、技术和用途多种多样,因此不可能有“一种真正的加固程序”,但同样真实的是,你可以开发一种方法来管理这些技术以及将技术投入使用的流程,从而引导你走向合理的设置。你可以将要点归结为几个原则,然后你可以将其普遍应用。在本文中,我将探讨一些应用示例。

我还应该说,服务器加固本身几乎是一种无用的努力,如果你要用诸如“abc123”之类的懒惰密码选择来削弱自己,或者在环境中缺乏整体的安全方法。不安全的编码实践可能意味着你打开的一个漏洞是巨大的,并且用户通过电子邮件发送密码可能会抵消你所有的辛勤工作。人的因素是关键,这意味着在流程的每个步骤都要培养安全意识。与其说是内置于系统,不如说是附加于系统的安全性永远不会像前者那样完整或易于维护,但是当你没有执行组织标准的高层支持时,附加安全性可能是你能做的最好的事情。不过,你可以安心入睡,因为至少你负责的 Linux 服务器实际上得到了适当的(即使不是详尽的)安全保护。

服务器加固的最重要原则是:最小化你的攻击面。原因很简单且直观:较小的目标更难击中。在服务器的所有方面应用此原则至关重要。这首先要安装仅对于服务器的业务目的以及最少的管理和维护软件包绝对必要的特定软件包和软件。存在的每样东西都必须经过审查、信任和维护。每一行可以运行的代码都是你系统上的另一个潜在漏洞,而未安装的东西则无法被用来攻击你。我所知道的每个发行版和服务都有一个最小安装选项,这始终是你应该开始的地方。

第二个最重要的原则与之类似:保护必须暴露的东西。这同样涵盖了从硬件的物理访问到加密所有你可以加密的一切——磁盘上的静态数据、网络上的数据以及介于两者之间的所有数据。对于服务器的物理位置,锁、生物识别技术、访问日志——你可以用来控制和记录谁可以物理访问你的服务器的所有工具都是好东西,因为物理访问、可访问的 BIOS 和可启动的 USB 驱动器只是一种组合,可能意味着你的服务器可能已经长腿跑了,并且带走了你所有的数据。流氓的、隐藏的无线 SSID 可以从 USB 设备广播一段时间,然后才会被偶然发现。

不过,就本文而言,我将做出一些假设,这将缩小要涵盖的主题范围。假设你正在云服务(如 AWS 或 Rackspace)上部署新的基于 Linux 的服务器。你首先需要做什么?由于这在别人的数据中心中,并且你已经审查了提供商的物理安全实践(对吧?),你首先从你选择的发行版和最小安装开始——只需足够启动并启动 SSH,以便你可以访问你闪亮的新服务器。

在这个示例场景的参数范围内,根据服务器的用途,关注的级别有所不同,从“这是一个我正在玩弄的玩具,我不在乎它会发生什么”一直到“如果此信息泄露,政府将会垮台,大量民众会死亡”,尽管在每种情况下都需要应用不同程度的偏执和努力,但原则仍然相同。即使你不在乎服务器最终会发生什么,你仍然不希望它加入僵尸网络并助长互联网混乱。如果你不在乎,那你很糟糕,你应该感到糟糕。如果你正在为后一种目的设置服务器,你可能比我更专业,并且没有理由阅读本文,因此让我们折中一下,假设如果你的服务器被破解,尴尬、品牌损害和收入损失(以及你的工作)将会随之而来。

在任何这些情况下,首先要做的事情都是加强你的网络访问。如果托管提供商提供了实现此目的的机制,例如亚马逊的“区域”,请使用它,但不要止步于此。在保护必须暴露的东西之下是另一个原则:层层叠叠,障碍重重。增加到达最终目的地所需的努力,你就会减少愿意并且能够到达那里的人数。区域或网络防火墙可能会因错误、失误以及谁知道可能出现的哪些因素而失败。在发生故障时最大化冗余和备份系统本身就是一件好事。所有最受关注的数据盗窃事件都发生在不仅忽略了本文中包含的一些建议,而且忽略了所有建议的情况下,如果只有一个障碍需要克服,那么那些负责任的人很可能会转向其他更容易得手的人。不要成为容易得手的猎物。你不必总是跑过熊。

第一个原则,即不存在(未安装或未运行)的东西不能被用来攻击你,要求你确保你已关闭并关闭了所有运行级别中所有不必要的服务和端口,并通过服务器的防火墙使它们无法访问,此外还有你在网络上进行的任何其他防火墙操作。这可以通过你的发行版的工具来完成,或者仅仅通过编辑 /etc/rcX.d 目录中的文件名来完成。如果你不确定是否需要某些东西,请将其关闭,重新启动,然后查看哪些东西坏了。

但是,在执行上述操作之前,请确保你首先有一个紧急控制台后门!这不会是你最后一次需要它。当刚开始摆弄服务器安全时,你很可能会不止一次将自己锁定在外面。如果你的提供商没有提供在网络无法访问时可以工作的控制台,那么下一个最好的方法是拍摄映像并在服务器变黑时回滚。

我建议首先做两件事:运行 ps -ef 并确保你了解所有正在运行的进程都在做什么,以及 lsof -ni | grep LISTEN 以确保你了解所有侦听端口为何打开,以及你期望的进程已打开它们。

例如,在我运行 WordPress 的一台服务器上,结果如下


# ps -ef | grep -v \] | wc -l
39

我不会列出我所有的进程名称,但在提取所有内核进程后,我还有 39 个其他进程在运行,我确切地知道它们都是什么以及它们为何运行。接下来我检查


# lsof -ni | grep LISTEN
mysqld    1638    mysql  10u  IPv4  10579  0t0  TCP
127.0.0.1:mysql (LISTEN)
sshd      1952     root   3u  IPv4  11571  0t0  TCP *:ssh (LISTEN)
sshd      1952     root   4u  IPv6  11573  0t0  TCP *:ssh (LISTEN)
nginx     2319     root   7u  IPv4  12400  0t0  TCP *:http (LISTEN)
nginx     2319     root   8u  IPv4  12401  0t0  TCP *:https (LISTEN)
nginx     2319     root   9u  IPv6  12402  0t0  TCP *:http (LISTEN)
nginx     2320 www-data   7u  IPv4  12400  0t0  TCP *:http (LISTEN)
nginx     2320 www-data   8u  IPv4  12401  0t0  TCP *:https (LISTEN)
nginx     2320 www-data   9u  IPv6  12402  0t0  TCP *:http (LISTEN)

这完全符合我的预期,并且它是服务器用途(运行 WordPress)所需的最小端口集。

现在,为了确保仅打开必要的端口,你需要调整你的防火墙。大多数托管提供商,如果你使用他们的模板之一,默认情况下会将所有规则设置为“接受”。这很糟糕。这违背了第二个原则:必须暴露的东西必须得到保护。如果由于某种自然事故,某些软件打开了你未预料到的端口,你需要确保它无法访问。

每个发行版都有其用于管理防火墙的工具,并且大多数软件包管理器中也提供了其他工具。我不费心使用它们,因为 iptables(一旦你熟悉它)相当容易理解和使用,并且它在所有系统上都是相同的。就像 vi 一样,你可以期望它无处不在,因此能够使用它是有好处的。一个基本的防火墙看起来像这样


# make sure forwarding is off and clear everything
# also turn off ipv6 cause if you don't need it 
# turn it off
sysctl net.ipv6.conf.all.disable_ipv6=1
sysctl net.ipv4.ip_forward=0
iptables -F
iptables --flush
iptables -t nat --flush
iptables -t mangle --flush
iptables --delete-chain
iptables -t nat --delete-chain
iptables -t mangle --delete-chain


#make the default -drop everything
iptables --policy INPUT DROP
iptables --policy OUTPUT ACCEPT
iptables --policy FORWARD DROP


#allow all in loopback
iptables -A INPUT -i lo -j ACCEPT

#allow related
iptables -A INPUT -m state --state 
 ↪ESTABLISHED,RELATED -j ACCEPT

#allow ssh
iptables -A INPUT -m tcp -p tcp --dport 22 -j ACCEPT

你可以变得花哨,将其包装在一个脚本中,在 /etc/rc.d 中放置一个文件,将其链接到 /etc/rcX.d 中的运行级别,并使其在联网后立即启动,或者对于你的目的来说,直接从 /etc/rc.local 运行它可能就足够了。然后,你可以根据需求的变化修改此文件。例如,要允许 ssh、http 和 https 流量,你可以将上面的最后一行切换到这一行


iptables -A INPUT -p tcp -m state --state NEW -m 
 ↪multiport --dports ssh,http,https -j ACCEPT

更具体的规则更好。假设你构建的是一个内联网服务器,并且你知道你的流量将来自何处以及在哪个接口上。你可以将类似这样的内容添加到你的 iptables 脚本的底部


iptables -A INPUT -i eth0 -s 192.168.1.0/24 -p tcp 
 ↪-m state --state NEW -m multiport --dports http,https

在这个示例中,有几个你需要考虑进行调整的事项。首先,这允许从服务器发起的全部出站流量。根据你的需求和偏执程度,你可能不希望这样做。将出站流量设置为默认拒绝将大大增加安全更新等维护的复杂性,因此请权衡这种复杂性与你对 rootkit 向外拨号回家的担忧程度。如果你选择默认拒绝出站,iptables 是一个极其强大且灵活的工具——你可以根据进程名称和所有者用户 ID 等参数控制出站通信、速率限制连接——几乎任何你能想到的——因此如果你有时间进行实验,你可以非常精细地控制你的网络流量。

其次,我将默认值设置为 DROP 而不是 REJECTDROP 有点像通过混淆来达到安全。如果他的端口扫描花费的时间太长,它可以阻止一个脚本小子,但由于你打开了常用扫描的端口,它不会阻止一个坚定的攻击者,并且它可能会使你自己的故障排除变得复杂,因为你必须等待客户端超时,以防你在 iptables 中阻止了端口,无论是故意的还是意外的。此外,正如我在Linux Journal 的一篇先前文章中详细介绍的那样(https://linuxjournal.cn/content/back-dead-simple-bash-complex-ddos),TCP 级别的拒绝在流量大的情况下非常有用,可以清除用于在服务器和更远处的网络设备上状态化跟踪连接的资源。你的里程可能会有所不同。

最后,你的发行版的最小安装可能没有安装或默认启用 sysctl。你需要它,因此请确保它已启用并正常工作。它使检查和更改系统值变得更加容易,因为大多数版本都支持制表符自动完成。你也可能需要包含二进制文件的完整路径(通常是 /sbin/iptables 和 /sbin/sysctl),具体取决于你的特定系统的基本路径变量。

以上所有操作可能都应该在启动服务器后的几分钟内完成。我建议在安装和配置服务器上运行的应用程序之前不要打开应用程序的端口。因此,在你拥有仅打开 SSH 的新最小服务器时,你应该使用你的发行版的方法应用所有更新。你现在可以决定是否要按计划手动执行此操作,还是将其设置为自动,你的发行版可能具有执行此操作的机制。如果没有,在 cron.daily 中放置一个脚本即可解决问题。有时更新会破坏某些东西,因此请仔细评估。无论你是否进行自动更新,鉴于现在如此频繁地发现有时需要手动配置更改的严重缺陷,你需要手动监视适当的列表和站点,以获取你的堆栈的关键安全更新,并在必要时应用它们。

处理完更新后,你可以继续前进,并继续根据 1) 最小攻击面和 2) 保护所有必须暴露的东西这两个安全原则评估你的服务器。在这一点上,你在第一点上非常扎实。在第二点上,你还有更多可以做的。

障碍的概念要求你不允许 root 用户远程登录。获得 root 权限至少应该是一个两部分的过程。这很容易做到;你只需在 /etc/ssh/sshd_config 中设置这一行


PermitRootLogin no

就此而言,root 用户根本不应该能够直接登录。该帐户应该没有密码,并且应该只能通过 sudo 访问——另一个需要清除的障碍。

如果用户不需要远程登录,则不允许它,或者更确切地说,仅允许你认识的需要远程访问的用户。这满足了这两个原则。使用 /etc/ssh/sshd_config 中的 AllowUsersAllowGroups 设置来确保你仅允许必要的用户。

你可以为你的服务器设置密码策略,以要求任何和所有用户都使用复杂密码,但我认为通常更好的方法是完全绕过可破解的密码,而使用仅密钥登录,并使密钥需要复杂的密码短语。这提高了破解你的系统的门槛,因为暴力破解 RSA 密钥几乎是不可能的。密钥可能会从你的客户端系统物理盗取,这就是为什么你需要复杂的密码短语的原因。无需深入讨论密钥或密码短语的长度或强度,创建它的一种方法是这样的


ssh-keygen -t rsa

然后在提示时,输入并重新输入所需的密码短语。将公共部分(id_rsa.pub 或类似文件)复制到用户主目录中名为 ~/.ssh/authorized_keys 的文件中,然后在新的终端窗口中,尝试登录,并根据需要进行故障排除。我将密钥和密码短语存储在 Personal, Inc.(https://personal.com)提供的安全数据保险库中,这将允许我,即使不在家且不在我的常用系统旁边,也可以安装密钥并拥有密码短语来解锁它,以防发生紧急情况。(免责声明:Personal 是我目前工作的初创公司。)

一旦它工作了,请更改 /etc/ssh/sshd_config 中的这一行


PasswordAuthentication no

现在你只能使用密钥登录。我仍然建议为用户保留一个复杂密码,这样当你 sudo 时,你也拥有那一层保护。现在要完全控制你的服务器,攻击者需要你的私钥、你的密码短语和你服务器上的密码——障碍重重。事实上,在我的公司,除了这些其他方法之外,我们还使用多因素身份验证,因此你必须拥有密钥、密码短语、预先保护的设备(它将接收登录请求的通知)和用户的密码。这是一座相当陡峭的山峰要攀登。

加密是保持服务器安全的重要组成部分——加密所有对你重要的数据。始终注意数据,尤其是身份验证数据,是如何存储和传输的。不用说,你绝不应该允许通过未加密的通道(如 FTP、Telnet、rsh 或其他旧协议)进行登录或连接。这些都是巨大的禁忌,会完全抵消你为保护服务器所做的所有辛勤工作。任何可以访问附近交换机并执行反向 arp 欺骗以镜像你的流量的人都将拥有你的服务器。始终使用 sftp 或 scp 进行文件传输,并使用 ssh 进行安全 shell 访问。为应用程序的登录使用 https,并且永远不要存储密码,只存储哈希值。

即使使用了强大的加密,在最近的过去,在广泛使用的程序和协议中也发现了许多漏洞——习惯于在 OpenSSH 和 OpenSSL 中都打开和关闭密码。我在这里不讨论 Web 服务器,但你将放在你的 /etc/ssh/sshd_config 文件中的相关行看起来像这样


Ciphers aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128
MACs hmac-sha1,umac-64@openssh.com,hmac-ripemd160

然后你可以根据需要添加或删除。有关所有详细信息,请参阅 man sshd_config

根据你的偏执程度和服务器的用途,你可能会想在此处停止。我不会。习惯于安装、使用和调整更多安全要素,因为这最后几个步骤将使你呈指数级地更安全。我现在已经深入到第二个原则(保护所有必须暴露的东西),并且我正在接近第三个原则:假设每项措施都将被击败。第三个原则肯定存在收益递减的点,即风险的变化不足以证明额外的时间和精力是合理的,但该点落在何处是需要你和你的组织决定的。

事实是,即使你锁定了你的身份验证,仍然存在机会,无论多么小,配置错误或更新正在更改/破坏你的配置,或者由于盲目的运气,攻击者可能会找到进入你系统的方法,甚至该系统带有后门。你可以做一些事情来进一步保护你免受这些风险。

说到后门,从手机到硬盘驱动器的固件,一切都预装了后门。联想至少三次被抓到预装 rootkit,而索尼在一个误导性的 DRM 尝试中 root 了客户系统。OpenSSL 中的一个编程错误留下了一个漏洞,NSA 一直在利用该漏洞来击败加密至少十年而没有通知社区,这显然只是几个漏洞之一。在 2000 年代后期,有人匿名试图在 Linux 内核中插入一个两行编程错误,该错误会在某些条件下导致远程 root 漏洞。因此,我个人不信任任何来自 NSA 的东西,并且我关闭了 SELinux,因为我是授权和第四修正案的粉丝,这就足够了。说明通常是可用的,但通常你只需要对 /etc/selinux/config 进行此更改


#SELINUX=enforcing # comment out
SELINUX=disabled # turn it off, restart the system

本着关闭和阻止不需要的东西的精神,由于互联网上的大多数恶意流量都来自少数来源,为什么你需要给他们一个破解你服务器的机会?我运行一个简短的脚本,收集僵尸网络中被利用服务器的各种黑名单、中国和俄罗斯 CIDR 范围等等,并从中创建一个阻止列表,每天更新一次。在过去,你无法做到这一点,因为 iptables 会因匹配超过几千行而变得迟缓,因此为每个恶意 IP 制定规则是不可行的。随着 ipset 项目的成熟,现在可以了。ipset 使用二进制搜索算法,每次列表加倍时,搜索仅增加一次传递,因此可以有效地搜索任意大的列表以进行匹配,尽管我相信 ipset 表中条目限制为 65k。

要使用它,请将其添加到你的 iptables 脚本的底部


#create iptables blocklist rule and ipset hash
ipset create blocklist hash:net
iptables -I INPUT 1 -m set --match-set blocklist 
 ↪src -j DROP

然后将此内容放在某个可执行位置,并每天从 cron 运行一次


#!/bin/bash

PATH=$PATH:/sbin
WD=`pwd`
TMP_DIR=$WD/tmp
IP_TMP=$TMP_DIR/ip.temp
IP_BLOCKLIST=$WD/ip-blocklist.conf
IP_BLOCKLIST_TMP=$TMP_DIR/ip-blocklist.temp
list="chinese nigerian russian lacnic exploited-servers"
BLOCKLISTS=(
"http://www.projecthoneypot.org/list_of_ips.php?t=d&rss=1" # Project
 ↪Honey Pot Directory of Dictionary Attacker IPs
"http://check.torproject.org/cgi-bin/TorBulkExitList.py?ip=1.1.1.1"  
 ↪# TOR Exit Nodes
"http://www.maxmind.com/en/anonymous_proxies" # MaxMind GeoIP 
 ↪Anonymous Proxies
"http://danger.rulez.sk/projects/bruteforceblocker/blist.php" 
 ↪# BruteForceBlocker IP List
"http://rules.emergingthreats.net/blockrules/rbn-ips.txt" 
 ↪# Emerging Threats - Russian Business Networks List
"http://www.spamhaus.org/drop/drop.lasso" # Spamhaus Dont Route 
 ↪Or Peer List (DROP)
"http://cinsscore.com/list/ci-badguys.txt" # C.I. Army Malicious 
 ↪IP List
"http://www.openbl.org/lists/base.txt"  # OpenBLOCK.org 30 day List
"http://www.autoshun.org/files/shunlist.csv" # Autoshun Shun List
"http://lists.blocklist.de/lists/all.txt" # blocklist.de attackers
)

cd  $TMP_DIR
# This gets the various lists
for i in "${BLOCKLISTS[@]}"
do
    curl "$i" > $IP_TMP
    grep -Po '(?:\d{1,3}\.){3}\d{1,3}(?:/\d{1,2})?' $IP_TMP >>
$IP_BLOCKLIST_TMP
done
for i in `echo $list`; do
    # This section gets wizcrafts lists
    wget --quiet http://www.wizcrafts.net/$i-iptables-blocklist.html
    # Grep out all but ip blocks
    cat $i-iptables-blocklist.html | grep -v \< | grep -v \: |
     ↪grep -v \; | grep -v \# | grep [0-9] > $i.txt
    # Consolidate blocks into master list
    cat $i.txt >> $IP_BLOCKLIST_TMP
done

sort $IP_BLOCKLIST_TMP -n | uniq > $IP_BLOCKLIST
rm $IP_BLOCKLIST_TMP
wc -l $IP_BLOCKLIST

ipset flush blocklist
egrep -v "^#|^$" $IP_BLOCKLIST | while IFS= read -r ip
do
        ipset add blocklist $ip
done

#cleanup
rm -fR $TMP_DIR/*

exit 0

你可能不希望阻止所有这些。我通常保持 tor 出口节点开放以启用匿名性,或者如果你在中国开展业务,你当然不能阻止来自那里的每个 IP 范围。从要下载的 URL 中删除不需要的项目。当我启用此功能后,在 24 小时内,由 SSH 上的暴力破解尝试触发的被禁 IP 数量从数百个下降到不到十个。

尽管还有许多其他领域需要加固,但由于根据第三个原则,我们假设所有措施都将被击败,因此我将不得不将锁定 cron 和 bash 以及跨环境自动化标准安全配置等事情留到以后再说。还有一些软件包我认为是安全必需品,包括多种检查入侵的方法(我运行 chkrootkit 和 rkhunter 来更新签名并至少每天扫描我的系统)。我想用最后一个必用的工具结束:Fail2ban。

Fail2ban 现在几乎在每个发行版的存储库中都可用,并且它已成为我的首选。它不仅是一个可扩展的暴力破解身份验证预防瑞士军刀,而且还附带了大量的过滤器,用于检测对你的系统进行恶意操作的其他尝试。如果你除了安装它、运行它、保持更新并为你运行的任何服务(尤其是 SSH)启用其过滤器之外什么都不做,你也会比以前好得多。至于我,我还有其他更高级别的软件(如 WordPress)记录到 auth.log,以便使用 Fail2ban 过滤和禁止作恶者。你可以自定义配置基于多少个过滤器匹配(如各种类型的登录尝试失败)来禁止多长时间,并为不断回来的“累犯”滥用者指定更长的禁止时间。

这是该工具可扩展性的一个示例。在日志审查(整体安全方法的另一个重要组成部分)期间,我注意到成千上万的以下类型的探测,尤其来自中国


sshd[***]: Received disconnect from **.**.**.**: 11: Bye Bye [preauth]
sshd[***]: Received disconnect from **.**.**.**: 11: Bye Bye [preauth]
sshd[***]: Received disconnect from **.**.**.**: 11: Bye Bye [preauth]

这种探测有两种形式,我找不到任何已知漏洞的解释与这种模式匹配,但一定有理由让我如此快速地收到这么多探测。它不足以成为拒绝服务,但它是一个稳定的流量。要么它是一个零日漏洞,要么是某种算法发送各种类型的畸形请求,希望触发内存问题,以期发现漏洞——无论如何,都没有理由允许它们继续。

我将这一行添加到 /etc/fail2ban/filter.d/sshd.local 的 failregex = 部分


^%(__prefix_line)sReceived disconnect from <HOST>: 
 ↪11: (Bye Bye)? \[preauth\]$

在几分钟之内,我已经禁止了 20 个新的 IP 地址,并且我的日志几乎完全清除了这些行。

到现在为止,你已经看到了我在服务器加固方面的三个主要原则的实际应用,足以了解系统地将它们应用于你的系统将使你在短时间内批量生产出相当加固的系统。但是,为了再次重申一遍

  1. 最小化攻击面。

  2. 保护剩余的且必须暴露的一切。

  3. 假设所有安全措施都将被击败。

请随时给我留言,让我知道你对这篇文章的想法。让我知道你对我的决定要包含的内容的想法,你认为应该包含但为了节省空间而被我删除的任何主要遗漏,以及你想在未来看到的内容!

加载 Disqus 评论