已复制
全屏展示
复制代码

SSH隧道实现内网穿透原理与实战


· 8 min read

一. 管道和隧道

1.1 什么是管道

下面是 ssh 最简单的一个连接命令,方括号内的内容可以省略,尖括号内的内容是必选参数。

ssh [-p <onPort>] [<user>@] <connectToHost>

这条命令表示,自执行命令的本机,向 connectToHostonPort(默认 22 端口) 端口发起请求,尝试以 user 身份登录。在上述 ssh 命令执行成功之后,我们就建立了从本机到 connectToHost 的连接。具体来说,本机的 SSH clientconnectToHostSSH 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 负责发往 forwardToHostforwardToPort 端口。

此处有两点值得注意。其一,bindHost 省略时或置为 * 时,表示本机将转发所有主机发往 sourcePort 的消息。其二,forwardToHost 是站在 connectToHost 的视角看待的,因此若 forwardToHost 的值是 localhost,则在此语境下表示 connectToHost 这台主机自己。 注意bindHostsourcePort 表示本机监听hostport

  • -R 选项 (起点是远程)

ssh -R [<bindHost>:]<sourcePort>:<forwardToHost>:<forwardToPort> <connectToHost>

这条命令表示,本机的 SSH 客户端将与 connectToHost 建立连接(管道),并在管道内建立从 connectToHost 到本机的隧道。此后,connectToHost 将把所有来自 bindHost 发往 connectToHostsourcePort 端口的消息,通过上述隧道转交给本机,并由本机负责发往 forwardToHostforwardToPort 端口。 注意bindHostsourcePort 表示 connectToHost 监听hostport

四. 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_AHOST_B发起SSH连接,建立了一个管道。而后,在管道内建立了一个从HOST_BHOST_A的反向隧道。HOST_B会将所有的7000端口的流量,经由上述隧道转交给HOST_A,而后转发给HOST_A22端口。注意:这里的HOST_B大多是外网服务器,防火墙需要允许端口7000通行。


ssh -R :7000:localhost:22 -CNT -o ServerAliveInterval=30 -o ServerAliveCountMax=10 USER_B@HOST_B

步骤二

HOST_C上执行如下命令,表示通过HOST_B7000端口,转发到HOST_A22端口,所以是USER_A@HOST_B的连接方式。


ssh -p 7000 USER_A@HOST_B

5.2 SSH隧道穿透防火墙

场景再现

  • 现有HOST_AHOST_BHOST_B想访问HOST_A上的WEB服务8080端口。
  • 当主机HOST_A只开放了SSH的端口,但是由于防火墙其他端口都不允许访问,此时在HOST_B上想访问HOST_A上的8080端口。

解决方法

此时在主机HOST_B可以通过-L参数实现。在HOST_B上执如下命令,表示访HOST_B上的6080端口时,HOST_B会通过隧道将请求数据发送到HOST_A8080端口上。


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

文章推荐