利用SSH隧道建立端口转发或Socks代理

前言

在工作过程中,我们通常需要一些工具来帮助我们建立代理隧道,达到隐藏IP地址,或者穿透防火墙的效果。好用的工具有很多,但最常见的应该还是ssh。

ssh自带三种转发功能,分别为动态转发(Socks代理)、本地端口转发、远程端口转发

环境假设

假设我们有三台主机:

  • Client A:Windows Localhost
  • Tunnel B:Linux ssh 192.168.1.2:22
  • Tunnel C:Linux ssh 192.168.1.3:33

以下所有示例都按照此环境进行说明,我们本地终端就是Client A

动态转发(socks代理)

newbing:SSH的动态端口转发(也称为“SOCKS代理”)允许您通过SSH连接将本地计算机上的指定端口配置为SOCKS代理服务器。当您的应用程序连接到该端口时,SSH客户端会创建到远程服务器的加密隧道,并且所有流经该隧道的流量都将被动态转发到目标主机和端口。

动态端口转发的命令格式为:

1
ssh -Nf -D [bind_address:]port user@remotehost -p 22

其中:

  • -D [bind_address:]port:指定要使用的本地IP地址(可选)和要侦听的端口号。
  • -N:告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发。
  • -f:告诉SSH客户端在后台运行。

bind_address是指定要使用的本地IP地址。它是可选的,如果未指定,通常默认为localhost,但实际默认应根据配置文件中GatewayPorts来。这意味着只有本地计算机上的应用程序才能连接到SOCKS代理服务器。

如果您希望其他计算机也能连接到SOCKS代理服务器,您可以将bind_address设置为您计算机的公共IP地址或0.0.0.0(表示侦听所有网络接口)。

很好理解,就是相当于把远程服务器当做socks服务器,进行代理,以下是一个示例

我们将A与B之间建立动态转发

1
ssh -D 1234 -N -f root@192.168.1.2 -p 22

