SSH 服务器加固之旅

  Armstrong 的服务器正在运行 CentOS 8 系统,远程管理 Linux 系统的服务器经常会使用 SSH 那是大家都知道的事情,至少,正在看本文的你那是清楚得不能再清楚了,简单说,通过 SSH 操作远程服务器,就好像你坐在服务器控制台前面一样。在那帮攻击服务器的家伙看来,直接攻击 SSH 可要比一步一步渗透要来得直接,你知道吗?攻击 SSH 偶尔是会成功的,即便没有成功,那些家伙没日没夜地骚扰你的 SSH 服务器,就好像小偷不断使用万能钥匙来试验你家大门那个铁将军一样,叫人心烦,现实生活当中,真没有人敢去试验门锁能不能打开,在因特网世界,那可不是这样了。如果情况再糟糕一点,服务器真的被攻击者打开了,对于你来说,也许是一场噩梦的开始,你被迫重装你的服务器系统,检查受损的文件并让它们恢复正常。我们现在就开始吧!走!
  在开始的开始,我想,你一定明白密码的重要性,那么,请你用 passwd 命令,为你的 root 账号设置一个复杂的密码,别忘了,你设置的密码既要方便你自己记得住,又要防止被攻击者猜中。老生常谈的不要用生日作为密码,你怎么看?不要相信那帮蠢材说的话,你当然可以用你的生日作为密码,这没什么不可以的,当然了,你一定要在其中掺杂一些英文字母或者符号。如果再把一些数字用符号或看起来相近的英文字母替代一下可以吗?Oh 天哪,你的想法真是他妈的太妙了,如果你总是使用 qwerty 键盘,你甚至可以用移位法。好了,你的密码也设置得差不多了,我们继续往前走,头也不回地往前走。
  SSH 使用的网络端口是 TCP 22 号端口,对吧?没错,那帮攻击者早就知道了这一点,所以,咱们一定要把它改了,怎么改呢?Jackson 先生,你的服务器正在跑 CentOS 8 系统,对吧?Oh No,Armstrong 先生,我的服务器由于当年开发的程序有需要,所以现在它还在跑着 CentOS 7。没事儿,Jackson 先生,CentOS 7 和 CentOS 8 的修改方法是完全一样的,我们现在就来修改吧!且慢,修改之前,我们还需要首先设置防火墙。防火墙?是的,不然它会把你挡在外面,这样,你就不能控制你的服务器了。在我们的终端上,输入下面两条命令:
firewall-cmd –add-port=51136/tcp
firewall-cmd –permanent –add-port=51136/tcp
  Armstrong 先生,为什么要输入两行命令,看起来它们都是一样的?是这样的,第一条命令是改变防火墙的当前配置,第二条命令是改变防火墙的配置文件,也就是会存盘的,如果你只执行第一条命令,那么防火墙软件重启之后,配置就丢失了,如果你只执行第二条命令,你就必须重新加载防火墙的配置文件,这样才能让配置起作用,明白了吗?Eh,这个 51136 是什么呢?它就是你要修改到的端口号,你一定要选择 1024 以上的端口号,因为 1024 以下的端口号原则上是系统保留的,而且,你还要注意有没有跟其它网络服务软件起冲突。Armstrong 先生,我要怎么判断我想要修改到的端口现在有没有被占用,怎么做?好的 Jackson 先生,你可以执行下面这条命令:
