校园系统越权漏洞实战挖掘:从IDOR到垂直越权的完整攻防解析

📅 2026/6/25 20:54:33
校园系统越权漏洞实战挖掘:从IDOR到垂直越权的完整攻防解析
1. 项目概述一次典型的校园系统越权漏洞挖掘实录最近在和一些刚入行的朋友交流时发现大家对“越权漏洞”这个概念既熟悉又陌生。熟悉是因为这个词在各类安全报告里出现频率极高陌生是因为很多人不知道在实际渗透测试中如何系统性地去发现和利用它。正好前段时间我参与了一次针对某中学内部管理系统的授权安全测试整个过程堪称一次教科书式的越权漏洞挖掘案例。这个案例非常典型涉及到的系统架构、业务逻辑和漏洞成因在很多中小型企事业单位、教育机构的系统中都能找到影子。今天我就把这个过程完整地复盘一遍从踩点、信息收集、漏洞发现、利用链构造到最终的报告希望能给想入门漏洞挖掘特别是逻辑漏洞挖掘的朋友们提供一个清晰的实战思路。这次的目标是一个中学的“智慧校园综合管理平台”。这类系统通常集成了学生信息管理、成绩查询、教职工办公、后勤报修等多个模块用户角色复杂学生、教师、班主任、教务处管理员、校领导等数据敏感一旦出现越权后果可能非常严重。我们的任务就是模拟攻击者在不具备高权限账户的情况下尝试访问或操作本不该有权限的数据和功能。2. 前期侦察与目标分析2.1 目标系统画像勾勒在开始任何测试之前盲目地“戳端口、扫目录”效率很低。我们需要先给目标画个像。通过公开信息搜集我们了解到这个平台是一个B/S架构的Web应用采用常见的“前端后端API”模式。前端是Vue.js后端是JavaSpring Boot框架数据库是MySQL。这些信息可以通过查看网页源代码中的注释、引入的JS/CSS文件、HTTP响应头如X-Powered-By: Spring Boot甚至一些报错信息获得。更重要的是业务逻辑分析。我们以访客身份浏览了系统的登录页和找回密码等公开页面。从页面文字和功能描述中我们梳理出系统可能存在的几类角色学生查看个人信息、课表、成绩、提交作业。教师管理所教班级的学生、录入成绩、发布通知。班主任管理本班学生详细信息、审核请假条。教务处管理员管理全校学生、教师档案设置课程权限最高。 系统采用“用户ID密码验证码”登录登录后跳转到统一的仪表盘再通过侧边栏菜单进入各功能模块。2.2 关键入口点探测越权漏洞的核心在于“权限校验的缺失或失效”。因此我们的侦察重点在于找出所有涉及“对象标识符”的接口。所谓对象标识符就是URL或请求参数中用于唯一指定某个数据对象的ID例如/api/student/info?id1001查询ID为1001的学生信息/api/grade/update?examId205studentId1001更新某次考试中某个学生的成绩/api/leave/approve?leaveId58审批ID为58的请假条我们的策略是在获得一个低权限账号比如一个普通学生账号后系统地遍历这些接口尝试修改这些ID参数访问其他用户的数据。首先我们需要找到一个可用的低权限账号。对于教育系统有时会有公开的演示账号或者通过“忘记密码”功能结合社工技巧但需在授权范围内来推测账号格式如学号、工号。在本案例中我们通过测试方提供了一个测试用的学生账号stu_20230001。登录后我们立即打开浏览器的开发者工具F12切换到Network网络标签页并勾选“Preserve log”保留日志。然后在页面上进行常规操作点击查看“我的信息”、查看“我的成绩单”、申请“请假”等。所有前端发往后端的HTTP请求都会在这里一览无余。注意很多现代前端应用使用API接口数据通过AJAX请求异步加载页面的URL可能不会变化。因此观察网络请求比看浏览器地址栏更重要。3. 漏洞挖掘过程水平越权与垂直越权的发现3.1 水平越权IDOR的快速验证水平越权Insecure Direct Object References, IDOR是最常见的越权类型即用户A可以操作属于用户B的同类数据对象。我们首先从最直观的“个人信息查询”接口入手。在“我的信息”页面网络请求中捕获到这样一个请求GET /api/personal/v1/profile?userId20230001 HTTP/1.1 Host: school.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...响应返回了该学生的姓名、班级、学号、身份证号脱敏、联系方式等。这里userId20230001就是关键的对象标识符。它很可能就是我的当前登录账号stu_20230001对应的用户ID。那么如果我把它改成20230002假设是同班同学服务器会返回谁的数据我们使用Burp Suite的Repeater模块来重放这个请求。将请求中的userId参数值从20230001修改为20230002其他头部特别是Authorization令牌保持不变发送。结果服务器返回了状态码200 OK并且响应体里完整包含了学号为20230002的学生的个人信息漏洞确认系统后端在处理/api/personal/v1/profile接口时只验证了用户是否登录即Authorization令牌是否有效但没有校验当前登录的用户ID是否与请求的userId参数一致。这就导致了任何登录用户都可以通过遍历userId来获取其他用户的敏感信息。这是一个典型的水平越权漏洞。3.2 垂直越权与功能滥用挖掘水平越权往往伴随着更危险的垂直越权可能。垂直越权是指低权限用户获得了高权限用户的功能。我们继续深挖。在教师端有一个“成绩录入”功能。我们当然没有教师账号但我们可以观察学生账号相关的成绩查询接口。我们发现一个查询某次考试详细成绩的接口GET /api/grade/v1/detail?examId101classId12 HTTP/1.1这个接口看起来是根据examId考试ID和classId班级ID来查询整个班级的成绩列表。那么是否存在一个“更新/录入”单个学生成绩的接口通过爬取前端JS文件或者通过模糊测试工具对/api/grade/目录进行探测我们发现了疑似接口POST /api/grade/v1/update HTTP/1.1 Content-Type: application/json {examId: 101, studentId: 20230001, course: math, score: 95, comment: 优秀}这是一个POST请求用于更新成绩。我们用自己的学生账号尝试直接向这个接口发送请求哪怕是把我的数学成绩从80改成95。结果返回了403 Forbidden提示“权限不足”。这说明接口有做权限校验学生角色不能访问。但是校验是否彻底我们注意到成绩管理模块里教师也只能修改自己所教班级的学生成绩。那么如果有一个教师账号他能否修改其他班级、其他教师名下的学生成绩呢这属于教师角色内部的水平越权也可能是一种“功能滥用”式的垂直越权如果系统本意是严格隔离。我们转换思路。在“请假申请”模块学生可以提交请假条然后由班主任审批。流程如下学生提交请假申请POST /api/leave/v1/apply生成一条请假记录状态为“待审批”并有一个唯一的leaveId例如 58。班主任审批接口可能是POST /api/leave/v1/approve?leaveId58actionapprove我们以学生身份尝试调用审批接口即使我们不知道确切的URL可以尝试常见路径如/api/leave/approve,/api/leave/audit。使用Burp Intruder对leaveId进行遍历同时猜测action参数approve,reject,pass,agree等。结果我们发现了接口/api/leave/v1/audit。当我们将请求方法从GET改为POST并携带参数leaveId58status1假设1代表通过时服务器返回了200 OK并且前端显示该请假条状态变成了“已通过”漏洞分析这是一个严重的垂直越权漏洞。系统在/api/leave/v1/audit接口上只校验了用户是否登录但没有校验登录用户的角色是否为“班主任”更没有校验该班主任是否是请假学生所在班级的班主任。导致任何登录用户包括学生都可以审批任何请假条。这完全破坏了审批流程的业务逻辑。3.3 参数污染与接口组合拳单一的越权可能危害有限但组合起来就能形成强大的攻击链。例如我们利用之前的信息查询越权可以遍历userId获取全校学生的学号和姓名列表。然后利用请假审批越权可以为他们批量“批准”请假条造成管理混乱。更进一步我们发现了“消息通知”接口。教师可以向指定班级发送通知。接口如下POST /api/msg/v1/sendToClass HTTP/1.1 Content-Type: application/json {classId: 12, title: 考试通知, content: 明天上午数学考试。}我们尝试用学生账号调用此接口。返回403权限校验生效。但是我们注意到发送成功后前端会跳转到消息列表页列表页查询接口是GET /api/msg/v1/list?classId12page1size20这个list接口学生有权限访问吗测试发现可以学生可以查看发送给班级12的所有通知。那么如果classId参数存在水平越权呢我们将classId改为其他班级的ID例如11。成功返回了班级11的所有内部通知其中可能包含教学安排、会议纪要等敏感信息。这里暴露了两个问题1列表接口存在水平越权2更重要的是权限校验的不一致性。系统禁止低权限用户“写”发送通知却允许其“读”查看通知而“读”的接口又未做好对象级权限校验。这种设计缺陷非常普遍。4. 漏洞原理深度剖析与修复方案4.1 为什么会出现越权漏洞根本原因在于“信任前端传递的参数”和“权限校验的粒度不足”。我们来看看后端伪代码可能是什么样子漏洞代码示例查询个人信息GetMapping(/api/personal/v1/profile) public ResponseEntity getUserProfile(RequestParam Long userId) { // 1. 从JWT令牌中解析出当前登录用户的ID (currentUserId) Long currentUserId getCurrentUserIdFromToken(); // 2. 直接根据前端传来的userId查询数据库 UserProfile profile userService.getProfileById(userId); // 漏洞点 // 3. 返回结果 return ResponseEntity.ok(profile); }这段代码的问题在于它从令牌中解析了currentUserId但后续查询完全没有使用它它盲目地相信了前端传来的userId参数。正确的做法应该是将查询条件与当前用户绑定或者至少进行比对。正确的代码应该如下GetMapping(/api/personal/v1/profile) public ResponseEntity getUserProfile(RequestParam Long userId) { Long currentUserId getCurrentUserIdFromToken(); // 方案A强制绑定当前用户如果接口设计就是查自己 // userId参数甚至可以去掉直接使用currentUserId UserProfile profile userService.getProfileById(currentUserId); // 方案B如果接口设计允许查他人如老师查学生则需进行角色和业务关系校验 if (!securityService.canViewUserProfile(currentUserId, userId)) { // 校验当前用户是否有权限查看目标用户的资料 // 例如老师只能查看自己班级的学生 throw new AccessDeniedException(无权查看该用户信息); } profile userService.getProfileById(userId); return ResponseEntity.ok(profile); }对于审批接口的漏洞问题类似PostMapping(/api/leave/v1/audit) public ResponseEntity auditLeave(RequestParam Long leaveId, RequestParam Integer status) { // 缺少对当前用户角色的校验 LeaveRecord leave leaveService.findById(leaveId); leave.setStatus(status); leaveService.update(leave); return ResponseEntity.ok(审批成功); }缺少了关键的步骤判断当前用户是否为班主任并且是该请假条所属学生的班主任。4.2 修复方案建议给开发团队提供可操作的修复建议比单纯报告漏洞更重要。我通常会从三个层面给出方案1. 代码层修复立即执行强制会话绑定对于查询、修改、删除等操作后端必须从可信的会话如JWT token中获取主体标识用户ID、角色并将其作为数据库查询的必要条件。例如SELECT * FROM orders WHERE user_id ? AND order_id ?。引入统一的权限校验框架在Spring Boot中可以使用Spring Security的PreAuthorize注解或自定义AOP切面在方法执行前进行细粒度校验。PostMapping(/api/leave/v1/audit) PreAuthorize(permissionService.canAuditLeave(#leaveId)) public ResponseEntity auditLeave(RequestParam Long leaveId, RequestParam Integer status) { // 业务逻辑 }其中permissionService.canAuditLeave方法实现了复杂的业务规则校验。2. 架构层优化中期规划实施RBAC基于角色的访问控制与ABAC基于属性的访问控制结合RBAC控制功能菜单访问如“审批请假”按钮是否显示ABAC控制数据级权限如“只能审批本班学生的请假条”。API网关统一鉴权在请求到达业务服务之前由网关对令牌有效性、接口黑白名单进行初步校验减轻业务服务压力。后端对前端隐藏真实ID使用无意义的UUID或加密后的令牌代替数据库自增ID作为资源标识符增加攻击者猜测和遍历的难度。但请注意这并非银弹核心还是服务端的校验。3. 安全开发流程长期建设将越权检查纳入代码审查清单在CR环节重点审查所有涉及资源ID操作的接口。定期进行专项安全测试在测试阶段使用自动化工具如Burp Suite的Autorize插件配合手动测试专门针对越权漏洞进行扫描。安全意识培训让开发人员深刻理解“永远不要信任客户端传来的任何用于权限判断的参数”。5. 实战中的技巧与避坑指南5.1 信息收集阶段的技巧不要只看“显式”参数除了URL中的?id更要关注POST请求的JSON/XML body、Cookie甚至HTTP头部中可能存在的标识符。例如X-User-Id: 1001这样的自定义头。关注批量操作接口像/api/users/batchDelete、/api/data/export?ids1,2,3这类接口一旦越权危害呈指数级放大。利用JS文件映射API路由前端Vue/React应用通常有router.js或api.js文件里面定义了所有接口路径是宝贵的“地图”。5.2 漏洞利用与验证的注意事项遵守测试范围绝对不要测试授权范围以外的系统或功能。本次测试仅限于指定的“智慧校园平台”。使用无害的POC验证越权时尽量执行“读”操作避免“增删改”。例如验证信息查询越权比验证删除用户越权更安全。如果必须“写”也要使用测试账号或构造无实际影响的数据。注意请求顺序和状态依赖有些操作需要前置状态。例如审批请假条需要请假条处于“待审批”状态。你需要先利用一个账号创建这个状态如学生提交请假再用另一个账号去测试越权审批。Burp Suite插件推荐Autorize自动化越权测试神器。配置好低权限和高权限账号的Cookie/Token它能自动重放所有请求并对比响应快速找出差异。Param Miner可以自动发现那些不常见的、可能被遗漏的参数如uid,key,ref等。5.3 报告撰写要点一份好的漏洞报告能帮助开发团队快速理解并修复问题。我的报告通常包含漏洞标题清晰明了如“智慧校园平台个人信息查询接口存在水平越权漏洞”。风险等级根据CVSS标准或内部规范评定如高危、中危。漏洞详情请求URL完整的HTTP请求。请求参数指出存在问题的参数如userId。攻击步骤一步一步描述如何复现1. 使用账号A登录2. 捕获查询请求3. 修改参数为B的ID4. 成功获取B的信息。漏洞证明附上成功利用的截图或响应包关键部分。漏洞原理简要分析后端代码可能的问题所在。修复建议提供具体的代码修改方案或安全配置建议。影响范围评估受影响的功能模块、用户群体和数据量。6. 从SRC视角看教育系统漏洞挖掘本次挖掘的中学系统属于教育行业SRC安全应急响应中心关注的范畴比如EDUSRC。教育系统的特点使得它成为漏洞挖掘的热点资产复杂历史遗留系统多新老系统并存框架从ASP、PHP到Java、Python都有安全水平参差不齐。数据敏感包含大量学生、家长、教师的个人身份信息、联系方式、成绩、档案等数据价值高。用户安全意识薄弱师生群体庞大普遍网络安全意识不强弱口令、默认口令问题严重。开发运维投入有限很多学校系统由外包公司开发追求功能实现而忽视安全且后期维护不足。针对教育系统的漏洞挖掘除了常规的越权、注入、XSS还可以特别关注统一身份认证系统单点登录SSO这是整个数字校园的入口一旦突破全线崩溃。在线考试系统是否存在时间绕过、答案泄露、分数篡改等逻辑漏洞。家校通/移动APPAPP的API接口安全、数据传输加密、客户端反编译风险。文件上传功能成绩单、作业、通知附件上传处往往是获取Webshell的突破口。对于想入门SRC漏洞挖掘的朋友我的建议是从逻辑漏洞开始。相比于需要深厚经验的二进制漏洞或复杂的Web漏洞如SSRF、反序列化逻辑漏洞尤其是越权更依赖于对业务的理解和耐心的测试门槛相对较低但产出和价值非常高。找一个目标像侦探一样去梳理它的业务流程思考“如果我是开发者我会在哪里做校验”然后去验证这些校验是否真的存在且有效。这个过程本身就是一次极佳的学习和实战锻炼。