实验环境
我使用的是 Ubuntu 24.04.4,基本环境配置见ARP攻击原理学习
网络结构如下:
| 节点 | IP 地址 | 作用 |
|---|---|---|
| A 区主机 1 | 10.8.0.5 |
用来测试从 A 区访问 B 区服务 |
| A 区主机 2 | 10.8.0.6 |
辅助测试 |
| A 区网关 | 10.8.0.99 |
A 区出口,也是 SSH 隧道的一端 |
| B 区服务器 1 | 192.168.20.5 |
B 区目标服务器,提供 Telnet 服务 |
| B 区服务器 2 | 192.168.20.6 |
B 区目标服务器,辅助测试 |
| B 区网关 | 192.168.20.99 |
B 区出口,也是 SSH 隧道的一端 |
| 中间路由器 | 10.8.0.11 / 192.168.20.11 |
转发 A 区 B 区之间的流量,同时执行防火墙规则 |
启动实验前先清理容器环境,防止被之前的网络配置干扰
|
|
进入实验目录并启动
|
|
验证容器状态
|
|
基础网络测试
实验开始前,首先确认从A1到B1,和从A1到B2容器的网络连通性
|
|
查看中间路由器的路由表,这里可以看到它有两个网卡,eth0连接 A 区网段10.8.0.0/24,自己的地址是 10.8.0.11,eth1连接 B 区网段192.168.20.0/24,自己的地址是 192.168.20.11。说明这个路由器就是两个区域通信的中转
|
|
进入防火墙容器,查看默认的 iptables 规则
|
|
这部分规则模拟的就是一个简易的公司内网防火墙环境,类比到真实情况,A 就是防火墙外侧网络,可以作为外部访问者或者代理服务器,B 就是受保护的内网。这里重点是看FORWARD链,也就是路由器转发包的规则,默认策略是 ACCEPT
从 A 区方向进来的 TCP 流量中,已经建立的连接和SSH流量,也就是目标端口是22的流量可以通过,其他普通 TCP 会被丢弃,这样 A 区主机如果直接访问 B 区的 Telnet、HTTP 这类普通 TCP 服务,就会被防火墙阻断
从 B 区方向进来的所有流量中,目标是93.184.216.0/24的流量会被丢弃,这个 IP 在之后的实验中对应的是www.example.com,这就体现了 B 区主机访问某个网站被防火墙阻断的情况
静态端口转发
通过建立 SSH 静态隧道,把对本地端口的访问转发到远程内网的特定服务上。这里把 A 区网关的
8000端口映射到192.168.20.5:23,从而让 A 区主机穿透防火墙,访问 B 区被保护的 Telnet 服务
新开一个终端窗口,进入 A 区网关容器
|
|
在 A 区网关上建立 SSH 隧道,目的是将10.8.0.99:8000映射到192.168.20.5:23。执行命令后终端会持续运行来维持 SSH 隧道
|
|
-
-L:启用本地端口转发 -
0.0.0.0:8000:让 A 区网关上的 SSH 客户端监听所有网卡上的8000端口 -
192.168.20.5:23:如果监听到8000端口的连接,就通过 SSH 隧道转发给 B 区网关,B 区网关再连接 B1 的 Telnet 服务 -
seed@192.168.20.99:SSH 连接到 B 区网关 -
-N:不执行远程命令,只做端口转发 -
-T:不分配伪终端
新开一个终端进入 A1 容器,通过 A 区网关的 8000 端口连接 B1 的 Telnet 服务
|
|
输入用户和密码登录后,输入下面的命令
|
|
可以看到已经成功登录到了 192.168.20.5的终端
为了观察防火墙视角下的数据包,可以在路由器容器中抓包
|
|
防火墙主要能看到 10.8.0.99 到 192.168.20.99:22 的 SSH 流量,真正的 Telnet 数据被包在 SSH 加密连接内部,防火墙看不到里面访问的是 192.168.20.5:23,这就是静态端口转发可以绕过防火墙限制的原因
动态 SOCKS 代理
静态端口转发每次只能绑定一个固定目标,而动态端口转发会在本地启动一个 SOCKS 代理,仅指定代理出入口,但访问目标不写死在 SSH 命令里,由 SOCKS 代理命令来传输。因此能让 B 区主机通过 A 区网关作为出口,来访问原本被防火墙拦截的网站
SOCKS 代理在应用层,处理的是应用发起的具体访问请求
新开一个终端窗口,进入 B 区网关容器
|
|
在 B 区网关上建立动态代理隧道,目的是通过 SSH 隧道把请求发到 A 区网关,再让 A 区网关去访问目标。执行命令后终端会持续运行来维持 SSH 隧道
|
|
-
-D:启用动态端口转发,也就是创建 SOCKS 代理 -
0.0.0.0:9000:让 B 区网关上的 SSH 客户端监听所有网卡上的9000端口 -
seed@10.8.0.99:SSH 连接到 A 区网关
进入 B1 容器,修改本地映射,将域名与被防火墙封锁的网段绑定
|
|
直接访问目标网站,可以发现没有反应,说明无法访问
|
|
用 SOCKS5 代理访问网站,这次可以成功拿到网页 HTML 内容。这是因为 B1 会把请求交给 B 区网关的 SOCKS 代理,也就是192.168.20.99:9000,代理再通过 SSH 隧道把请求发给 A 区网关来访问外部网站
这里使用的是
socks5h,h表示域名解析交给代理端完成。因此 B1 在/etc/hosts中对www.example.com的映射主要用来直接访问触发防火墙阻断,而通过 SOCKS 代理访问时,域名可能被代理出口重新解析
|
|
浏览器也是类似的流程,首先在 Firefox 浏览器中如图配置代理
访问www.example.com,可以发现页面成功加载了
关闭SSH隧道,再次访问就发现代理服务器拒绝连接,这和真实网络中的代理是类似的
3 层 VPN 隧道
这部分更接近真正 VPN 的工作方式,它会创建一个虚拟网卡
tun0,然后把数据包放进 SSH 隧道中传输。3层指的是网络层,记录了源IP、目标IP
开始这个部分前,先重启实验容器,清理之前的隧道和路由配置来防止干扰
|
|
A区访问B区
这里先在防火墙上添加规则,禁止 A 区网段直接访问 B 区网段。这个规则是添加在最后的,用于处理不匹配前面几个规则的流量
|
|
此时从 A 区网关 ping B1 会失败
|
|
为了成功访问,A 网关不再把包直接发向 B 区,而是建立 SSH 连接。但由于SSH只是一个通信协议,不参与流量的规划,因此还要在 A 区网关和 B 区网关创建虚拟网卡
tun0,作用是分离需要代理的流量。这样只需要把访问 B 区网段的流量引到tun0,这些数据包就会被 SSH 封装后发到 B 区网关
进入 A 区网关容器,用 SSH 的 -w 参数建立三层 VPN 隧道。执行命令后终端会持续运行来维持 VPN 隧道
|
|
-w 0:0:启用 SSH 的三层隧道功能,在 A 区和 B 区网关分别创建tun0虚拟网卡root@192.168.20.99:SSH 连接到 B 区网关,也就是 VPN 隧道出口PermitLocalCommand=yes:允许 SSH 连接建立后在本地自动执行命令LocalCommand=...:在 A 区网关上配置tun0,分配192.168.53.88/24并启动网卡RemoteCommand=...:在 B 区网关上配置tun0,分配192.168.53.99/24并启动网卡
上一步只是搭建了传输的基础,但还没有对流量进行规划。这一步需要配置路由,给访问流量和回程流量添加规则
进入 A 区网关配置路由。为了避免 SSH 隧道自己的连接进入 tun0,先给 B 区网关加一条单独的规则来让它走原来的路径,再把访问 B 区网段的流量引入 tun0
|
|
还需要给 B1 配置回程路由,将目标是 192.168.53.0/24 的回包交给 B 区网关
|
|
回到 A 区网关再次 ping B1,此时数据包先进入 tun0,再被封装进 SSH 连接中,防火墙看到的外层还是 SSH,不会进行拦截,因此成功发送了数据包
|
|
B区访问外部
注意:实验中的
www.example.com通过CDN解析,域名解析出的IP经常会变化,变化一次就会导致之前的一大串配置全部失效,需要修改IP后重新执行。这个方案稳定性极低,但也是无奈之举,更好的实验流程是将访问目标设置为自己有公网IP的服务器
开始这个部分前,重启容器来清理路由
|
|
首先在A网关尝试访问目标网站,输出中可以看到www.example.com被映射到了104.20.23.154。这个IP经常变化,因此虽然在VPN转发流程中A区网关并不参与DNS解析,还是需要执行下面的命令来找到有效的IP
|
|
重新在路由器容器建立B区网段对于目标网段的防火墙阻断
|
|
将 B1 的本地映射改成 104.20.23.154
|
|
直接访问目标网站被拒绝
进入 B 区网关容器,用 SSH 的 -w 参数建立带 NAT 的三层 VPN 隧道。执行命令后终端会持续运行来维持 VPN 隧道
|
|
-w 0:0:启用 SSH 的三层隧道功能,在 B 区网关和 A 区网关之间创建tun0虚拟网卡root@10.8.0.99:SSH 连接到 A 区网关,也就是 VPN 隧道出口PermitLocalCommand=yes:允许 SSH 连接建立后在本地自动执行命令LocalCommand=...:在 B 区网关上配置tun0,分配192.168.53.88/24,启动网卡,并把目标网段的流量导入隧道RemoteCommand=...:在 A 区网关上配置tun0,分配192.168.53.99/24并启动网卡
这里两侧都配置 NAT是为了解决返回路径问题。B 区网关上的NAT会把 B1 发出的流量伪装成 B 区网关的隧道地址 192.168.53.88,这样 A 区网关会将数据包从 tun0 送回 B 区网关,网关再根据NAT记录发给B1。A 区网关上的NAT会把从隧道转发出来的流量伪装成 A 区网关的出口地址 10.8.0.99,这样目标IP的返回包会先回到 A 区网关,再由 NAT 记录转发回隧道
然后给 B1 添加到目标网段的规则,让它访问 104.20.23.0/24 时先交给 B 区网关
|
|
在 B1 容器中访问 www.example.com,直接 curl www.example.com 可能会变成 IPv6 访问,因此使用下面的命令验证
|
|
B1 先把访问目标 IP 的数据包交给 B 区网关,B 区网关通过 NAT 把源地址改成隧道入口地址后送进tun0,SSH 隧道把数据包带到 A 区网关,A 区网关再通过 NAT 把源地址改成自己的出口地址然后从 eth0 发出。回包按照 NAT 记录逐层返回最终回到 B1,就这样实现了防火墙的越过
实验理解
在这个实验中,不管用什么方法绕过防火墙,最外层都是一个SSH隧道。整个过程中,防火墙负责拦截不允许的流量,路由决定数据包应该走普通网络还是进入隧道,SSH 作为隧道承载流量,tun0 是让流量进入隧道的虚拟网卡,NAT 负责处理返回路径。因此 VPN 本质是在原有网络外创建了一条新的虚拟网络路径
| 方式 | 本质 | 结果 |
|---|---|---|
| 直接 SSH | 远程登录 B 网关 | 在 B 网关上执行命令 |
| SSH 端口转发 | 转发特定端口到固定地址 | 只能访问指定服务 |
| SSH SOCKS | 应用层主动走代理 | 代理代替连接目标 |
SSH + tun0 |
新增虚拟网络 | 系统按照路由分流 |
附:VPN 访问排查
在B区访问外部部分,原本是将
www.example.com绑定到93.184.216.34,再添加防火墙规则来阻断 B 区主机访问这个IP,最后建立VPN隧道绕过防火墙,但发现VPN建立完成后依旧无法访问
1sudo docker exec -it B1-192.168.20.5 /bin/bash -c 'echo "93.184.216.34 www.example.com" >> /etc/hosts'
域名解析
在B1容器检查域名解析,发现显示的是 IPv6 地址,说明这个域名的解析可能自动走了IPv6
|
|
查询 IPv4 解析结果,可以看到确实绑定了 93.184.216.34
|
|
强制用 IPv4 访问目标,结果依旧失败
|
|
网关配置
检查 B 区网关的 VPN 隧道、路由、 NAT 配置
|
|
B 区网关上可以看到 tun0,地址是 192.168.53.88/24。访问 93.184.216.34 的路由是 dev tun0,说明网关会把目标流量送进 VPN 隧道。NAT 表中可以看到 MASQUERADE -o tun0,计数也增加了,说明有数据包经过 NAT 规则
检查 A 区网关的隧道、出站路由、NAT 、IP 转发状态
|
|
A 区网关上可以看到 tun0,地址是 192.168.53.99/24。访问 93.184.216.34 的路由是 via 10.8.0.1 dev eth0 src 10.8.0.99,说明网关会把目标流量从 eth0 发出,并通过 10.8.0.1 访问外部网络。NAT 表中可以看到 MASQUERADE -o eth0,计数也增加了,说明有数据包经过 NAT 规则。两边的 ip_forward 也都是 1,说明配置部分没有问题
数据链路
之后抓包来查看数据包的路径,在 A 区网关上抓 tun0 的流量
|
|
然后在 B1 中重新访问目标网站
|
|
抓到如下的流量,这里的 192.168.53.88 > 93.184.216.34.80 说明 B 区网关已经把 B1 的访问流量送进 VPN 隧道并到达 A 区网关的 tun0
检查 A 区网关是否把收到的流量继续从 eth0 发出去
|
|
抓到如下流量,可以看到源地址被 NAT 成 10.8.0.99,数据包也成功发出,说明传输路径是通的
目标IP
既然数据传输没有问题,那么问题很可能出在 A 区网关访问目标IP的时候。尝试直接访问 93.184.216.34,发现连接不上
|
|
直接访问 www.example.com 却成功了
|
|
输出中可以看到实际连接的是 104.20.23.154,而下面的这段信息说明请求经过 Cloudflare
|
|
可以得出 www.example.com 由 CDN 网络提供访问,域名解析可能返回不同的公网 IP。而之前只配置了 93.184.216.0/24的 VPN 隧道,当域名解析到新的IP地址时,原本的规则就无法匹配IP了,导致表现出来的结果是无法访问