穿越校园网的准入控制

穿越校园网的准入控制

背景

在严格实施准入控制(NAC)的校园网环境中,我们经常面临这样一个尴尬的场景:主机位于内网(宿舍区),物理连通性良好,但因未通过身份认证,被防火墙(准入网关)死死挡在互联网大门之外。

这就引出了一个有趣的技术探讨:对于一台处于校园网内网、但未通过认证的主机(Host A),我们是否有办法绕过网关的限制,借助其他已联网的内网资源实现上网呢?

尝试一:SSH 动态转发(SOCKS5 代理)

1. 理想模型:寻找“跳板”

假设我们的网络拓扑如下: * Host A:位于宿舍区,未登录校园网,无法访问公网,且访问 HTTP 网页会被重定向到认证页面。 * Host B:位于实验室或办公区,已通过认证,拥有合法的互联网访问权限。

既然 Host A 和 Host B 同属校园内网,且两者之间物理链路是通的。那么,是否可以让 Host A 连接到 Host B,让 Host B 充当“跳板”将流量转发到互联网呢?

最直观的方案就是使用 SSH 动态转发(Dynamic Forwarding, -D

2. 实施与原理

我们在 Host A 上执行以下命令,试图建立一条通往 Host B 的隧道:

1
2
3
4
5
6
# -D 7890: 在本地(Host A)开启 7890 端口作为 SOCKS 代理
# -f: 后台运行
# -N: 不执行远程命令(仅用于转发数据)
# -C: 开启压缩(可选,视网络环境而定)
# 注意:这里连接的是 Host B 的内网 IP,而非公网 IP
ssh -fN -D 127.0.0.1:7890 user@Host_B_内网IP

如果连接成功,Host A 本地的 7890 端口就变成了一个通往互联网的 SOCKS5 接口。此时,我们只需配置环境变量 export all_proxy=socks5://127.0.0.1:7890,当 Host A 执行 curl www.baidu.com 时,数据流向将如下所示:

  1. 本地封装(Local):Host A 的 curl 将请求发送给本地的 127.0.0.1:7890(遵循 SOCKS5 协议)。
  2. 隧道传输(Tunnel):本地 SSH 客户端将数据包加密并封装,通过 TCP 连接(默认 22 端口)发送给 Host B 的 SSH 服务端(sshd)。
  3. 远程发射(Remote):Host B 的 sshd 收到数据后解密,识别出这是动态转发请求。sshd 随即将以 Host B 的身份,发起一个新的 TCP 连接去连接目标网站(百度)。

本质上,ssh -D 将远程主机(Host B)变成了一个 NAT 网关,流量经过加密隧道“偷渡”到 Host B,再由 Host B “发射”到公网。

3. 为什么是 SOCKS5?

这里值得插入一段技术背景。SSH 动态转发默认通过 SOCKS5 协议 提供服务,这并非巧合,而是由协议特性决定的。

我们可以把 SOCKS5 和常见的 HTTP 代理 做个对比:

  • SOCKS5 (会话层/Layer 5):它像是一个“盲人快递员”。它介于传输层和应用层之间,根本不关心你传的是什么——无论是看网页(HTTP)、传文件(FTP),还是私有协议的游戏数据,它只负责建立连接并透传数据。正因为它的“盲”和“通用”,它支持 TCP 和 UDP,且没有解析应用层协议头部的开销,速度快且普适性强。
  • HTTP Proxy (应用层/Layer 7):它像是一个“精明的秘书”。它非常懂 HTTP 协议,会拆开数据包查看 URL、Header 等信息,再代你发起一个新的 HTTP 请求。它通常只支持 TCP,且专注于 Web 流量。

SSH 的开发者选择了 SOCKS5,正是因为他们无法预知用户想在隧道里跑什么业务(可能是 Web,也可能是 MySQL 连接,甚至是魔兽世界的数据包)。既然不可预知,就提供一个什么都能传更注重隐私(不分析头部)的通用接口。

4. 现实的骨感:校园网的“大手”

上述方案在理论上完美无缺,但在实际的校园网(特别是清华大学校园网 TUNET)环境中,这个方案通常会直接失败

原因在于我们低估了校园网准入控制系统的拦截能力。根据清华大学校园网的架构文档,核心网关实施了严格的 ACL(访问控制列表)策略:

“阻断未经批准的 0~1024 端口。”

这是什么意思?这意味着,虽然 Host A 和 Host B 都在校内,但只要它们处于不同的子网(例如宿舍区 59.66.x.x 和办公区 166.111.x.x),它们之间的流量就必须经过核心路由或汇聚层设备。

而在 Host A 完成认证之前,网关不仅拦截了去往公网的流量,也无差别阻断了校内跨子网的低位端口通信。SSH 标准端口是 TCP 22,恰好落在 0~1024 的被封锁区间内。

结论是残酷的:Host A 甚至连 Host B 的 SSH 握手包都发不过去,连接会直接超时(Connection Timed Out)或被拒绝。除非 Host B 能够修改 SSH 监听端口到高位(如 10000+),否则 SSH 动态转发策略宣告失效

image-20251119150714473

SSH 反向隧道(Remote Forwarding)

既然“正向连接”的道路被准入网关无情阻断,我们只能转变思路,利用“反向隧道”来实施救援。

1. 核心思路:带网救援

反向隧道(Reverse Tunneling)的核心逻辑是:既然内网主机(Host A)出不去,那就让一台有网的机器(Host B)带着网络进来。

这种场景通常发生在你拥有一台能够同时连接内网和外网的设备时。例如: * 你坐在宿舍(Host A 旁),手边有一台笔记本(Host B)。 * 笔记本(Host B)通过双网卡工作:一张有线网卡连接校园内网(与 Host A 互通),另一张无线网卡连接手机热点(直通互联网)。或者笔记本本身通过了校园网的认证,可以访问公网。

只要 Host B 能 SSH 连上 Host A,它就能把自己的互联网能力通过 SSH 隧道“反向映射”给 Host A。

2. 操作步骤

第一步:在“救援机”上准备代理

首先,我们需要在 Host B(救援机/本地主机)上运行一个代理服务。假设我们运行了 Clash 或 V2Ray,并在本地监听了 7890 端口。确保这个代理服务工作正常,Host B 自己能通过它上网。

第二步:打通反向隧道

接着,我们在 Host B 上执行 SSH 命令,主动连接内网主机 Host A,并建立端口映射:

1
2
3
# -R [远程IP:]远程端口:本地IP:本地端口
# 含义:将远程(Host A)的 7890 端口,绑定到我本地(Host B)的 7890 端口
ssh -R 7890:127.0.0.1:7890 user@Host_A_内网IP

这条命令就像是在两台主机之间架设了一根无形的管道。管道的一头接在 Host A 的 127.0.0.1:7890,另一头接在 Host B 的 127.0.0.1:7890

第三步:内网主机配置环境变量

现在,对于 Host A 而言,它本地的 7890 端口就等同于 Host B 的代理端口。我们只需要告诉 Host A 的应用程序往这里发数据即可:

1
2
3
4
5
6
# 如果 Host B 提供的是 SOCKS5
export all_proxy=socks5://127.0.0.1:7890

# 如果 Host B 提供的是 HTTP
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890

3. 深度解析:隧道只是管道,协议决定生死

这里栽跟头的地方在于协议不匹配

SSH 反向隧道(-R)本质上是协议无关的 TCP 数据透传。SSH 只负责把字节流从一头搬运到另一头,它根本不关心里面跑的是什么。端口上跑什么协议,完全取决于隧道出口(Host B)接的是什么服务。

为了避坑,我们需要理解以下三种情况:

  • 情况 A:出口接 SOCKS5(最常见错误)
    • 场景:Host B 上运行的是纯 SOCKS5 代理(如 ssh -D 或某些配置下的 V2Ray)。
    • 现象:如果你在 Host A 上错误地设置了 export http_proxy=http://...,你的 curl 会发送 HTTP 格式的请求头,但隧道另一头的服务只听得懂 SOCKS5 握手包。
    • 结果通信失败。你会看到 Protocol mismatch 或连接立即重置。
  • 情况 B:出口接 HTTP Proxy(推荐)
    • 场景:Host B 上的代理软件(如 Clash 的 Mixed Port)开启了 HTTP 代理支持。
    • 配置:Host A 设置 export http_proxy=http://...
    • 结果成功。源头是 HTTP 请求,终点也是 HTTP 代理服务器,语言相通。
  • 情况 C:出口接 Web 服务
    • 场景:Host B 的 7890 端口跑了一个 Nginx 网页。
    • 结果:你在 Host A 上 curl http://127.0.0.1:7890,会直接获取到 Host B 的网页内容。此时它不再是代理,而是一个远程端口映射。

一句话总结export http_proxy 只是你的一厢情愿,告诉软件用 HTTP 协议去对话;如果隧道对面坐着的是个只懂 SOCKS5 的服务,那是真正的“鸡同鸭讲”。

4. 图解全流程:穿越红墙的数据之旅

为了更直观地理解数据包是如何“欺骗”网络层,成功绕过校园网防火墙的,我们结合以下两张图来复盘整个过程。

架构视图:隧道突围

image-20251119144206868

从这张架构图中我们可以清晰地看到: 1. 物理阻断:Host A(左侧)直接向上的箭头被标红的“防火墙”无情拦截。 2. 局域网互通:Host A 和 Host B 之间的横向连接(LAN)是通畅的,这是建立 SSH 隧道的基础。 3. 借道出海:Host B(中间)利用其双网卡特性,一手拉着内网的 Host A(通过 SSH 隧道),一手连着自由的互联网(NicExt)。数据包实际上走了一条“U型”弯路,成功绕过了红墙。

时序视图:数据包的奇幻漂流

让我们跟着一个 HTTP 请求走一遍(参考时序图):

  1. 发起请求:Host A 上的 curl 发起请求。由于设置了环境变量,请求被发送到了 127.0.0.1:7890
  2. 隧道封装:Host A 的 SSHD 进程捕获了这个连接。注意,此时原本明文的 HTTP 请求被 SSH 协议加密封装成了二进制流。
  3. 穿透防火墙:加密后的数据包通过 TCP 22 端口在局域网内传输。对于校园网设备来说,这看起来只是普通的 SSH 流量,完全合规,因此予以放行。
  4. 还原与代理:数据包到达 Host B 后,被 SSH 客户端解密还原,并转发给本地的 Clash 代理(7890 端口)。
  5. 真实访问:Host B 的 Clash 接收到请求,利用其外网网卡,向 Google 发起真正的网络访问,并将结果沿原路返回。

通过这种“移花接木”的手法,Host A 成功地在严苛的准入控制下呼吸到了互联网的空气。这不仅是 SSH 技术的胜利,也是对网络协议深刻理解的体现。

5. 补充说明

既然流量都要经过那个严厉的“核心网关/防火墙”,为什么 A 连接 B (被杀),而 B 连接 A (能活)

答案的核心在于现代防火墙的工作机制:状态检测(Stateful Inspection)访问控制列表(ACL)的方向性

简单来说:“谁发起连接”决定了生杀大权。

“有罪推定” vs “信任放行” (ACL 的方向性)

在校园网的防火墙(或核心交换机)上,规则通常是这样配置的:

  • 针对未认证区域(Host A 所在的宿舍网 59.66.x.x)
    • 出站规则 (Outbound):默认DENY ALL(拒绝所有)。只有 DNS、认证页面等白名单流量可以通过。因为 Host A 还没登录,被视为“潜在威胁”或“未付费用户”,所以禁止 Host A 主动向外(无论是互联网还是办公网)发起连接
    • 这就是为什么 Host A ssh user@Host B 会失败。
  • 针对已认证区域(Host B 所在的办公网 166.111.x.x)
    • 出站规则 (Outbound)ALLOW ALL(或宽松策略)。Host B 既然已经认证(或者是老师/实验室的高权限机器),网关默认信任它。
    • 入站规则 (Inbound):网关允许 Host B 访问校内其他子网(包括宿舍网)。
    • 这就是为什么 Host B ssh user@Host A 能成功。

总结:防火墙挡的是“未认证的人出去找人”,但不挡“已认证的人进来找你”。

状态检测防火墙 (Stateful Firewall)

现代防火墙不是死板地看每一个包,而是看“连接状态” (Connection State)

场景一:Host A 主动连接 Host B (失败)

  1. Host A 发送 SYN 包(握手请求)给 Host B。
  2. 数据包到达核心网关。
  3. 网关查表:源 IP 是 Host A(未认证)。
  4. 动作:DROP(丢弃)。
  5. 结果:连接根本建立不起来。

场景二:Host B 主动连接 Host A (成功) 1. 阶段 1(建立连接): * Host B 发送 SYN 包给 Host A。 * 数据包到达核心网关。 * 网关查表:源 IP 是 Host B(已认证/可信)。 * 动作:ACCEPT(放行)。 * 关键点:防火墙会在内部的小本本(状态表/State Table)上记一笔:“Host B 正在和 Host A 通话,这是合法的。”

  1. 阶段 2(SSH 反向隧道流量回传)
    • 现在隧道建立了。Host A 需要把 HTTP 请求通过隧道传给 Host B。
    • Host A 发送数据包给 Host B。
    • 网关拦截到这个包。
    • 网关查表:虽然源 IP 是 Host A(未认证),但是!网关发现这个包属于“刚才 Host B 发起的那个合法连接”(Matched Existing State)。
    • 动作:ACCEPT(放行)。

结论:一旦 Host B(特权方)成功建立了连接(“反向隧道”),这条连接就变成了一条双向畅通的合法通道。Host A 在这个通道里发数据,被视为是“对 Host B 请求的回应”,因此畅通无阻。

流量性质的“伪装”

你可能会问:“但是我通过隧道传的是 Google 的数据啊,这也能过?”

  • 防火墙看到的是
    • 源:Host A (59.66.x.x)
    • 目的:Host B (166.111.x.x)
    • 协议:SSH (TCP 22)
    • 内容:加密的二进制乱码
  • 防火墙不知道的是
    • 这堆乱码解密后其实是 GET / HTTP/1.1 Host: google.com

因为 SSH 是加密的,而且 Host A 和 Host B 之间的通信属于校内局域网流量(Intranet Traffic)。对于网关来说,这就是两个校内主机在进行正常的 SSH 管理操作,完全合规。网关并不知道 Host B 偷偷充当了 NAT 代理,把数据转手发给了外网。

打个比方:监狱探视

  • Host A:囚犯(未认证用户)。
  • Host B:律师(已认证用户)。
  • 网关:狱警。

情况 1 (Host A -> B):

  • 囚犯想给外面的律师打电话。
  • 狱警(网关):“不准打,你没这个权限。”(阻断)

情况 2 (Host B -> A):

  • 律师来监狱探视囚犯。
  • 狱警(网关):“律师请进。”(放行)
  • 于是律师和囚犯坐在了探视间里。

反向隧道发生了什么?

  • 此时,囚犯(Host A)想买包烟(上 Google)。他不能自己出去买,但他可以趁着律师(Host B)来探视的时候,把钱偷偷塞给律师,说:“帮我带包烟进来”。
  • 狱警看着律师和囚犯在说话(SSH 加密流量),觉得这是正常的法律咨询,就不管了。
  • 律师拿着钱,走出监狱(利用 Host B 的外网权限),买了烟,再带回来给囚犯。

这就是为什么 22 端口阻断了囚犯的电话,但阻断不了律师的探视。

小结

这场从 SSH 动态转发(-DSSH 反向隧道(-R 的探索之旅,表面上是为了解决“内网主机上网”的琐碎需求,实则是一次对网络安全策略与协议原理的深度解构。

回顾整个过程,我们得出以下几个核心洞见:

  1. 方向决定生死(ACL 的非对称性)

    在严格的准入控制网络中,防火墙的策略往往是“有罪推定”的。未认证的主机(Host A)被视为不可信,其主动发起的连接(无论是通往公网还是校内其他子网)都会被无情阻断。然而,反向隧道巧妙地利用了防火墙对高权限区域(Host B)的“信任”。“谁发起连接”才是通过防火墙的关键——既然由于 ACL 的限制,囚犯无法呼叫律师,那就让律师主动利用特权来探视囚犯。一旦连接建立(State Established),双向的数据传输便成了顺理成章的“合法通信”。

  2. 隧道的本质是“协议封装”

    SSH 隧道之所以强大,是因为它构建了一个加密的 TCP 管道。对于校园网网关而言,它看到的只是两个校内 IP 之间合规的 SSH 流量(TCP 22),而无法得知(或不深入检测)在这个管道内部,实际上奔跑着 HTTP、HTTPS 乃至 SOCKS5 的互联网请求。这正是“隧道技术”在网络穿透中的核心价值。

  3. 细节是魔鬼:SOCKS5 与 HTTP 的博弈

    打通了隧道仅仅是修好了路,车能不能跑起来,还得看“车”和“路”是否匹配。我们深入讨论了 ssh -D 原生 SOCKS5 与 http_proxy 环境变量之间的协议冲突。理解 OSI 模型中会话层(SOCKS)与应用层(HTTP Proxy)的区别,是避免配置陷阱的关键。

最后,技术本无界。无论是正向的代理还是反向的隧道,它们本质上都是对 TCP/IP 协议栈的灵活运用。掌握这些原理,不仅能帮我们在受限网络中获得自由的空气,更能让我们深刻理解网络安全设备是如何思考、防御以及被绕过的。

本文仅供技术研究与学习交流,请遵守所在机构的网络管理规定,勿用于非法用途。