SSH端口转发与内网穿透

如何在没有路由器端口映射的情况下实现从外网访问内网服务器?如何实现穿墙访问内网无法访问的ftp资源?实现的方法有很多,其中一种有效的方案就是使用ssh的端口转发。

一、快速实现

1.1 反向ssh(无端口映射访问)

要求:

1. 一台具有公网IP的vps(A),ipv4为(aaa.aaa.aaa.aaa),用户名为unameA
2. 能连上公网vps的服务器(B),用户名为unameB

服务器B上能够非常轻松的访问A,故可以在B的终端上输入:

$ ssh -N -f -R 2222:127.0.0.1:22 unameA@aaa.aaa.aaa.aaa

然后就可以在A的终端上输入:

$ ssh -p 2222 unameB@localhost

即可以反向从vpsB登陆服务器

1.2 转发ftp端口数据

要求:

1. 公网IP的vps(A),ipv4为(aaa.aaa.aaa.aaa),用户名为unameA
2. 内网服务器(B)
2. 具有ftp资源的服务器(C),ipv4为(ccc.ccc.ccc.ccc),ftp端口为21

由于内网服务器被防火墙的限制,无法直接访问C,但是可以借助A进行转发。在内网服务器(B)上输入:

$ ssh -N -f -L 2121:ccc.ccc.ccc.ccc:21 unameA@aaa.aaa.aaa.aaa

这样就可以访问本地的2121端口来访问C的ftp资源

$ ftp localhost:2121

二、概述

让我们先来了解一下端口转发的概念吧。我们知道,SSH 会自动加密和解密所有 SSH客户端与服务端之间的网络数据。但是,SSH 还同时提供了一个非常有用的功能,这就是端口转发。它能够将其他 TCP 端口的网络数据通过 SSH 链接来转发,并且自动提供了相应的加密及解密服务。

这一过程有时也被叫做“隧道”(tunneling),这是因为 SSH为其他 TCP 链接提供了一个安全的通道来进行传输而得名。例如,Telnet,SMTP,LDAP 这些 TCP 应用均能够从中得益,避免了用户名,密码以及隐私信息的明文传输。

而与此同时,如果您工作环境中的防火墙限制了一些网络端口的使用,但是允许 SSH 的连接,那么也是能够通过将 TCP 端口转发来使用 SSH 进行通讯。总的来说 SSH 端口转发能够提供两大功能1:

  1. 加密 SSH Client 端至 SSH Server 端之间的通讯数据。
  2. 突破防火墙的限制完成一些之前无法建立的 TCP 连接。

三、本地转发与远程转发

3.1 本地转发

我们先来看第一个例子,在实验室里有一台 LDAP 服务器(LdapServerHost),但是限制了只有本机上部署的应用才能直接连接此 LDAP 服务器。如果我们由于调试或者测试的需要想临时从远程机器(LdapClientHost)直接连接到这个 LDAP 服务器 , 有什么方法能够实现呢?

答案无疑是本地端口转发了,它的命令格式是:

$ ssh -L <local port>:<remote host>:<remote port> <SSH hostname>

在 LdapClientHost 上执行如下命令即可建立一个 SSH 的本地端口转发,例如:

$ ssh -L 7001:localhost:389 LdapServerHost

这里需要注意的是本例中我们选择了 7001 端口作为本地的监听端口,在选择端口号时要注意非管理员帐号是无权绑定 1-1023 端口的,所以一般是选用一个 1024-65535 之间的并且尚未使用的端口号即可。

然后我们可以将远程机器(LdapClientHost)上的应用直接配置到本机的 7001 端口上(而不是 LDAP 服务器的 389 端口上)。之后的数据流将会是下面这个样子:

  • 我们在 LdapClientHost 上的应用将数据发送到本机的 7001 端口上,
  • 而本机的 SSH Client 会将 7001 端口收到的数据加密并转发到 LdapServertHost的SSH Server 上。
  • SSH Server 会解密收到的数据并将之转发到监听的 LDAP 389 端口上,
  • 最后再将从 LDAP 返回的数据原路返回以完成整个流程。

