SSH端口转发
SSH Port Forwarding
什么是SSH端口转发?
SSH端口转发也被称为SSH隧道,我们知道SSH是加密通讯的,所以通过SSH建立的隧道传输的内容是安全的。它通常被用来绕过防火墙等设备穿透到内网,或用于保护TCP连接等。
SSH端口转发分为以下三种:
- 本地转发
- 远程转发
- 动态转发
什么时候需要使用SSH 端口转发?
让我们思考以下的情况,当我们使用WorkStation SSH连接到SQL Server时,我们通过SSH发送的数据是加密的,但我们通过Mysql连接到SQL Server时,它的连接则是不会被加密的,如果这是一条跨越公网的连接,这将是十分危险的。
但如果我们时候SSH端口转发,我们则能够在过程中通过SSH进行加密,能够保护我们数据的安全。
那么我们应该使用那种SSH端口转发方式呢?
本地转发
本地转发——表示本地某个端口的数据通信会被转发到目标主机的特定端口,注意,我们把执行本地转发命令的设备称为本地主机。
在上面讲的情况,我们就可以使用本地转发的方式。在设置完成本地转发的方式之后,我们就能够通过本地与目标主机建立的SSH隧道进行传输数据,这样可以将我们传输的数据进行加密。
让我们看看下面这个实例:
我们通过curl命令行工具访问Web服务器。
[root@localhost ~]# curl -I 192.168.222.128
HTTP/1.1 403 Forbidden
Date: Wed, 23 Nov 2022 20:04:36 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
通过上述内容,我们可以通过WireShark抓取到明文的HTTP包。
那么我们接下来设置SSH端口转发。
[root@localhost ~]# ssh -L 80:192.168.222.128:80 192.168.222.128
The authenticity of host '192.168.222.128 (192.168.222.128)' can't be established.
ECDSA key fingerprint is SHA256:gfElOdquMLiDDsfg0TQG//KU+uahlfzSjb23pQlbSxk.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.222.128' (ECDSA) to the list of known hosts.
root@192.168.222.128's password:
Last login: Thu Nov 24 04:01:51 2022 from 192.168.222.1
值得注意的是,这里我们设置了SSH端口转发,输入完相应的密码后,我们将会生成一个新的SSH会话连接到远程主机。
关于设置隧道的具体参数,我们在端末会提到。
[root@localhost ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:80 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 ::1:80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
从上可见,我们在本地监听了一个80端口(此时使用的是刚刚执行端口转发命令的机器)。我们再次尝试访问Web服务器。
[root@localhost ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Wed, 23 Nov 2022 20:12:06 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
我们再次查看产生的流量。
那么此时的流量则是经过SSH加密后的流量了。
那么如何不产生新的SSH会话,单独建立SSH隧道呢?我们可以使用 -N 参数,这个选项将会帮助我们仅建立SSH隧道而不会打开远程Shell连接到主机。
但是光使用这个 -N 参数也是不够的(具体为什么不够,可以自己尝试一下),我们还需要使用 -f 参数,让这个隧道在后台运行。
那么我们目前的命令就是 ssh -f -N -L 80:192.168.222.128:80 192.168.222.128
但是这也是有弊端的,这条命令仅仅只会监听在 127.0.0.1 这个本地环回地址上。如果想要监听在一个本地地址上我们可以写成下面这种形式。
ssh -f -N -L 192.168.222.129:80:192.168.222.128:80 192.168.222.128 像这样我们就能够将80端口监听在自己所需要的地址上。
如果我们需要设置端口监听在所有地址上,我们可以使用 -g 参数——开启网关模式,这样它就能够监听在所有的地址上了。
远程转发
介绍完本地转发后,我们再来思考一下远程转发,为了方便理解,我们来描述一个场景。
在上述这个场景中,Web服务器由于防火墙的原因不能正常的通过外部进行访问,我们能够控制内部Web服务器与外部服务器,但却没有防火墙的操作权限。
此时我们就可以使用远程转发,来绕过防火墙的限制,穿透到内网访问内网的资源。
我们来通过实际实验介绍远程转发。
远程&动态转发实验
实验环境
我们是用CentOS 8 作为服务器以及防火墙,通过设置三台主机构建网络环境,通过模拟防火墙分割网络,具体拓扑如下图。
环境部署
FW
首先我们要设置Linux的全局转发功能,使得Linux能够转发不同网段之间的数据。
[root@localhost ~]# vim /etc/sysctl.conf
在其中添加如下内容。
net.ipv4.ip_forward = 1
然后执行命令。
[root@localhost ~]# sysctl -p
net.ipv4.ip_forward = 1
其次,设置 CentOS 8 自带的 Firewalld,我们需要将 FW 连接到 OutSide 设备的网段的网卡设置到 external 区域( external 区域自动开启 masquerade 功能 )。
默认网卡都在 Public 区域中,我们应该先把网卡从 Public 区域中删除,然后将网卡添加到 external 区域中。
[root@localhost ~]# firewall-cmd --remove-interface=ens160 --zone=public
success
[root@localhost ~]# firewall-cmd --add-interface=ens160 --zone=external
success
下述内容均为操作 Inside 主机。
Inside
然后在Web服务器Ping OutSide 主机,理论上是能够通信的。
[root@localhost ~]# ping outside
PING outside (192.168.244.130) 56(84) bytes of data.
64 bytes from outside (192.168.244.130): icmp_seq=1 ttl=63 time=2.49 ms
64 bytes from outside (192.168.244.130): icmp_seq=2 ttl=63 time=0.890 ms
64 bytes from outside (192.168.244.130): icmp_seq=3 ttl=63 time=0.854 ms
64 bytes from outside (192.168.244.130): icmp_seq=4 ttl=63 time=1.02 ms
那么基础网络已经构建完毕了。我们给 Inside 服务器安装 Web 功能,设置防火墙放行指定服务。
[root@localhost ~]# yum install -y httpd
[root@localhost ~]# systemctl start httpd && systemctl enable httpd
[root@localhost ~]# firewall-cmd --add-service=http --per && firewall-cmd --reload
[root@localhost ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Fri, 25 Nov 2022 20:57:20 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
# 能有如上显示则成功配置
以上就是基础环境的配置,下面开始远程转发的配置。
远程转发实验
我们可以从拓扑得知,FW 隔断开了两个网段,由于 FW 的 Masquerade 功能,Inside 可以访问 OutSide,而 OutSide 不能访问 Inside。如果此时 Inside 想让 OutSide 能够访问自己的Web服务就可以通过远程转发功能。
[root@localhost ~]# ssh -f -N -R 80:192.168.222.128:80 192.168.244.130
root@192.168.244.130's password:
此时我们可以在OutSide机器上看到相应端口的建立。
[root@OutSide ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:80 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp6 0 0 ::1:80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
我们在OutSide机器上访问本地的80端口。
[root@OutSide ~]# curl -I localhost
HTTP/1.1 403 Forbidden
Date: Fri, 25 Nov 2022 22:01:18 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
至此,我们的远程端口转发也搭建成功。
具体的流量还是会经过 FW ,但是 FW 放行 SSH 端口,数据经过SSH的加密将会穿过FW到达内网,从而能够访问Inside。
动态转发&实验
那么我们都有了远程转发和本地转发,为什么还需要动态转发呢?
那么我们思考这样一个问题,如果我们本地转发的目的端口为443,对端是一个HTTPS服务,在我们通过浏览器访问时,由于本地监听的地址为localhost,则会出现证书无法被验证的问题。还有假设这个Web服务器将我们重定向至另一个URL,很有可能将连接失败,例如在使用单点登录时(SSO),这种情况很可能出现问题。
使用动态转发则能够解决这个问题,动态转发的实现是SSH通过在本地建立Socks代理,然后通过SSH转发到远程主机,然后远程主机再将SSH内的数据包转发至内网主机。
在如上的拓扑环境中,OutSide会首先监听本地端口用作Socks代理,对于传输到Socks代理的数据,将会经过SSH的加密传输到FW,然后FW再次转发至内部Web服务器。
我们在OutSide主机上进行操作。
[root@Outside ~]# ssh -f -N -D 1080 192.168.244.129
root@192.168.244.129's password:
[root@Outside ~]# netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:1080 0.0.0.0:* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 ::1:1080 :::* LISTEN
一般Socks代理都会监听在 TCP/1080 上,不过在上述命令行命令中,你可以任意指定,但是使用的端口要和设置的相匹配。
我们在输入正确的远程主机SSH密码后,则能够建立SSH隧道。然后我们使用Curl的命令行工具进行测试。
[root@Outside ~]# curl -I --socks5 127.0.0.1:1080 192.168.222.128
HTTP/1.1 403 Forbidden
Date: Sat, 26 Nov 2022 00:08:51 GMT
Server: Apache/2.4.37 (centos)
Content-Location: index.html.zh-CN
Vary: negotiate,accept-language
TCN: choice
Last-Modified: Fri, 14 Jun 2019 03:37:43 GMT
ETag: "fa6-58b405e7d6fc0;5ee25c70d2749"
Accept-Ranges: bytes
Content-Length: 4006
Content-Type: text/html; charset=UTF-8
Content-Language: zh-cn
可以看到,我们虽然指定的是内网地址,但仍能通过Socks代理成功访问到了Web服务器。
接下来我们利用WireShark查看实际的情况。首先我们来分析建立隧道时候的数据包。
这里与SSH打开Shell所建立的过程类似。接下来看看通过Socks代理转发HTTP请求的情况。
从上图可以得知,由于Socks地址监听在本地并不会在虚拟网卡上产生数据,我们只能抓到通过隧道传输过的HTTP请求数据。那么我们如何抓取本地网卡上的Socks包呢?
我们通过tcpdump工具监听数据。
[root@Outside ~]# tcpdump -i lo dst port 1080 -vv > test.pcap
我们将 test.pcap 文件传入主机,用WireShark打开分析。我们可以看到,这里显示出的内容证明了SSH动态转发是通过Socks实现的,而且数据包为明文。
接下来关注 FW 到 Inside 服务器的传输情况。
可以看到,FW承担了一个代理的作用,在内网与外网间转发流量,而且仅仅使用了SSH所需要的端口。
只要应用程序能够通过Socks代理,动态转发理论上可以转发所有的流量,就不用设置转发目的端口(本地)这一麻烦的步骤了。