netstat -ln | grep “:51136″
如果你执行之后,有类似这样的输出,就表示。就表示端口被占用了,是吗?Um,端口他妈的被占用了。
tcp 0 0 0.0.0.0:51136 0.0.0.0:* LISTEN
防火墙的配置完毕之后,我们就来修改 SSH 服务器的配置文件。Eh,它们两个不是联动的吗?也就是说,我修改了其中一个,另一个就跟着修改了。不,防火墙就相当于咱们的门卫,我们只是告诉门卫,放行某个端口,而我们的程序没有监听该端口,就好像门口打开了,但里面是空的一样。Oh 好吧,配置文件在哪里呢?在这里:/etc/ssh/sshd_config,我们打开它。这个配置文件有一百多行,我们需要找到 Port 所在的那行,那行的前面很可能有一个井号,把 Port 前面的井号删除,然后复制它,在行尾按一下回车键,然后毫不犹豫地粘贴,把粘贴的那行的 22 改成 51136,或者你想要的端口号,改完后存盘,但是,千万别改到驴唇不对马嘴哈,也就是说,防火墙开放了一个端口,配置文件中却设置成别的端口。
  好了,Jackson 先生,我们来重启 SSH 服务器吧,输入命令:systemctl restart sshd,然后执行,确认没有出现错误信息吗?好的,我们来通过新的端口连接 SSH 服务器。Armstrong 先生,为了确保万无一失,我们应该新打开一个 SSH 会话,而不要关闭现在的 SSH 会话吧?不要那么对自己没信心,放心地关掉吧!Ah,我不敢这么做,Armstrong 先生。Eh,新端口连接失败?你关掉了先前的 SSH 会话了吗?Jackson 先生,我没有关掉。谢天谢地。我们赶紧来运行 yum install -y policycoreutils-python-utils,安装完成后,我们来运行 semanage port -a -t ssh_port_t -p tcp 51136。大家不要被吓坏了,这是最糟糕的情况下才会出现的问题。对了 Armstrong 先生,我记得一个细节,我们刚才修改配置文件的时候,保留了 22 端口的那行参数,这是不是为了避免出现刚才那种无法连接的情况?是的,Jackson 先生,我之前配置服务器的时候,就是在这里搞砸了,然后我的师傅 Andrew 先生告诉我,在确认新端口有效之前,不要让以前的端口无法连接上,这是相当关键的。Oh,Andrew 先生真是太有经验了。可不是吗,Jackson 先生。Oh,服务器能连上了!
  我们刚才为了应对新端口可能无法连接的情况,保留了原来的端口,现在可以把它安全地关上了。如果我们要通过 SSH 配置文件关闭 22 端口,可以在那行配置语句之前加上半角的井号(#),也可以关闭防火墙上的 SSH 服务端口,请执行下面的两条命令:
firewall-cmd –remove-service=ssh
firewall-cmd –permanent –remove-service=ssh
  Armstrong 先生,上面的两行命令,就跟开放某个端口是差不多的?是的 Jackson 先生,唯一的不同是,我们是在关闭服务,前面我们打开端口是直接打开端口的。Eh,怎么理解呢?是这样的,防火墙为了方便我们的操作,会把服务需要的一个或多个端口汇聚在一个 XML 文件里,这样,我们就可以很容易开放或关闭服务所需的一组端口。Oh,可以详细介绍一下吗?Oh Sorry,我们今天的重点不是要详解防火墙的配置,我们以后有机会再来分享吧,Jackson 先生。那我们继续往前走吧。好的 Jackson 先生。
  密码真是一种糟糕的东西,设置太简单容易被别人暴力破解,设置复杂,又容易忘记,Jackson 先生,有没有想过让电脑自己生成秘钥,然后我们用它来登录呢?Armstrong 先生,这想法好,可是,一个密码我们都记不住,电脑生成的密码肯定很长吧,我们哪里记得住呢?伙计,想不到你也翻到了坑里,秘钥和密码不是同一种东西,我们可以把秘钥看做一个文件,就好像凭证一样,比如,你的机票,你只要拿着你的机票,就能登上飞机,而不需要播报密码,尽管听起来用密码取代机票有点荒诞,实际上,无论密码也好,秘钥也罢,甚至是机票,做的事情都是同样的,那就是完成身份验证。Oh,秘钥跟密码是什么关系呢?Jackson 先生,你说的是登录 SSH 的情况吗?Yes,是的,Armstrong 先生。如果你把服务器配置成通过秘钥来登录,那么,你就不需要输入密码,为了保障服务器的安全,你确实应该为你的秘钥设置一个密码,避免秘钥失窃后,攻击者可以轻易打开你的秘钥,这样可以给你预留足够的时间来为服务器更换一组新的秘钥。我们现在就开始创建秘钥并配置服务器,让服务器只能通过秘钥进行登录吧!
  要以秘钥替代账号的密码来登录服务器,我们首先得为账户创建一个秘钥,怎么创建?首先,我们正常登录到自己的账户,比如 root 账户,并启动控制台。好了,控制台被成功启动,我们来执行:ssh-keygen。SSH 秘钥生成器会问我们要把秘钥保存在哪里,并且它提供了预设值,这里,只要按下回车键确认就行。这一步,SSH 秘钥生成器会要求我们键入私钥的密码,我们可以在这里输入私钥的密码,也可以留空,在以后通过 Puttygen.exe 修改它,我们输入 armstrong,然后回车。再次输入一模一样的密码,然后按回车。好了,公钥和私钥被我们创建出来了。
  Jackson 先生,这应该很简单吧?可不是吗,我们下一步要做什么呢?Armstrong 先生。这一步,我们来重命名我们的公钥,首先,我们要知道秘钥创建在了哪里?通常,它被放在用户目录的 .ssh 文件夹。来吧,执行:cd ~/.ssh/,这条命令是跳转到秘钥所在的文件夹,我们来执行:mv id_rsa.pub authorized_keys,如果不执行这条重命名文件的命令,OpenSSH 程序就找不到我们的公钥,秘钥登录的设置也就不会成功。最重要的,我们可不能让其他人拿走我们的公钥了,如果公钥落入攻击者手中,他们就可以用来冒充我们的服务器,这非常危险!我们来执行:chmod 0600 ./authorized_keys。好了,公钥的安装已经完成,我们来把私钥下载到客户端机器,看到了吗?id_rsa,没错,就是它。下载到客户端,然后把它从服务器上删除。
  Armstrong 先生,我通常使用 WinSCP 和 PuTTY 来登录服务器,这个秘钥下载后就可以马上使用了吗?不,Jackson 先生,你还需要把它转换成 PuTTY 可以识别的格式。怎么转换?我们下载 PuTTY 的时候,可以下载 puttygen,Jackson 先生,你已经下载了 puttygen 了,对吗?是的,我们来打开它吧。打开后,我们弹出 Files 菜单,然后点击 Load private key,找到咱们下载的 id_rsa 文件,打开它,如果没有,就在文件类型组合框里,选择所有文件。如果你设置了密码,那么,这里会要求你输入密码,否则,会提示 Successfully imported foreign key,如果出现了错误,那就更新 puttygen 软件。把 Key passphrase 和 Confirm passphrase 清空,然后为私钥设置一个密码,点击 Save private key,将私钥保存在一个安全的位置,关闭 puttygen。
  我们启动 WinSCP,然后输入连接服务器的参数,请记住不要输入你的密码,点击高级,然后切换到验证标签页,浏览到私钥文件,然后点击确定,如果 Jackson 先生想要方便地连接到服务器,可以点击保存,点击登录。输入用户名,然后按回车键,如果没有为私钥设置密码,现在就成功登录到系统了,当然,我们已经设置了密码,所以,我们现在需要输入私钥的密码来打开私钥,从而进行登录。如果你的私钥没有密码,但是登录时还要输入密码,或者输入私钥的密码无法登录,输入服务器账号的密码反而可以登录,这种情况,Armstrong 先生是否遇到过呢?Oh,这种情况,基本上都是秘钥配置不正确导致的,在登录之前看一下密码输入框上面的列表是一个好习惯,如果里面出现了“服务器拒绝我们的秘钥”或者“Server Reject Ours Key”就要当心你的秘钥可能配置不正确了,在这种情况下,WinSCP 会尝试使用用户账户的密码进行登录,而不是秘钥。
  我们的秘钥工作正常,所以,现在是禁止使用账户密码登录的时候了,怎么做呢?让我们重返 sshd_config 文件,找到 PasswordAuthentication 配置于巨,然后,把yes 改成 no,保存 sshd_config,然后重启 sshd 服务。这里一定要小心,首先,要把所有 PasswordAuthentication 配置语句的 yes 改为 no,然后要真正确保修改得毫无差错,否则 SSHD 会无法启动。Armstrong 先生,我完成了所有修改并重启了 SSHD 服务,现在用秘钥登录,一切成功,我尝试了使用密码登录,被服务器拒绝了。被拒绝了最好,瞧,那些攻击者已经对我们的服务器无可奈何了。
  到这里就可以了吗?Armstrong 先生。不,现在还不行,我从日志里,恰好看到了有人在扫描我们的服务器端口,正好扫描到了 SSH 端口,我不知道他们为什么会来扫描我们的服务器,看来,更换 SSH 端口也只能让那些撒小网捕小鱼的家伙连接不上我们的服务器,但对于那些撒大网捕大鱼的家伙,我们的服务器大概还不够健壮。不够健壮,我们的服务器不是只能通过秘钥进行登录吗?是的,但是,既然那些攻击者可以连接到我们的 SSH 端口,终有一天,我们的一个不小心,把秘钥泄漏出去了,那,我们的好日子也就到头了。放心了 Jackson 先生,我们可以关掉 SSH 端口。Eh,你说,要关闭 SSH 端口?那么,我们怎么连接呢?是这样的,Jackson 先生,我们可以只对自己开放 SSH 端口。你说的,大概是 VPN 吧?不,Jackson 先生,我们通过 VPN 当然可以抵挡那些讨厌的攻击者直接连接到我们的服务器,但是额外启动一个服务,并且暴露到公网上,多少会有一些风险,再说了,你要知道,咱们是在中国,中国当局那些家伙对 VPN 可是非常敏感的,因为他们的人民经常借助这种技术访问那些他们认为非法的网站,如果我们利用这种技术,还可能让服务器引来被屏蔽的风险。Armstrong 先生,我们要怎么让服务器只对我们自己开放 SSH 端口呢?
  Jackson 先生,不知道你有没有仔细读过 Firewalld 的文档?那里面写了高级的用法,我们可以使用 rich-rule 以及 timeout 这两项高级功能,rich-rule 是高级的规则,可以精确匹配源 IP 地址、端口号、协议类型等,而 timeout 是规则的有效时间。也就是说,我们可以配置服务器,让它只接受特定 IP 地址到服务器特定端口的连接,并且还可以限定该规则的有效时间,是这样吗?Armstrong 先生。对的,这是不是很酷呢?这看来会有一些问题啊,我们的防火墙把我们挡在了系统外面,我们被挡在系统外面,怎么给自己开门呢?Oh,这个问题,Jackson 先生你问得挺好。其实,这个问题也很好解决,我们的服务器上不是托管了很多 HTTPS 网站吗?而且通过 Let’s Encryp,我们可以免费而又快速地获取到 HTTPS 证书,然后,我们可以开辟一个新的 HTTPS 站点,在这个新站点中,我们可以放置一个 PHP 脚本,专门用来执行系统命令,这不就可以在防火墙上开通端口了吗?
  这真是太酷了,Armstrong 先生,我要怎么做呢?我不会告诉你怎么做的 Jackson 先生,但是我可以给你思路。我们知道,PHP 有好几个函数可以用来执行系统命令,比如说 exec,比如说 system,又或者 passthru,它们的功能有一些差异,但用来给防火墙添加规则,这三个函数都能胜任。而如果你要建立一个中央控制系统,用来将主机添加到远程服务器上的 SSH 白名单,还可以通过 PHP 的 SSH 扩展来实现,这个扩展现在叫做 ssh2,需要手动编译安装,在此之前,还应该安装 libssh 支持库。PHP 可以定义变量,我们可以把客户端的 IP 地址视为一个变量,作为 Firewalld 防火墙 rich-rule 规则中的源 IP,为了更好的控制防火墙,我们还可以通过变量,为 timeout 设置想要的时长,单位是秒。防火墙的 rich-rule 规则的设置命令是:
firewall-cmd –add-rich-rule=”rule family=”ipv4″ source address=”8.8.8.8″ port protocol=”tcp” port=”51136″ accept” –timeout=3600
至于在 PHP程序中如何写,这个问题就留给 Jackson 先生去思考啦。
  到现在,我们的旅途算即将结束了,Armstrong 还有一些事情想要告诉大家:我非常建议阻止 root 直接登录到 SSH,这将让服务器变得更安全,请在 sshd_config 中,把 PermitRootLogin 后面的配置于巨改为 no。另外,rich-rule 也支持永久开放某个 IP 到某个端口,在受控服务器上,可以借由这个特性来允许从中央服务器的远程连接。
  就这样吧,Jackson 先生,我们一起回去研究如何用 PHP 来控制防火墙,这真是太酷了!

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注