我们可以看到,这整个流程应用并没有直接连接 LDAP 服务器,而是连接到了本地的一个监听端口,但是 SSH 端口转发完成了剩下的所有事情,加密,转发,解密,通讯。
这里有几个地方需要注意:

  1. SSH 端口转发是通过 SSH 连接建立起来的,我们必须保持这个 SSH 连接以使端口转发保持生效。一旦关闭了此连接,相应的端口转发也会随之关闭。
  2. 我们只能在建立 SSH 连接的同时创建端口转发,而不能给一个已经存在的 SSH连接增加端口转发。
  3. 你可能会疑惑上面命令中的 为什么用 localhost,它指向的是哪台机器呢?在本例中,它指向 LdapServertHost 。我们为什么用 localhost 而不是 IP地址或者主机名呢?其实这个取决于我们之前是如何限制 LDAP 只有本机才能访问。如果只允许 lookback 接口访问的话,那么自然就只有 localhost 或者 IP 为127.0.0.1 才能访问了,而不能用真实 IP 或者主机名。
  4. 命令中的必须是同一台机器么?其实是不一定的,它们可以是两台不同的机器。我们在后面的例子里会详细阐述这点。
  5. 好了,我们已经在 LdapClientHost 建立了端口转发,那么这个端口转发可以被其他机器使用么?比如能否新增加一台 LdapClientHost2 来直接连接LdapClientHost 的 7001 端口?答案是不行的,在主流 SSH 实现中,本地端口转发绑定的是 lookback 接口,这意味着只有 localhost 或者 127.0.0.1 才能使用本机的端口转发 , 其他机器发起的连接只会得到“connection refused.”。好在 SSH 同时提供了 GatewayPorts 关键字,我们可以通过指定它与其他机器共享这个本地端口转发。
$ ssh -g -L <local port>:<remote host>:<remote port> <SSH hostname>

3.2 远程转发

我们来看第二个例子,这次假设由于网络或防火墙的原因我们不能用 SSH 直接从LdapClientHost 连接到 LDAP 服务器(LdapServertHost),但是反向连接却是被允许的。那此时我们的选择自然就是远程端口转发了。
它的命令格式是:

ssh -R <local port>:<remote host>:<remote port> <SSH hostname>

例如在 LDAP 服务器(LdapServertHost)端执行如下命令:

$ ssh -R 7001:localhost:389 LdapClientHost

和本地端口转发相比,这次的图里,SSH Server 和 SSH Client 的位置对调了一下,但是数据流依然是一样的。我们在 LdapClientHost 上的应用将数据发送到本机的 7001 端口上,而本机的 SSH Server 会将 7001 端口收到的数据加密并转发到LdapServertHost 的 SSH Client 上。 SSH Client 会解密收到的数据并将之转发到监听的 LDAP 389 端口上,最后再将从 LDAP 返回的数据原路返回以完成整个流程。

看到这里,你是不是会有点糊涂了么?为什么叫本地转发,而有时又叫远程转发?这两者有什么区别?

3.3 本地转发与远程转发的对比与分析

不错,SSH Server,SSH Client,LdapServertHost,LdapClientHost,本地转发,远程转发,这么多的名词的确容易让人糊涂。让我们来分析一下其中的结构吧。首先,SSH 端口转发自然需要 SSH 连接,而 SSH 连接是有方向的,从 SSH Client 到SSH Server 。而我们的应用也是有方向的,比如需要连接 LDAP Server 时,LDAP Server 自然就是 Server 端,我们应用连接的方向也是从应用的 Client 端连接到应用的Server端。如果这两个连接的方向一致,那我们就说它是本地转发。而如果两个方向不一致,我们就说它是远程转发。

我们可以回忆上面的两个例子来做个对照。

  1. 本地转发时:
    LdapClientHost 同时是应用的客户端,也是 SSH Client,这两个连接都从它指向LdapServertHost(既是 LDAP 服务端,也是 SSH Server)。
  2. 远程转发时:
    LdapClientHost 是应用的客户端,但却是 SSH Server ;而 LdapServertHost 是 LDAP的服务端,但却是 SSH Client 。这样两个连接的方向刚好相反。

