防火墙翻越
2026-08-28 03:18:49实验环境
我使用的是 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 区之间的流量,同时执行防火墙规则
启动实验前先清理容器环境,防止被之前的网络配置干扰
1
2
sudo docker stop $(sudo docker ps -q) 2>/dev/null
sudo docker system prune -f
进入实验目录并启动
1
2
cd seed-labs/category-network/Firewall_Evasion/Labsetup
sudo docker compose up -d
验证容器状态
1
sudo docker ps
基础网络测试
实验开始前,首先确认从A1到B1,和从A1到B2容器的网络连通性
1
2
sudo docker exec A1-10.8.0.5 ping -c 3 192.168.20.5
sudo docker exec A1-10.8.0.5 ping -c 3 192.168.20.6
查看中间路由器的路由表,这里可以看到它有两个网卡,eth0连接 A 区网段10.8.0.0/24,自己的地址是 10.8.0.11,eth1连接 B 区网段192.168.20.0/24,自己的地址是 192.168.20.11。说明这个路由器就是两个区域通信的中转
1
sudo docker exec router-firewall ip route show
进入防火墙容器,查看默认的 iptables 规则
1
2
3
4
5
sudo docker exec -it router-firewall /bin/bash
# 查看完整的 filter 表
iptables -L -v -n
# 只看 FORWARD 链
iptables -L FORWARD -v -n
这部分规则模拟的就是一个简易的公司内网防火墙环境,类比到真实情况,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 区网关容器
1
sudo docker exec -it A-10.8.0.99 /bin/bash
在 A 区网关上建立 SSH 隧道,目的是将10.8.0.99:8000映射到192.168.20.5:23。执行命令后终端会持续运行来维持 SSH 隧道
1
ssh -4NT -L 0.0.0.0:8000:192.168.20.5:23 seed@192.168.20.99
-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 服务
1
2
sudo docker exec -it A1-10.8.0.5 /bin/bash
telnet 10.8.0.99 8000
输入用户和密码登录后,输入下面的命令
1
ip -br a
可以看到已经成功登录到了 192.168.20.5的终端
为了观察防火墙视角下的数据包,可以在路由器容器中抓包
1
tcpdump -n -i any
防火墙主要能看到 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 区网关容器
1
sudo docker exec -it B-192.168.20.99 /bin/bash
在 B 区网关上建立动态代理隧道,目的是通过 SSH 隧道把请求发到 A 区网关,再让 A 区网关去访问目标。执行命令后终端会持续运行来维持 SSH 隧道
1
ssh -4NT -D 0.0.0.0:9000 seed@10.8.0.99
-D:启用动态端口转发,也就是创建 SOCKS 代理
0.0.0.0:9000:让 B 区网关上的 SSH 客户端监听所有网卡上的 9000 端口
seed@10.8.0.99:SSH 连接到 A 区网关
进入 B1 容器,修改本地映射,将域名与被防火墙封锁的网段绑定
1
2
sudo docker exec -it B1-192.168.20.5 /bin/bash
echo "93.184.216.34 www.example.com" >> /etc/hosts
直接访问目标网站,可以发现没有反应,说明无法访问
1
curl www.example.com
用 SOCKS5 代理访问网站,这次可以成功拿到网页 HTML 内容。这是因为 B1 会把请求交给 B 区网关的 SOCKS 代理,也就是192.168.20.99:9000,代理再通过 SSH 隧道把请求发给 A 区网关来访问外部网站
这里使用的是 socks5h, h 表示域名解析交给代理端完成。因此 B1 在/etc/hosts 中对 www.example.com 的映射主要用来直接访问触发防火墙阻断,而通过 SOCKS 代理访问时,域名可能被代理出口重新解析
1
curl --proxy socks5h://192.168.20.99:9000 www.example.com
浏览器也是类似的流程,首先在 Firefox 浏览器中如图配置代理
访问www.example.com,可以发现页面成功加载了
关闭SSH隧道,再次访问就发现代理服务器拒绝连接,这和真实网络中的代理是类似的
3 层 VPN 隧道
这部分更接近真正 VPN 的工作方式,它会创建一个虚拟网卡 tun0,然后把数据包放进 SSH 隧道中传输。3层指的是网络层,记录了源IP、目标IP
开始这个部分前,先重启实验容器,清理之前的隧道和路由配置来防止干扰
1
2
sudo docker compose down
sudo docker compose up -d
A区访问B区
这里先在防火墙上添加规则,禁止 A 区网段直接访问 B 区网段。这个规则是添加在最后的,用于处理不匹配前面几个规则的流量
1
2
sudo docker exec -it router-firewall /bin/bash
iptables -A FORWARD -s 10.8.0.0/24 -d 192.168.20.0/24 -j DROP
此时从 A 区网关 ping B1 会失败
1
sudo docker exec -it A-10.8.0.99 ping 192.168.20.5
为了成功访问,A 网关不再把包直接发向 B 区,而是建立 SSH 连接。但由于SSH只是一个通信协议,不参与流量的规划,因此还要在 A 区网关和 B 区网关创建虚拟网卡 tun0,作用是分离需要代理的流量。这样只需要把访问 B 区网段的流量引到 tun0,这些数据包就会被 SSH 封装后发到 B 区网关
进入 A 区网关容器,用 SSH 的 -w 参数建立三层 VPN 隧道。执行命令后终端会持续运行来维持 VPN 隧道
1
2
3
4
5
sudo docker exec -it A-10.8.0.99 /bin/bash
ssh -w 0:0 root@192.168.20.99 \
-o "PermitLocalCommand=yes" \
-o "LocalCommand= ip addr add 192.168.53.88/24 dev tun0 && ip link set tun0 up" \
-o "RemoteCommand=ip addr add 192.168.53.99/24 dev tun0 && ip link set tun0 up"
-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
1
2
3
4
5
sudo docker exec -it A-10.8.0.99 /bin/bash
# 访问192.168.20.99的流量走原来的中间路由器
ip route add 192.168.20.99/32 via 10.8.0.11
# 访问所有192.168.20.0网段的流量走虚拟网卡
ip route replace 192.168.20.0/24 dev tun0
还需要给 B1 配置回程路由,将目标是 192.168.53.0/24 的回包交给 B 区网关
1
sudo docker exec -it B1-192.168.20.5 ip route add 192.168.53.0/24 via 192.168.20.99
回到 A 区网关再次 ping B1,此时数据包先进入 tun0,再被封装进 SSH 连接中,防火墙看到的外层还是 SSH,不会进行拦截,因此成功发送了数据包
1
ping 192.168.20.5
B区访问外部
注意:实验中的www.example.com通过CDN解析,域名解析出的IP经常会变化,变化一次就会导致之前的一大串配置全部失效,需要修改IP后重新执行。这个方案稳定性极低,但也是无奈之举,更好的实验流程是将访问目标设置为自己有公网IP的服务器
开始这个部分前,重启容器来清理路由
1
2
sudo docker compose down
sudo docker compose up -d
首先在A网关尝试访问目标网站,输出中可以看到www.example.com被映射到了104.20.23.154。这个IP经常变化,因此虽然在VPN转发流程中A区网关并不参与DNS解析,还是需要执行下面的命令来找到有效的IP
1
sudo docker exec A-10.8.0.99 curl -4 --connect-timeout 8 -v www.example.com
重新在路由器容器建立B区网段对于目标网段的防火墙阻断
1
2
sudo docker exec -it router-firewall /bin/bash
iptables -A FORWARD -s 192.168.20.0/24 -d 104.20.23.0/24 -j DROP
将 B1 的本地映射改成 104.20.23.154
1
2
3
4
5
sudo docker exec -it B1-192.168.20.5 /bin/bash
sed '/www.example.com/d' /etc/hosts > /tmp/hosts
cat /tmp/hosts > /etc/hosts
echo "104.20.23.154 www.example.com" >> /etc/hosts
getent ahostsv4 www.example.com
直接访问目标网站被拒绝
进入 B 区网关容器,用 SSH 的 -w 参数建立带 NAT 的三层 VPN 隧道。执行命令后终端会持续运行来维持 VPN 隧道
1
2
3
4
5
6
7
8
sudo docker exec -it B-192.168.20.99 /bin/bash
ssh -w 0:0 root@10.8.0.99 \
-o "PermitLocalCommand=yes" \
-o "LocalCommand= ip addr add 192.168.53.88/24 dev tun0 && ip link set tun0 up \
&& ip route add 104.20.23.0/24 dev tun0 \
&& iptables -t nat -A POSTROUTING -j MASQUERADE -o tun0" \
-o "RemoteCommand=ip addr add 192.168.53.99/24 dev tun0 && ip link set tun0 up \
&& iptables -t nat -A POSTROUTING -j MASQUERADE -o eth0"
-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 区网关
1
sudo docker exec -it B1-192.168.20.5 ip route add 104.20.23.0/24 via 192.168.20.99
在 B1 容器中访问 www.example.com,直接 curl www.example.com 可能会变成 IPv6 访问,因此使用下面的命令验证
1
curl -4 www.example.com
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建立完成后依旧无法访问
1
sudo docker exec -it B1-192.168.20.5 /bin/bash -c 'echo "93.184.216.34 www.example.com" >> /etc/hosts'
域名解析
在B1容器检查域名解析,发现显示的是 IPv6 地址,说明这个域名的解析可能自动走了IPv6
1
sudo docker exec B1-192.168.20.5 getent hosts www.example.com
查询 IPv4 解析结果,可以看到确实绑定了 93.184.216.34
1
getent ahostsv4 www.example.com
强制用 IPv4 访问目标,结果依旧失败
1
curl -4 --connect-timeout 5 www.example.com
网关配置
检查 B 区网关的 VPN 隧道、路由、 NAT 配置
1
2
3
sudo docker exec B-192.168.20.99 ip -br a
sudo docker exec B-192.168.20.99 ip route get 93.184.216.34
sudo docker exec B-192.168.20.99 iptables -t nat -L POSTROUTING -v -n
B 区网关上可以看到 tun0,地址是 192.168.53.88/24。访问 93.184.216.34 的路由是 dev tun0,说明网关会把目标流量送进 VPN 隧道。NAT 表中可以看到 MASQUERADE -o tun0,计数也增加了,说明有数据包经过 NAT 规则
检查 A 区网关的隧道、出站路由、NAT 、IP 转发状态
1
2
3
4
5
sudo docker exec A-10.8.0.99 ip -br a
sudo docker exec A-10.8.0.99 ip route get 93.184.216.34
sudo docker exec A-10.8.0.99 iptables -t nat -L POSTROUTING -v -n
sudo docker exec A-10.8.0.99 sysctl net.ipv4.ip_forward
sudo docker exec B-192.168.20.99 sysctl net.ipv4.ip_forward
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 的流量
1
sudo docker exec -it A-10.8.0.99 tcpdump -n -i tun0
然后在 B1 中重新访问目标网站
1
sudo docker exec B1-192.168.20.5 curl -4 --connect-timeout 5 www.example.com
抓到如下的流量,这里的 192.168.53.88 > 93.184.216.34.80 说明 B 区网关已经把 B1 的访问流量送进 VPN 隧道并到达 A 区网关的 tun0
检查 A 区网关是否把收到的流量继续从 eth0 发出去
1
sudo docker exec -it A-10.8.0.99 tcpdump -n -i eth0 host 93.184.216.34
抓到如下流量,可以看到源地址被 NAT 成 10.8.0.99,数据包也成功发出,说明传输路径是通的
目标IP
既然数据传输没有问题,那么问题很可能出在 A 区网关访问目标IP的时候。尝试直接访问 93.184.216.34,发现连接不上
1
sudo docker exec A-10.8.0.99 curl -4 --connect-timeout 8 -v http://93.184.216.34
直接访问 www.example.com 却成功了
1
sudo docker exec A-10.8.0.99 curl -4 --connect-timeout 8 -v www.example.com
输出中可以看到实际连接的是 104.20.23.154,而下面的这段信息说明请求经过 Cloudflare
1
2
3
Server: cloudflare
cf-cache-status: HIT
CF-RAY: a0c2ec3ca90216f0-FRA
可以得出 www.example.com 由 CDN 网络提供访问,域名解析可能返回不同的公网 IP。而之前只配置了 93.184.216.0/24的 VPN 隧道,当域名解析到新的IP地址时,原本的规则就无法匹配IP了,导致表现出来的结果是无法访问