在校外访问图书馆、内网管理系统时,WebVPN 是最常用的工具。与传统的 SSL VPN 不同,WebVPN 不需要安装客户端,它通过动态重写 URL 和 反向代理 技术实现内网访问。
而某学校可以代理的页面属实有限,常用的比如说课程点播就没有webvpn访问的链接
1. WebVPN 的工作原理
WebVPN 的核心是一个“应用层代理”。当你通过 VPN 访问一个地址时,网关会做两件事:
-
URL 转换:将内网地址(如
172.16.x.x)转换为 VPN 域名的子路径。 -
内容改写:实时修改返回网页中的所有超链接,确保你点击后仍然留在 VPN 环境内。
2. 核心算法分析
通过分析 3e70562.js 中的 Module 146,我们发现其域名加密采用了 AES (Advanced Encryption Standard)。
关键技术参数
-
加密模式:$AES-CFB$ (Cipher Feedback Mode)。
-
填充方式:由于是 CFB 模式,它对长度不敏感,但源码中会对主机名进行简单的
填充以对齐 16 字节。 -
密钥 (Key) 与 偏移量 (IV):该系统通常使用固定字符串作为初始 Key,常见的默认为
wrdvpnisthebest!。 -
输出格式:生成的路径由
32位十六进制IV+十六进制密文组成。
链接结构表
| 原始组件 | 转换后格式 | 说明 |
|---|---|---|
| 协议 | https/ 或 http/ |
位于域名后的第一级路径 |
| 端口 | https-8080/ |
如果是非标准端口,会拼接到协议后 |
| 主机名 | 加密后的16进制串 |
包含 IV 和密文主机名 |
| 路径/参数 | /index.html?id=1 |
保持不变,拼在加密串最后 |
3. 算法实现 (Python)
为了实现自动化工具,我们需要使用 pycryptodome 库。该脚本可以将任意内网 URL 快速转换为 WebVPN 格式。
import binascii
from Crypto.Cipher import AES
from urllib.parse import urlparse
class WebVPNConverter:
def __init__(self, vpn_domain, key=None, iv=None):
"""
:param vpn_domain: WebVPN 的基础域名,例如 'vpn.example.edu.cn'
:param key: 16字节的加密密钥,默认为常见默认值
:param iv: 16字节的初始化向量
"""
self.vpn_domain = vpn_domain.strip('/')
# 很多此类系统的默认 Key/IV 是 'wrdvpnisthebest!' (16位)
self.key = key.encode('utf-8') if key else b'wrdvpnisthebest!'
self.iv = iv.encode('utf-8') if iv else b'wrdvpnisthebest!'
def encrypt_host(self, host):
"""
实现 AES-CFB 加密算法:hex(iv) + hex(encrypted_host)
"""
# 模拟 JS 中的截断逻辑
original_len = len(host)
# 初始化 AES CFB 模式
# 注意:这里的 segment_size 默认为 128 位 (16字节)
cipher = AES.new(self.key, AES.MODE_CFB, self.iv, segment_size=128)
# 加密主机名
encrypted_bytes = cipher.encrypt(host.encode('utf-8'))
# 转换为十六进制字符串
iv_hex = binascii.hexlify(self.iv).decode('utf-8')
encrypted_hex = binascii.hexlify(encrypted_bytes).decode('utf-8')
# 拼接结果:IV 的 hex + 加密后的 hex (长度与原主机名对应)
return iv_hex + encrypted_hex[:original_len * 2]
def convert(self, original_url):
"""
将普通 URL 转换为 WebVPN URL
"""
parsed = urlparse(original_url)
if not parsed.scheme or not parsed.netloc:
return "无效的 URL"
protocol = parsed.scheme # http 或 https
# 提取主机名和端口
host_parts = parsed.netloc.split(':')
hostname = host_parts[0]
port = host_parts[1] if len(host_parts) > 1 else ""
# 加密主机名部分
encrypted_host = self.encrypt_host(hostname)
# 构造 VPN 路径部分
# 格式:/http-端口/加密主机/路径 或 /http/加密主机/路径
if port:
vpn_path = f"{protocol}-{port}/{encrypted_host}"
else:
vpn_path = f"{protocol}/{encrypted_host}"
# 拼接完整 URL
new_url = f"https://{self.vpn_domain}/{vpn_path}{parsed.path}"
if parsed.query:
new_url += f"?{parsed.query}"
return new_url
# --- 使用示例 ---
if __name__ == "__main__":
# 替换为你学校或单位的 WebVPN 域名
MY_VPN = "webvpn.hfut.edu.cn"
converter = WebVPNConverter(MY_VPN)
urls_to_convert = [
"https://local.example.com/",
"http://172.11.1.1",
]
print(f"{'原始 URL':<50} -> {'转换后的 WebVPN URL'}")
print("-" * 110)
for url in urls_to_convert:
result = converter.convert(url)
print(f"{url:<50} -> {result}")
4. 进阶:如何应对失效?
在实际应用中,单有转换算法是不够的,你可能还会遇到以下问题:
-
Session 校验:WebVPN 会校验 Cookie 中的
TW_SESS。在 Python 中使用requests.Session()保持登录状态是必须的。 -
动态 Key:部分安全性更高的配置会每隔一段时间更换 Key。如果算法失效,建议检查 F12 网络面板中的
/config接口获取最新配置。(不过泥工是写死的) -
JS 渲染:对于使用了大量框架(如 Vue/React)的内网系统,简单的 URL 转换可能无法覆盖动态加载的资源,建议配合浏览器的
Header Rewrite插件使用。
5. 总结
WebVPN 的设计初衷是为了便捷,但在安全性上由于依赖前端加密,其逻辑对开发者几乎是透明的。通过这种办法也可以省去frp/打洞的麻烦,方便访问校内的服务
免责声明:本文仅供技术交流学习,请自觉遵守校园网及相关网络使用规定。

