信息收集
通过微信小程序搜索相关关键词,定位到一个校园一卡通系统的H5页面。此类校园卡、水卡管理系统通常是安全测试的重点目标。

进入系统后,首先尝试使用弱密码进行登录。这里的“弱密码”通常指系统预设的、规律性强的初始密码,例如“身份证号后六位”。通过外围信息收集,成功获取到一组学生信息,并利用该初始密码成功登录。

漏洞一:任意手机号绑定
在“绑定手机”功能处,输入已获取的学生身份信息及攻击者自己的手机号,发现可以直接绑定成功。这意味着攻击者可以接管该校园卡账户,并进行余额转账等操作。


漏洞成因分析:
该功能仅在前端校验了输入信息的格式,后端在收到绑定请求后,未严格校验该手机号是否已被其他账户绑定,也未验证当前操作者是否为本人的合法性,直接执行了绑定操作。正确的逻辑漏洞防御应包含:接收请求 → 验证会话或Token → 查询数据库校验手机号绑定状态 → 验证用户提交的身份信息是否与数据库匹配 → 执行绑定。
漏洞二:JS审计发现后台并越权访问
对前端JavaScript文件进行审计,发现代码中硬编码了后台管理系统的地址及默认账号。

尝试登录时提示密码错误。继续审计JS代码,通过已知账号进行全局搜索,发现了另一个默认的管理员账号。

尝试使用常见的弱口令(如与用户名相同、123456等)成功登录后台管理系统。

在后台中,存在数据导出功能,导致大量学生敏感信息泄露(姓名、学号、手机号、身份证号),这正好为“漏洞一”的利用提供了充足的数据源,形成组合攻击链。
深入挖掘:任意身份注册与垂直越权
分析发现系统存在四类角色:default(学生)、teacher(教师)、admin(管理员)、superadmin(超级管理员)。
-
任意身份注册:在前台用户注册处拦截请求包,将role参数修改为admin,即可直接注册一个管理员身份的账号。

-
垂直越权:使用一个普通管理员账号,在后台创建用户时,同样通过修改请求包中的role参数为superadmin,成功创建出超级管理员账号,实现了从低权限到高权限的垂直越权。
基于JWT令牌的跨平台越权通杀
使用网络空间搜索引擎对相关系统指纹进行检索,发现大量同类厂商搭建的系统。在测试水平越权时,发现一个更严重的跨租户漏洞。
攻击过程:
- 登录A学校的管理员账号。
- 拦截创建用户的请求数据包。
- 仅将请求的
Host头修改为B学校的域名,其他所有参数(包括Cookie中的JWT令牌)保持不变,发送请求。
- 请求成功,在B学校的系统中创建了一个管理员账号。

漏洞根源分析:
问题的核心在于系统使用的身份验证令牌(JWT)设计存在缺陷。令牌的生成与校验未与具体的学校租户绑定,导致一个学校的有效令牌可以被滥用于访问其他学校的资源。
有问题的JWT生成与校验逻辑模拟:
// JWT生成 - 未绑定学校ID
function generateToken($user) {
$payload = [
'account' => $user['account'],
'role' => $user['role'], // 如:admin
'exp' => time() + 7200
// 缺少 `school_id` 字段
];
return jwt_encode($payload, JWT_SECRET);
}
// 注册接口 - 未校验令牌所属学校
function registerUser() {
$token = jwt_decode(getBearerToken(), JWT_SECRET);
if ($token->role !== 'admin') {
http_response_code(403);
exit('forbidden');
}
// 根据当前访问的Host动态获取学校ID
$current_school = getSchoolByHost($_SERVER['HTTP_HOST']);
$data = json_decode(file_get_contents('php://input'), true);
// 致命漏洞:直接使用当前Host对应的school_id,未校验token中的用户是否属于该学校
$sql = "INSERT INTO users (username, role, school_id) VALUES (?, ?, ?)";
db_execute($sql, [$data['username'], $data['role'], $current_school['id']]);
}
安全修复方案:
应在JWT负载(Payload)中嵌入不可篡改的租户标识(如school_id),并在每个业务接口的入口进行强制校验。
// 修复后的JWT生成
function generateToken($user) {
$payload = [
'account' => $user['account'],
'role' => $user['role'],
'school_id' => $user['school_id'], // 关键:绑定到具体学校
'exp' => time() + 7200
];
return jwt_encode($payload, JWT_SECRET);
}
// 修复后的注册接口
function registerUser() {
$token = jwt_decode(getBearerToken(), JWT_SECRET);
if ($token->role !== 'admin') {
http_response_code(403);
exit('not admin');
}
$current_school = getSchoolByHost($_SERVER['HTTP_HOST']);
// 核心修复:校验令牌中的学校ID与当前访问的Host是否匹配
if ($token->school_id != $current_school['id']) {
http_response_code(403);
exit('cross tenant access');
}
$data = json_decode(file_get_contents('php://input'), true);
// 强制使用令牌中的school_id,防止参数污染
$sql = "INSERT INTO users (username, role, school_id) VALUES (?, ?, ?)";
db_execute($sql, [$data['username'], $data['role'], $token->school_id]);
}
总结
本次渗透测试从一个简单的弱密码入手,通过信息收集、JS审计、逻辑漏洞挖掘,最终发现了涉及认证和授权层面的深层次水平越权漏洞。其中,基于JWT的跨租户越权问题尤其值得开发者警惕,在设计多租户SaaS系统时,必须将租户隔离作为身份认证与授权校验的核心环节。安全测试的成功离不开耐心细致的观察和对每一处技术实现细节的深入思考。