另一个方便记忆的方法是,Server 端的端口都是预定义的固定端口(SSH Server 的端口 22,LDAP 的端口 389),而 Client 端的端口都是动态可供我们选择的端口(如上述例子中选用的 7001 端口)。如果 Server 端的两个端口都在同一台机器,Client 端的两个端口都在另一台机器上,那么这就是本地连接;如果这四个端口交叉分布在机器上,每台机器各有一个 Server 端端口,一个 Client 端端口,那就是远程连接。

四、反向直连

在完成上述反向ssh连接之后,你将会发现,需要登陆两次才能实现家庭电脑到内网服务器的连接:首先登陆到中间服务器,然后再从中间服务器登陆到内网服务器。但是实际上,有一种方法可以实现只需要一次登录就能够直接到达内网服务器2

为了达到这种效果,需要中继服务器的sshd不仅转发不仅转发回环地址上的端口,还要转发外部主机的端口。这通过指定中继服务器上运行的 sshd 的 GatewayPorts 实现。

在中继服务器上配置/etc/ssh/ssh_config,添加

    GatewayPorts clientspecified

然后重启sshd服务。
之后再在内网服务器上初始化反向SSH隧道:

$ ssh -fN -R bbb.bbb.bbb.bbb:10022:localhost:22 unameB@bbb.bbb.bbb.bbb

在这种情况下,ssh就直接将中继服务器的10022端口内网服务器相连,而10022端口是公网端口,而非127.0.0.1这种只有本机才能访问的端口。所以家里任意一台电脑即可以使用:

$ ssh -p 10022 unameA@bbb.bbb.bbb.bbb

来访问内网服务器(注意用户名是内网服务器的,但是ip却是中继服务器的)

五、永久反向ssh

现在你已经明白了怎样创建一个反向 SSH 隧道,然后把隧道设置为 “永久”,这样隧道启动后就会一直运行(不管临时的网络拥塞、SSH 超时、中继主机重启,等等)。毕竟,如果隧道不是一直有效,你就不能可靠的登录到你的家庭服务器。

对于永久隧道,我打算使用一个叫 autossh 的工具。正如名字暗示的,这个程序可以让你的 SSH
会话无论因为什么原因中断都会自动重连。因此对于保持一个反向 SSH 隧道非常有用。

1.我们要设置从内网服务器到中继服务器的 无密码 SSH 登录 。这样的话,autossh 可以
不需要用户干预就能重启一个损坏的反向 SSH 隧道。
2.安装autossh并创建一个连接到中继服务器的永久ssh隧道

$ autossh -M 10900 -fN -o "PubkeyAuthentication=yes" -o
"StrictHostKeyChecking=false" -o "PasswordAuthentication=no" -o
"ServerAliveInterval 60" -o "ServerAliveCountMax 3" -R
1.1.1.1:10022:localhost:22 relayserver_user@1.1.1.1
其中:
    -M 选项指定中继服务器上的监视端口
    -o xxx  -使用密钥验证,而不是密码验证
            -自动接受(未知)SSH 主机密钥
            -每 60 秒交换 keep-alive 消息。
            -没有收到任何响应时最多发送 3 条 keep-alive 消息。

六、小结

本文总结了如何ssh的反向隧道进行穿墙,但在企业网络中使用时你尤其要小心。这样的一个隧道可能被视为违反公司政策,因为它绕过了企业的防火墙并把企业网络暴露给外部攻击。这很可能被误用或者滥用。因此在使用之前一定要记住它的作用。

reference


  1. Billow.SSH端口转发.2017/04.http://www.cnblogs.com/angelfzt/p/3600650.html 
  2. Dan Nanni.如何通过反向SSH隧道访问NAT后面的Linux服务器.https://linux.cn/article-5975-1.html 
此条目发表在技术交流, 网络建站分类目录,贴了标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。