此时,我们把浏览器挂上代理(socks5://127.0.0.1:1234),最终出网地址为192.168.1.2

在本地终端中运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# rayi @ a in ~ [10:47:35] C:255
$ ssh -Nf -D localhost:1234 root@47.xxx -p 22
The authenticity of host '[47.xxx]:22 ([47.xxx]:22)' can't be established.
ECDSA key fingerprint is SHA256:xxx.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[47.xxx]:22' (ECDSA) to the list of known hosts.
root@47.xxx's password:
Permission denied, please try again.
root@47.xxx's password:
Permission denied, please try again.
root@47.xxx's password:

# rayi @ a in ~ [10:48:14]
$ curl cip.cc
IP : 59.xxx

# rayi @ a in ~ [10:48:17]
$ proxychains curl cip.cc
ProxyChains-3.1 (http://proxychains.sf.net)
|DNS-request| cip.cc
|S-chain|-<>-127.0.0.1:1234-<><>-4.2.2.2:53-<><>-OK
|DNS-response| cip.cc is 124.70.129.64
|S-chain|-<>-127.0.0.1:1234-<><>-124.70.129.64:80-<><>-OK
IP : 47.xxx

本地端口转发

newbing:SSH的本地端口转发允许您通过SSH连接将本地计算机上的指定端口转发到远程服务器上的指定主机和端口。当您的应用程序连接到该端口时,SSH客户端会创建到远程服务器的加密隧道,并且所有流经该隧道的流量都将被转发到目标主机和端口。

本地端口转发的命令格式为:

1
ssh -Nf -L [bind_address:]port:host:hostport user@remotehost

其中:

  • -L [bind_address:]port:host:hostport:指定要使用的本地IP地址(可选)、要侦听的端口号、目标主机和目标端口。
  • -N:告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发。
  • -f:告诉SSH客户端在后台运行。

简单来说,若我们的命令为:

1
ssh -Nf -L [bind_address:]HostAPort:HostC:HostCPort user@HostB

那么,该命令就是将对[bind_address:]APort的访问,通过B,转发成对C:CPort的访问

示例,将本地的1234端口与远程的2333端口绑定

在本地终端中运行:

1
2
3
4
5
6
# rayi @ a in ~ [11:02:44] C:1
$ ssh -Nf -L 1234:47.xxx:23002 root@47.xxx -p 3389
root@47.xxx's password:

# rayi @ a in ~ [11:03:28]
$ curl 127.0.0.1:1234

远程服务器监听,可得到结果

1
2
3
4
5
6
7
8
9
# root @ VM-4-2-ubuntu in ~ [11:03:37]
$ nc -lvp 23002
Listening on 0.0.0.0 23002
Connection received on 47.xxx 46662
GET / HTTP/1.1
Host: 127.0.0.1:1234
User-Agent: curl/7.68.0
Accept: */*

远程端口转发

newbing:SSH的远程端口转发允许您通过SSH连接将远程服务器上的指定端口转发到本地计算机上的指定主机和端口。当远程服务器上的应用程序连接到该端口时,SSH客户端会创建到本地计算机的加密隧道,并且所有流经该隧道的流量都将被转发到目标主机和端口。

远程端口转发的命令格式为:

1
ssh -Nf -R [bind_address:]port:host:hostport  user@remotehost

其中:

  • -R [bind_address:]port:host:hostport:指定要使用的远程IP地址(可选)、要侦听的端口号、目标主机和目标端口。
  • -N:告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发。
  • -f:告诉SSH客户端在后台运行。

这个就跟上面的相反,简单来说,若我们的命令为:

1
ssh -Nf -R [bind_address:]HostBPort:HostC:HostCPort user@HostB

那么,该命令就是将对[bind_address:]BPort的访问,通过A,转发成对C:CPort的访问

示例,在本地终端执行

1
2
3
4
5
6
7
8
9
10
11
12
# rayi @ a in ~ [11:10:17]
$ ssh -Nf -R 1234:127.0.0.1:2333 root@47.xxx -p 3389
root@47.xxx's password:

# rayi @ a in ~ [11:11:53]
$ nc -lvp 2333
Listening on 0.0.0.0 2333
Connection received on localhost 13190
GET / HTTP/1.1
Host: 127.0.0.1:1234
User-Agent: curl/7.81.0
Accept: */*

远程服务器执行

1
2
3
# root @ VM-4-2-ubuntu in ~ [11:05:28] C:130
$ curl http://127.0.0.1:1234

双层/多层代理隧道

利用ssh端口转发功能建立起双层代理隧道,即本机链接A,A转发给B,B访问真正的服务器

您可以使用SSH的端口转发功能来建立双层代理隧道。利用ssh构建多级跳板,就是先用本地转发将远端机器的ssh端口与本地端口做映射,然后再通过本地端口继续与其他远端机器的ssh端口构建映射,直至最后一跳时采用任意动态端口转发(建立socks服务器)。 最后本地使用socks代理工具进行配置,就可以根据需要让应用按照之前构建的隧道进行通信。¹

例如,如果您有两级跳板,系统为linux,实验环境为:

  • Client A:Windows Localhost
  • Tunnel B:Linux ssh 192.168.1.39:22
  • Tunnel C:Linux ssh 192.168.1.42:55

那么第一层转发为: A (localhost:12345)->B (192.168.1.39:22)->C (192.168.1.42:55)。¹

您可以参考这个例子来设置您自己的双层代理隧道。

根据您之前提到的例子,您可以按照以下步骤进行操作:

  1. 在Client A上执行第一层转发命令:

    1
    ssh -Nf -C -L 12345:192.168.1.42:55 rootB@192.168.1.39 -p 22

    这一步是做了本地12345端口到远程C的55端口映射,A与B之间的通信是SSH加密通信。

  2. 在Client A上执行第二层转发命令:

    1
    ssh -Nf -C -D localhost:9833 rootC@localhost -p 12345

    这一步只绑定本地端口(9833),虽然是与本地的12345通信,但实质是通过tunnel1与C建立了动态转发关系。此时C充当Socks代理服务器的角色。

希望这些信息对您有所帮助。

这些命令中的参数分别表示以下含义:

  • -N:告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发。
  • -f:告诉SSH客户端在后台运行。
  • -C:启用压缩数据传输。
  • -L:指定本地端口转发。格式为[bind_address:]port:host:hostport,表示将本地的port端口与远程的host:hostport建立映射关系。
  • -D [bind_address:]port:指定动态应用程序级别的端口转发。格式为[bind_address:]port,表示在本地机器上分配了一个端口进行侦听,一旦该端口有了连接,转发出去的数据由发起的请求来决定交给远端机器的哪个端口。

希望这些信息对您有所帮助。

源: 与必应的对话, 2023/6/18
(1) 利用SSH隧道构建多级Tunnel - 知乎 - 知乎专栏. https://zhuanlan.zhihu.com/p/94624842.
(2) 彻底搞懂SSH端口转发命令 - 知乎 - 知乎专栏. https://zhuanlan.zhihu.com/p/148825449.
(3) 使用SSH协议搭建隧道(附搭建环境超详细) - CSDN博客. https://blog.csdn.net/qq_45300786/article/details/111025981.