SSH隧道实现内网穿透原理与实战
一. 管道和隧道
1.1 什么是管道
下面是 ssh 最简单的一个连接命令,方括号内的内容可以省略,尖括号内的内容是必选参数。
ssh [-p <onPort>] [<user>@] <connectToHost>
这条命令表示,自执行命令的本机,向 connectToHost
的 onPort
(默认 22
端口) 端口发起请求,尝试以 user
身份登录。在上述 ssh
命令执行成功之后,我们就建立了从本机到 connectToHost
的连接。具体来说,本机的 SSH client
与 connectToHost
的 SSH server
建立了连接。我们可以将这一连接想像成一个有方向的管道;它的起点是本机的某个端口,而终点是 connectToHost
上的 onPort
端口。
1.2 什么是隧道
ssh
的 -L
和 -R
选项,允许用户在上述管道内部,再创建一个有向隧道。可以将其想像为外部的大号管道套住了内部的小号隧道。隧道的两端与管道的两端相同,起点则由 -Local/-Remote
决定。
- 使用
-L
选项时,本机的某个端口(不是onPort
)是起点,这种隧道称之为正向隧道; - 使用
-R
选项时,connectToHost
的某个端口(不是onPort
)是起点,这种隧道称之为反向隧道。
二. 配置ssh server转发
所有的隧道转发前提是 connectToHost
上的 ssh server 允许转发,必须要在中转机上配置 /etc/ssh/sshd_config
允许端口转发。
GatewayPorts yes
三. 通过隧道实现转发
下面来看看实际的使用参数
-L
选项 (起点是本机)
ssh -L [<bindHost>:]<sourcePort>:<forwardToHost>:<forwardToPort> <connectToHost>
这条命令表示,本机的 SSH
客户端将与 connectToHost
建立连接(管道),并在管道内建立从本机到 connectToHost
的隧道。此后,本机将把所有来自 bindHost
发往本机的 sourcePort
端口的消息,通过上述隧道转交给 connectToHost
,并由 connectToHost
负责发往 forwardToHost
的 forwardToPort
端口。
此处有两点值得注意。其一,bindHost
省略时或置为 *
时,表示本机将转发所有主机发往 sourcePort
的消息。其二,forwardToHost
是站在 connectToHost
的视角看待的,因此若 forwardToHost
的值是 localhost
,则在此语境下表示 connectToHost
这台主机自己。 注意:bindHost
和 sourcePort
表示本机监听host
和port
-R
选项 (起点是远程)
ssh -R [<bindHost>:]<sourcePort>:<forwardToHost>:<forwardToPort> <connectToHost>
这条命令表示,本机的 SSH
客户端将与 connectToHost
建立连接(管道),并在管道内建立从 connectToHost
到本机的隧道。此后,connectToHost
将把所有来自 bindHost
发往 connectToHost
的 sourcePort
端口的消息,通过上述隧道转交给本机,并由本机负责发往 forwardToHost
的 forwardToPort
端口。 注意:bindHost
和 sourcePort
表示 connectToHost
监听host
和port
四. SSH隧道使用参数
隧道需要的到参数,通常情况下,这几个参数就足够使用了。
-f
:使 SSH 在建立连接之后保持在后台运行。-C
:允许SSH
压缩数据。-N
:告诉SSH
我们只希望建立隧道,而不会在远程主机上执行任何指令。-T
:告诉SSH
我们只希望建立隧道,而不需要创建虚拟终端。-fCNT
可以谐音记忆force connect
-o ServerAliveInterval=30
:每30秒向服务器发送一个空包-o ServerAliveCountMax=10
:如果超过10次没成功就断开
五. SSH隧道实战场景
5.1 内网穿透SSH连接
场景再现
HOST_A
:目标机器,内网机器,可以访问外网,但无法从外网访问,已创建USER_A
用户。HOST_B
:跳板机器,与HOST_A
在同一个内网,公网机器,可以访问外网,也可以被外网访问,已创建USER_B
用户。HOST_C
:任一台可以访问外网的机器。
希望效果
希望能在HOST_C
上能够随时随地以USER_A
用户登录HOST_A
。
分析方法
我们可以在HOST_A
上向跳板机HOST_B
发起SSH
连接,并通过反向隧道(-R
)将流量返回HOST_A
。此时在SSH
管道内的隧道起点是跳板机HOST_B
,此时连接跳板机隧道开启的新端口即可连接到内网机器HOST_A
。实现方式有很多种,这里只介绍一种。
步骤一
HOST_A
上执行如下命令,此时,HOST_A
向HOST_B
发起SSH
连接,建立了一个管道。而后,在管道内建立了一个从HOST_B
到HOST_A
的反向隧道。HOST_B
会将所有的7000
端口的流量,经由上述隧道转交给HOST_A
,而后转发给HOST_A
的22
端口。注意:这里的HOST_B
大多是外网服务器,防火墙需要允许端口7000
通行。
ssh -R :7000:localhost:22 -CNT -o ServerAliveInterval=30 -o ServerAliveCountMax=10 USER_B@HOST_B
步骤二
HOST_C
上执行如下命令,表示通过HOST_B
的7000
端口,转发到HOST_A
的22
端口,所以是USER_A@HOST_B
的连接方式。
ssh -p 7000 USER_A@HOST_B
5.2 SSH隧道穿透防火墙
场景再现
- 现有
HOST_A
、HOST_B
,HOST_B
想访问HOST_A
上的WEB服务8080端口。 - 当主机
HOST_A
只开放了SSH
的端口,但是由于防火墙其他端口都不允许访问,此时在HOST_B
上想访问HOST_A
上的8080
端口。
解决方法
此时在主机HOST_B
可以通过-L
参数实现。在HOST_B
上执如下命令,表示访HOST_B
上的6080
端口时,HOST_B
会通过隧道将请求数据发送到HOST_A
的8080
端口上。
ssh -L :6080:localhost:8080 -CNT -o ServerAliveInterval=30 -o ServerAliveCountMax=10 USER_A@HOST_A
其实 5.1 里面的也可以用这种方法,这种方法更方便一些。
六. 网络 SOCKS5 动态代理1
场景再现
我现在的本地主机不能访问外网,但是和我在同一个内网的主机 HOST_V 能访问外网,我想通过 HOST_V 访问外网。
解决办法
运行下面命令,ssh会在本机启动监听socks5协议的1080端口,通过 ssh 的 -D
参数,将访问本地 1080 的所有连接,都让HOST_V帮忙请求。
ssh -D :1080 -CNT -o ServerAliveInterval=30 -o ServerAliveCountMax=10 USER_V@HOST_V
运行上面命令后还不能里面实现上面的功能,还需要额外的操作
- 如果是浏览器:需要在
chrome
中使用switchOmega
插件,创建socks5代理访问本机1080端口 - 如果是命令行:可以在命令行配置proxy
export https_proxy=http://127.0.0.1:1080
export http_proxy=http://127.0.0.1:1080
export all_proxy=socks5://127.0.0.1:1080
七. 网络 SOCKS5 动态代理2
假设我现在的本地主机不能通过 ssh 登录 HOST_V,也就是不能通过上面的方式上网,但是 HOST_V 开放了一个端口比如 1920,我们可以直接在 HOST_V 上开启代理
# 在 HOST_V 上启动 socks 端口 1920
# 即登录localhost,同时开启动态转发 socks 隧道
ssh -D :1920 -CNT -o ServerAliveInterval=30 -o ServerAliveCountMax=10 USER_V@localhost
然后直接在 我在主机上连接 socks5://HOST_V:1920 即可
八. 隧道稳定加速
前文描述的方法建立的隧道可能不稳定,导致连接不可用,此时autossh
的出现可以解决这个问题,autossh
是一个用来启动ssh
并进行监控的程序,可在需要时重启ssh
。
autossh
只是将ssh
包装了一层,然后添加了一些新功能,所以传递给autossh
的有关ssh
的参数都会传递给ssh
,所以在前面的基础上只需要将ssh
命令换成autossh
,同时添加-M
选项,它表示在隧道的起点添加监视端口,比如20000
,它会在建好的隧道中往20000
端口发送数据,在隧道的终点的20000
上接收数据后回传,然后在本机的20001
上再次接收数据,以作为心跳检测的依据,如果检测失败,则会重启ssh
隧道。
下面的命令执行后直接后台运行,所以,如果没有配置秘钥登录HOST_V
,则会失败。切记配置秘钥登录。
autossh -D :1080 -CNT -M 20000 -o ServerAliveInterval=30 -o ServerAliveCountMax=10 userV@HOST_V
九. SSH隧道总结
把上文的所有内容总结成三个示例,方便直接拷贝使用。
# -D 表示使用dynamic方式转发socks5
# -R 表示隧道的起点在remote
# -L 表示隧道的起点在local
# 注意:必须要在中转机上配置GatewayPorts yes (/etc/ssh/sshd_config 配置文件)
# 访问本机 1920 端口的socks5代理时,请求全部网络由 192.165.100.15 转发
ssh -D :1920 -CNT -o ServerAliveInterval=60 -o ServerAliveCountMax=10 root@192.165.100.15
# 访问 201.85.218.216 的 7000 端口,相当于访问了 192.168.100.29 的 22 端口
ssh -R :7000:192.168.100.29:22 -CNT -o ServerAliveInterval=60 -o ServerAliveCountMax=10 root@201.85.218.216
# 访问本机的 8080,相当于访问 201.85.218.216 的 8080
ssh -L :8080:localhost:8080 -CNT -o ServerAliveInterval=60 -o ServerAliveCountMax=10 root@201.85.218.216