我们管理CVM服务器的时候经常需要远程登录服务器。直接Telnet已经比较少人用了,大家比较广泛的使用ssh,再配合上证书或者高强度的密码登录,这样虽然安全了很多,但是把ssh端口暴露在外网仍然会召来黑客的探测和攻击,但是不开放接口的话自己都上不去了?
很多年前看一部流行的网络小说《我是一个黑客》,里面作者介绍了一个黑客们的小技巧:
但是要和其他系统通讯,端口肯定是必须要的。这个有经验的人用端口扫描一扫,一般也能看出多了一个端口。或者系统本来没有开的服务怎么开了等? 对付这种技术,曾经废了我很一段脑筋。 但是最后我还是想出一个办法。成功的解决了这个问题。 其实端口扫描就是和对方建立一个连接,如果连接成功,说明端口开发,否则就是没有开发的。 由于普通的网络程序,采用的都是tcp/ip的标准,所以当然你开了端口,程序都能连接。 但是我的后门,我拦截了连接函数。并且拦截了数据包。如果数据包不是我特殊的数据。我就知道是普通的扫描软件。我就不响应。于是对方就认为没有开发这个端口。 如果是我的程序的话,我是有特殊数据标识的。我的程序就会响应。
类似这样的手段其实在10多年前就已经不只是黑客手段,而成为了一种很好的安全手段了,还有了个很酷的名字: port knocking (介绍 / 中文介绍)。网站上收集了数十个port knocking的实现,其中有像小说一样通过ip包的端口+payload来实现的,更多的是通过依次knock一系列的端口号来实现的。假如每个端口号可以有65536种可能性(16bit),那么连续3个端口号就相当于一把48bit的密钥,已经具备相当的安全性了。
其中的一个port-knocking实现就叫knock(敲门),项目开源在github上 ,需要各种操作系统版本的knock工具可以在knock主页 下载到(注意mac和windows下的knock工具都是命令行工具,不要直接点击运行,要在cmd或者terminal窗口里面执行)。
敲门(knock)指的是我们从自己的客户端设备(pc、笔记本或者手机)向服务器IP发送一系列实现约好的暗号,而服务器上需要相应的安装接收暗号的服务knockd,它在接收到正确的暗号的时候,会临时性的为敲门者开一段时间的门并随后关上(当然也能够配置成一直开着),我们要在这几秒钟里面登录成功并且保持连接,如果不小心断了连接就要重新敲门。
knock动作的实质就是连续的向指定的ip的约定的端口连续的发送多个tcp或者udp包,比如我们可以通过*telnet 服务器地址 端口号* 命令来发送tcp包,也可以直接在浏览器地址栏里面用 http://服务器地址:端口号 的方式来让浏览器发出与服务器指定端口tcp握手的SYN包。但是最好用的还是直接下载knock工具(windows版、mac版),用 knock 服务器地址 端口号 的方式来实现敲门
(这里使用了-v 参数来显示敲门过程)
如果说knock是敲门的来宾,knockd就是应门的门童。inux下的守护进程(Linux Daemon )常用XXXd来命名,比如apache的守护进程httpd,knockd就是knock的守护进程,所以服务器只有安装了knockd,才能正确的相应客户端的knock暗号。
konck项目的主页在 http://www.zeroflux.org/projects/knock 我们可以从官网找到最新版的下载链接。不过为了避免可能遇到的GFW之类的问题,我们下载了一个版本放在腾讯云cos上,所以你可以通过执行下面这个命令,从腾讯云cos上下载knockd到服务器上(以下操作假设你在当前用户的的~目录下操作):
wget http://404-1252074372.cosgz.myqcloud.com/knockd/knock-0.7-1.el7.src.rpm
上一步下载到的是knockd的rpm文件,接下来我们要编译knockd,就需要使用到rpmbuild命令。
rpmbuild --rebuild knock-0.7-1.el7.src.rpm
但是因为centos系统默认不带rpmbuild这个工具的,你会看到一个错误提示,那么我们就需要先用yum命令吧这个工具安装上去。
yum install -y rpm-build
安装好了rpmbuild工具以后,我们再次尝试安装knockd:
rpmbuild --rebuild knock-0.7-1.el7.src.rpm
这次很可能提示变成了了“libpcap-devel is needed”,因为centos默认也不带libpcap-devel这个工具。那么一样的,我们可以用yum命令把它安装好:
yum install -y libpcap-devel
安装好了rpmbuild工具以后,我们再次尝试安装knockd:
rpmbuild --rebuild knock-0.7-1.el7.src.rpm
这次我们会看到一系列的自检,最后出现了这样的一些检测失败:
checking for x86_64-redhat-linux-gnu-gcc... no checking for gcc... no checking for x86_64-redhat-linux-gnu-cc... no checking for cc... no checking for x86_64-redhat-linux-gnu-cl.exe... no checking for cl.exe... no configure: error: in '/root/rpmbuild/BUILD/knock-0.7': configure: error: no acceptable C compiler found in $PATH
这说明,现在系统里面还缺一个C编译器,我们用yum来装一个:
yum install -y gcc
现在,我们终于可以编译 knockd 了:
rpmbuild --rebuild knock-0.7-1.el7.src.rpm
执行以下指令安装knock工具:
rpm -ivh /root/rpmbuild/RPMS/x86_64/knock-*
一般我们会把knockd的日志记录到knockd.log文件中,这一步你可以自己编辑/etc/knockd.conf
文件,把UseSyslog改成 LogFile = /var/log/knockd.log
如果不熟悉vi工具的话也可以直接执行这个命令
sed -i 's/UseSyslog*/LogFile = \\/var\\/log\\/knockd.log/g' /etc/knockd.conf
和上一步一样,你可以在/etc/knockd.conf
中编辑进去自己的敲门暗号,当然也可以直接用默认的暗号,但是因为默认暗号都是一样的,所以太容易被猜到了。
为了方便,这里我们也可以用三行sed命令来完成对暗号的修改
sed -i 's/sequence.*/sequence = 63654:tcp,59472:tcp,31023:tcp/g' /etc/knockd.conf
sed -i 's/tcpflags.*/tcpflags = syn/g' /etc/knockd.conf
sed -i 's/start_command.*/start_command = iptables -I INPUT -s %IP% -p tcp --dport ssh -j ACCEPT/g' /etc/knockd.conf
第一行:在这里我们使用的暗号是:连续通过63654、59472和31023这三个端口通过tcp协议各敲一次门。你也可以编辑自己的暗号。
第二行:我们只接受tcp的syn握手包作为敲门信号。
第三行:我们把原来默认的iptables规则的修改方式从append(附加到最后面)改成insert(插入到最前面)
现在我们可以启动 knockd 服务来侦听我们设置的暗号了,运行一下指令:
/etc/init.d/knockd start
也可以打开knockd计划任务,这样系统重启的时候也能自动启动了
chkconfig knockd on
然后我们可以检查一下knockd是否如期望启动了:
/etc/init.d/knockd status
如果看到Active: active (running)
就说明运行良好,如果看到Active: failed
的话就要检查一下日志文件(在前面的步骤中我们指定了/var/log/knockd.log
文件)的错误记录了。
现在knockd已经开始监听敲门声了,我们首先可以在服务器上就地运行一次敲门:
knock -v ${runtime.vars.cvmIpAddress} 63654 59472 31023
这样会依次用TCP协议从本服务器的公网接口向自己的63654、59472、31023端口发送三个数据包作为敲门暗号。
然后我们检查/var/log/knockd.log
文件。在日志文件中,如果第一个暗号被接受了,会记录下来:
$xxx.xxx.xxx.xxx : opencloseSSH: Stage 1
随后依次接收到后面的暗号会触发 Stage2、Stage3,之后接着会执行/etc/knockd.conf
文件中的[opencloseSSH]
段里面的 start_command
指令,并在日志文件中记录:
running command: /sbin/iptables -A INPUT -s xxx.xxx.xxx.xxx -p tcp --dport ssh -j ACCEPT
这条命令会在本机的iptables中的INPUT链上增加一条规则,允许发出正确敲门暗号的来源IP通过ssh端口访问服务器。
在经过10秒(在配置文件的cmd_timeout中可以修改)后,会接着执行 stop_command 。所以10秒后,再打开日志文件,会多看到两条:
$xxx.xxx.xxx.xxx: opencloseSSH: command timeoutopencloseSSH: running command: /sbin/iptables -D INPUT -s $xxx.xxx.xxx.xxx -p tcp --dport ssh -j ACCEPT
这条命令会把15秒前临时打开的后门又重新关掉。所以我们必须要在敲门成功后的10秒内通过ssh登录上服务器。
你可以在knock的主页的Other Downloads段那里下载到knock工具的各种版本。比如假如你是是windows电脑可以下载Native Win32 Client ,如果是mac当然就下载MacOS Client了。
下载下来的压缩包里面可能有各种源码,不要管它,在window client包里面直接解压出来knock-win32.zip\knock-win32-port\Release\knock.exe
这个文件放到一个控制台容易访问的位置,比如 C:\Windows\System32
下面。 如果是MacOS client的话解压出来就只有一个knock文件,把它放在容易调用的路径(比如~)就可以了。
从安装了knock工具的的台式机或者笔记本上执行:
knock -v xxx.xxx.xxx.xxx 63654 59472 31023
然后再检查确认一次 /var/log/knockd.log
文件中, 是否出现了新的从opencloseSSH: Stage 1
到opencloseSSH: Stage 3
的敲门记录和随后的opencloseSSH: running command
记录。
因为敲门是通过发送ip包实现的(即使我们选择了tcp协议,敲门过程实际上也并不会真的建立任何tcp连接,只是tcp和udp本身成为暗号的一部分而已),所以ip包到达服务器的时间有可能是乱序的,这回导致敲门失败,如果遇到只看到opencloseSSH: Stage 1
或者opencloseSSH: Stage 2
,看不到opencloseSSH: Stage 3
的情况,可以多试几次,或者手工依次执行:
knock -v xxx.xxx.xxx.xxx 63654
knock -v xxx.xxx.xxx.xxx 59472
knock -v xxx.xxx.xxx.xxx 31023
如果仍然不行,有可能部分端口被禁止了,可以换端口试试看;也有可能网络不通(假如服务器开启了icmp协议的话可以tracert / traceroute 看看链路是否有问题)
到现在,敲门策略已经生效,我们可以通过自己定制好的暗号来让knockd执行特定的任务(临时增加一条iptable)了,那现在我们可以披上隐身斗篷了。接下来的操作要非常小心,如果操作错误,有可能这台服务器从此就真的隐身了,我们再也登录不上去了。
首先,非常重要的一条,先执行这个命令:
iptables -I INPUT -p tcp --dport 22 -m state --state ESTABLISHED -j ACCEPT
这个命令会允许已经建立的tcp连接不会被我们下一步增加的规则封堵,这里使用I参数来确保这条规则被放在最上面。为了确保策略生效,我们需要运行这个命令:
iptables -L
如果看到
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh state ESTABLISHED
这个信息,就说明规则添加成功了。
为了防止万一误操作导致服务器穿上隐身衣以后再也找不到了,我们可以先放好一颗后悔药:
echo "iptables -D INPUT -p tcp --dport 22 -j DROP " | at now + 30 minutes
这条计划任务会在30分钟后把我们的下一条指令添加的规则清除掉,你可以通过atq命令来查看它是否成功计划了。
这样如果在后面一切操作都顺利的话,我们可以用
atq
命令查到这个计划任务的编号,然后用
atrm 编号
命令吧这条计划任务删除。
在开始隐身之前,还可以把当前的登录ip加到白名单中以防万一。我们可以从last命令中找到still logged in的登录记录看到自己的登录ip来加到iptables里面,或者也可以用这样一行代码来完成:
last|grep "still"|sed "s/root\s*pts\/0\s*\([0-9.]*\)\s*.*\s*still logged in/iptables -A INPUT -s \1 -p tcp --dport ssh -j ACCEPT/" |sh
如果登录用户不是root的话需要对命令做相应的修改。
一样的,我们需要检查确认生效:
iptables -L
现在我们要在iptables中添加一条规则,除了已经建立好的ssh连接和白名单IP之外,不允许任何人再建立新的ssh连接
iptables -A INPUT -p tcp --dport 22 -j DROP
如果前面的每一步都做对了,那么这一步我们的服务器连接还是持续的,如果万一服务器成功隐身了并且服务器和主机失去联系了了,,如果是你自己购买的主机,你可以通过控制台上的“登录”功能,用vnc登录服务器来删除刚刚添加的规则。或者你也可以等上一步操作埋下的后悔药自动生效,把刚刚添加的规则删除掉。
在vnc控制台上删除指令是
iptables -D INPUT -p tcp --dport 22 -j DROP
现在直接通过我们的笔记本、台式机进行ssh登录的行为已经被iptables规则给drop掉了,所以新建的会话根本登录不上去。以后如果需要登录的话我们可以这样做:
knock -v xxx.xxx.xxx.xxx 63654 59472 31023
ssh root@xxx.xxx.xxx.xxx
或者在putty/secureCRT中登录服务器前先在控制台执行一次:
knock -v xxx.xxx.xxx.xxx 63654 59472 31023
然后马上登录。
至此,我们成功的用knockd工具把随时端口隐藏起来了。如果有需要,我们还过配置 /etc/knockd.conf 文件来隐藏其他端口或者让服务器根据暗号执行其他任务。
有的时候网络不稳定,难以保证敲门序列依次到达服务器,我们可以用sleep命令来控制每个knock包发出的时间,从而尽量确保ip包到达次序:
knockhost=myhostname
./knock $knockhost -v 63654 ;sleep 0.1s;./knock $knockhost -v 59472;sleep 0.1s;./knock $knockhost -v 31023;sleep 0.1s;ssh root@$knockhost
这里的第一行要修改成自己的服务器域名或者ip,后面的每个sleep都是0.1秒,可以根据具体网络情况做调整。
我们前面已经通过 chkconfig knockd on 命令设置了knockd开机启动。但是这样每次服务器重启的时候我们设置的iptables设置都会丢失。为了在重启的过程保存和恢复iptables设置,我们可以在knockd命令中增加相应的操作:
在stop中添加保存设置
iptables-save>/etc/sysconfig/iptables
在start中恢复设置
iptables-restore</etc/sysconfig/iptables
我们可以通过这样一行命令把这两行添加到knockd文件中:
sed -i -e 's/^start() {/start() {\n iptables-restore < \/etc\/sysconfig\/iptables/' -e 's/^stop() {/stop() {\n iptables-save > \/etc\/sysconfig\/iptables/' /etc/init.d/knockd
这样应该算是标准做法,但是这样做有一个风险就是,如果一个人刚好knock进来,iptables中刚好保存了他的白名单,那么这个白名单就会被永久保存下去了。如果要回避这个风险,也可以直接只在start中添加我们的两条iptables规则,像这样:
sed -i 's/^start() {/start() {\n iptables \-I INPUT \-p tcp \-\-dport 22 \-m state \-\-state ESTABLISHED \-j ACCEPT\n iptables \-A INPUT \-p tcp \-\-dport 22 \-j DROP/' /etc/init.d/knockd
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。