
1. 从一次真实的越权访问说起为什么权限控制总在“意料之外”出问题几年前我参与一个电商项目的安全审计遇到一个典型的场景。用户A登录后在“我的订单”页面能看到自己的订单列表URL大概是https://example.com/order?user_id123。出于好奇A把浏览器地址栏里的user_id123改成了user_id124然后回车。页面刷新后他竟然看到了用户B的完整订单信息包括收货地址、购买商品和支付金额。用户A没有进行任何复杂的攻击只是修改了一个肉眼可见的参数就完成了一次“越权”访问。这个漏洞我们内部称之为“水平越权”。而更严重的情况是如果用户A通过某种方式将user_id参数替换为一个管理员ID或者直接访问一个本应是管理员后台的路径如/admin/user/list并且成功进入了那这就是“垂直越权”。这两个词——“水平越权”和“垂直越权”——是Web应用安全特别是渗透测试领域中最经典、最高频、也最容易被开发者忽视的漏洞类型。它们不像SQL注入或XSS那样需要构造特殊的攻击载荷其核心往往在于服务器端逻辑的“想当然”和“信任过度”。开发者潜意识里认为“前端已经通过菜单隐藏了管理员入口普通用户就找不到”、“登录后的用户ID是从Session里取的不会错”、“这个API只有APP会调用参数是固定的”。正是这些“认为”构成了权限防线上的巨大缺口。在渗透测试中越权漏洞的挖掘是基本功也是体现测试者思维缜密度的关键环节。它不依赖于复杂的工具链更多是依靠对业务逻辑的深度理解和对每个请求的“不信任”审视。无论是手动测试还是结合Burp Suite等工具进行半自动化测试其目标都是一致的验证系统在每个环节是否都严格执行了“你是谁你能干什么”这条铁律。接下来我将结合多年实战经验为你彻底拆解这两种越权漏洞的原理、测试方法论、实战案例以及修复思路。2. 权限模型的基石理解水平越权与垂直越权的本质区别在深入测试之前我们必须从概念上厘清两者。很多初学者的困惑在于一个漏洞到底是水平的还是垂直的其实判断标准非常清晰看越权所访问的资源或功能是否超越了当前用户角色本身的权限层级。2.1 水平越权同角色内的“串门”水平越权也叫作“访问控制缺失Insecure Direct Object References, IDOR”。它的场景发生在相同权限级别的用户之间。比如都是普通用户、都是VIP会员、都是某个部门的员工。核心问题应用程序在处理对某个数据对象如订单、消息、个人资料的访问请求时没有验证当前登录的用户是否与该数据对象存在合法的所属或关联关系。生活化类比这就像一栋公寓楼每户人家用户的门牌号对象ID是顺序排列的。物业系统只在大楼门口检查了你是不是业主是否登录但当你走到123号房对象ID123门前时物业并没有再次核对你的钥匙是否能打开这扇特定的门。结果住在122号的你顺手就打开了123号的门。典型特征参数可控请求中直接包含用于标识数据对象的参数如id、user_id、order_no、file_id等。可预测这些ID往往是自增数字、时间戳或经过简单编码的字符串容易被枚举或猜测。无归属校验后端接口接收到id456的请求后直接执行SELECT * FROM orders WHERE id 456而没有附加AND user_id current_user_id这样的条件。2.2 垂直越权普通用户的“升职记”垂直越权是指低权限用户能够访问或执行高权限用户如管理员才能使用的功能或数据。这是一种更严重的漏洞因为它可能直接导致系统被接管、敏感数据全量泄露。核心问题应用程序仅通过前端UI如菜单、按钮的显示/隐藏来控制功能访问或者在后端对功能入口的权限校验存在遗漏。生活化类比公司里查看个人工资单是员工权限查看全公司薪资报表是HR总监权限。如果系统只在网页上隐藏了“全公司薪资报表”的链接但对应的后台API地址/api/salary/company-report却没有做任何权限检查那么任何一个知道这个地址的员工都可以直接访问并下载机密报表。典型特征功能或路径暴露管理员后台的URL路径、特定的API端点、隐藏的功能参数如?actiondeleteAllUsers可能被低权限用户通过爬虫、源码分析或猜测发现。前端依赖症完全依赖前端JavaScript来禁用按钮或隐藏菜单后端接口“来者不拒”。角色/权限校验缺失后端在处理请求时没有验证current_user.role是否为admin或者没有检查用户是否拥有执行该操作的权限令牌。为了更直观地区分我们可以看下面这个对比表格特征维度水平越权垂直越权权限层级同一层级内用户A vs 用户B不同层级间普通用户 vs 管理员攻击目标同类型其他用户的数据更高权限角色的功能或所有数据漏洞关键点数据对象访问缺乏所属权校验功能入口或API缺乏角色/权限校验危害程度通常为单个或批量用户数据泄露可能导致系统沦陷、核心数据泄露测试焦点操作带ID参数的增删改查接口尝试访问高权限路径、使用高权限功能参数理解这个本质区别能帮助我们在测试时快速定位方向。看到一个修改个人资料的接口我们首先考虑水平越权能否修改别人资料看到一个疑似管理功能的URL我们首先考虑垂直越权低权限用户能否访问。3. 实战演练手把手测试水平越权漏洞理论清晰后我们进入实战。水平越权的测试思路可以概括为“找参数、改参数、看响应”。下面我以一个虚拟的博客系统为例拆解完整测试流程。3.1 测试环境与工具准备假设我们测试的目标是一个简单的博客平台拥有以下功能用户登录、注册。登录后可以发布、编辑、删除自己的博客文章。可以查看其他用户的公开文章。工具准备浏览器 开发者工具用于常规浏览、抓取网络请求。Chrome或Firefox均可。Burp Suite Community/Professional渗透测试核心工具用于拦截、重放、修改HTTP/HTTPS请求。社区版对于手动测试越权漏洞完全够用。两个测试账号我们需要至少两个同一权限级别的账号。例如用户Atest_user_a 密码Password123!用户Btest_user_b 密码Password123!浏览器多用户模式或不同浏览器方便同时保持两个用户的登录状态避免Session冲突。3.2 测试流程与案例拆解步骤一识别潜在的攻击面登录test_user_a进行正常操作同时打开浏览器开发者工具的“网络Network”选项卡记录所有操作发出的HTTP请求。查看自己的文章列表访问https://blog-test.com/my-articles。观察请求发现它通过GET请求调用了一个APIGET /api/articles?author_id1001。响应返回了用户AID为1001的所有文章数组。查看某一篇文章详情点击一篇文章URL变为https://blog-test.com/article/5001。网络请求显示调用了GET /api/article/5001。编辑自己的文章点击编辑按钮进入编辑页面https://blog-test.com/article/5001/edit。页面加载时会先请求GET /api/article/5001获取内容提交保存时发送POST /api/article/5001/update请求Body里包含标题、内容等。删除自己的文章点击删除通常是一个DELETE /api/article/5001请求或POST /api/article/5001/delete。关键发现几乎所有关键操作增、删、改、查都关联着一个唯一的数字ID如5001。这个article_id就是我们测试的重点。步骤二实施越权测试现在我们换到test_user_b的环境或者用Burp Suite拦截test_user_a的请求进行修改。案例1越权查看信息泄露在test_user_a的会话中我们已知查看文章详情的API是GET /api/article/5001。我们推测test_user_b可能也有文章假设其文章ID是连续的可能是5002、5003。在test_user_b的浏览器中直接访问https://blog-test.com/api/article/5001。观察结果漏洞存在成功返回了文章ID为5001的完整内容属于用户A且页面没有提示“无权访问”。这说明“查看”功能存在水平越权。漏洞不存在返回403 Forbidden、{error: Access denied}或重定向到错误页面。案例2越权修改数据篡改这是更危险的漏洞。我们测试编辑功能。在test_user_a的会话中用Burp Suite拦截编辑文章后提交的POST /api/article/5001/update请求。将整个请求发送到Burp的“Repeater”模块。在Repeater中我们不修改请求中的Cookie或Session这代表我们仍以test_user_a的身份但将请求路径中的article_id从5001修改为我们猜测的、属于test_user_b的文章ID例如5002。同时修改请求Body中的标题和内容。发送这个修改后的请求。观察结果漏洞存在服务器返回200 OK或{success: true}表示修改成功。随后用test_user_b账号登录查看发现其文章内容确实被篡改。漏洞不存在服务器返回403、404表示对象不存在或无权访问或校验错误。案例3越权删除数据破坏测试删除接口方法与修改类似。拦截test_user_a发出的DELETE /api/article/5001请求。在Repeater中将路径中的ID改为5002并发送。观察结果漏洞存在返回成功。用户B的文章消失。漏洞不存在返回权限错误。注意在真实测试中尤其是授权测试严禁对非自己的数据进行真正的修改或删除操作。正确做法是使用专门为测试创建的、属于你自己的两个账号。如果必须测试生产环境应在请求中尝试将数据修改为一个无意义的、可识别的测试值如将标题改为“TEST_BY_A”并在测试后立即恢复或与项目方明确测试边界。步骤三参数挖掘与模糊测试并非所有ID都像article_id这样明显。我们需要更深入地挖掘其他对象ID个人资料profile_id、上传的文件file_id、地址address_id、私信message_id等。非ID参数有时标识符可能是用户名username、邮箱、手机号。尝试将当前用户的username参数修改为其他用户的用户名。批量枚举如果发现一个接口存在水平越权且ID是连续的可以使用Burp Suite的“Intruder”模块进行批量枚举。例如设置article_id为载荷从5001递增到5020观察哪些请求返回了200状态码和有效数据从而一次性发现大量越权数据点。JSON/XML参数不要只盯着URL查询参数?id1。仔细检查POST请求的Body特别是JSON或XML格式的内容其中可能包含目标对象ID。3.3 常见绕过技巧与注意事项有些应用会做一些基础的防御但往往不彻底依赖前端传递的用户ID请求Body里除了article_id可能还有一个user_id。后端可能只检查了user_id是否与Session中的用户ID一致却忽略了article_id与user_id的关联性。测试时可以尝试将user_id改为当前用户的ID而article_id改为他人的看是否绕过。使用GUID/UUID而非自增ID这增加了猜测难度但并非绝对安全。如果某个功能点如分享链接暴露了GUID那么这个GUID仍然是一个直接对象引用。测试关键在于找到这些引用点。“盲”水平越权某些操作如“标记为已读”、“收藏”可能没有直接的视觉反馈。测试时需要对比操作前后目标用户数据的状态变化。这通常需要两个测试账号配合观察。水平越权测试的核心是思维转变从“系统应该会校验”转变为“系统可能在任何地方忘记校验”。每一个携带标识符的请求都是一个潜在的测试点。4. 深入腹地垂直越权漏洞的探测与利用垂直越权的危害性更大测试思路也从“数据归属”转向了“功能边界”。我们的目标是以普通用户身份触摸到系统设计的权限天花板之上。4.1 功能入口发现看不见的路径垂直越权测试的第一步是信息收集即发现那些本应对你隐藏的功能入口。前端代码分析查看网页源码在浏览器中右键“查看页面源代码”。搜索admin、manage、config、delete、export、listall等关键词。有时管理功能的JavaScript代码或注释会泄露路径。分析JS文件查看页面加载的.js文件。前端路由如React Router、Vue Router的定义可能包含所有路径包括权限控制路径。禁用JavaScript尝试在浏览器设置中禁用JS后刷新页面。有些应用仅靠JS隐藏管理链接禁用后链接可能暴露出来。目录与文件枚举使用工具如DirBuster、gobuster或ffuf对Web根目录进行暴力猜解尝试发现像/admin、/backend、/wp-admin、/manager、/api/admin这样的隐藏目录。常见的管理后台入口文件名admin.phpadmin.aspadministratorlogin.aspxmanage.html等。API接口探测使用Burp Suite抓取所有普通用户流程中的请求。关注那些看起来“权限很高”的API路径例如包含/api/users普通用户可能只有/api/profile、/api/system/、/api/logs、/api/config的请求。即使当前请求返回403它也暴露了接口地址。分析API文档如果应用有Swagger UI/swagger/api-docs等接口文档页面普通用户能否访问文档中可能列出了所有API包括需要管理员权限的。4.2 权限校验绕过实战发现疑似高权限入口后接下来就是尝试访问。案例1直接访问管理员后台URL普通用户登录后直接在浏览器地址栏输入https://blog-test.com/admin。可能的结果重定向到登录页或首页说明有前端或后端的路由守卫但可能不够完善。返回403 Forbidden说明权限校验生效这是正常情况。成功进入管理员后台界面严重垂直越权漏洞前端可能没有隐藏链接后端也完全没有校验。进入后台登录口这不算漏洞需要管理员凭证。但可以尝试暴力破解等属于另一类问题。案例2越权调用管理员API这是更常见且危险的情况。假设我们通过抓包或猜测发现了一个管理用户列表的APIGET /api/admin/users。在普通用户test_user_a的会话中用Burp Suite构造一个请求GET /api/admin/users HTTP/1.1并带上test_user_a的Cookie。发送请求。观察结果成功返回所有用户列表垂直越权漏洞实锤。普通用户能获取敏感信息。返回403权限校验正常。返回{error: Unauthorized}权限校验正常。返回空数组[]或{data: []}需要警惕这可能是一种不安全的实现后端执行了查询如SELECT * FROM users但因为当前用户角色是普通用户前端或后端逻辑过滤了结果。但查询本身被执行了可能存在性能问题或通过错误信息泄露数据。案例3参数提权某些功能通过参数来控制操作范围普通用户和高权限用户调用的是同一个接口。假设普通用户有一个“查看日志”的功能调用GET /api/logs?typemy_activity只查看自己的活动日志。尝试修改参数GET /api/logs?typeall_activities或GET /api/logs?scopesystem。假设“删除评论”接口为POST /api/comment/delete普通用户只能删除自己的评论。观察请求发现可能有一个is_admin参数默认为false。尝试将其修改为true并发送看是否能删除任意评论。4.3 组合攻击与深度测试垂直越权有时需要结合其他漏洞或技巧。水平垂直组合拳先通过一个水平越权漏洞获取到某个高权限用户的特定数据ID例如通过越权读取管理员日志发现一个特殊的配置ID。然后尝试用普通用户身份去调用一个需要高权限才能操作该ID的接口。Cookie/Session篡改如果应用将用户角色role或权限标志isAdmin直接存储在客户端的Cookie或JWT Token中那么通过修改这些值为高权限值如roleadmin可能直接实现垂直越权。这属于不安全的客户端校验在测试时务必检查Cookie和Token内容可使用Burp的Decoder模块分析JWT。Referer或Origin绕过有些简陋的权限校验可能只检查请求是否来自“管理后台页面”通过Referer头。普通用户页面发起的请求Referer是普通页面。我们可以尝试在Burp中直接删除或伪造Referer头为管理后台地址看是否能绕过校验。重要提示垂直越权测试的破坏性可能很强。在未授权的情况下绝对不要在真实系统上执行“删除所有用户”、“关闭系统”等危险管理操作。测试应集中在信息读取GET请求和低风险的操作验证上。5. 防御之道从开发视角构建坚固的权限防线作为渗透测试人员我们的价值不仅是发现问题更要能提出切实可行的解决方案。修复越权漏洞必须在设计之初就贯彻“最小权限原则”和“服务端强制校验”。5.1 水平越权防御策略核心思想每次数据访问必须显式关联当前用户上下文。使用不可预测的标识符避免使用自增整数ID作为数据对象的唯一访问凭证。可以采用UUID、加密的随机字符串或由“用户ID随机数”组合生成的复杂ID。但这只是增加了攻击成本并非根本解决方案。攻击者如果通过其他途径如分享功能获得了UUID同样可以尝试越权。服务端强制所有权校验最根本的解决方案在所有数据访问的持久层操作SQL、ORM查询中强制加入当前用户ID作为查询条件。错误示例易产生漏洞# 伪代码仅根据传入的order_id查询 order Order.query.get(order_id) if order: return order正确示例# 伪代码查询时关联当前用户ID current_user_id session.get(user_id) order Order.query.filter_by(idorder_id, user_idcurrent_user_id).first() if order: return order else: return {error: Order not found or access denied}, 404注意即使前端传递了user_id后端也绝不能信任必须从可信的Session或Token中获取当前用户身份。使用访问控制中间件或装饰器在Web框架中设计统一的权限检查层。例如在调用业务逻辑前先通过一个函数检查“当前用户是否有权操作目标资源”。示例Flask装饰器def check_article_owner(func): wraps(func) def wrapper(article_id, *args, **kwargs): current_user_id get_current_user_id() article Article.query.get(article_id) if not article or article.user_id ! current_user_id: abort(403) # 直接拒绝 return func(article_id, *args, **kwargs) return wrapper app.route(/api/article/int:article_id/update, methods[POST]) login_required check_article_owner # 添加所有权检查装饰器 def update_article(article_id): # 业务逻辑... pass5.2 垂直越权防御策略核心思想所有功能入口和API端点必须进行基于角色/权限的强制校验。RBAC基于角色的访问控制模型明确定义系统中的角色如uservipadminsuper_admin和权限如article:readarticle:write:ownarticle:delete:anyuser:manage。将权限分配给角色将角色分配给用户。在后端每个API或路由处理函数执行前检查当前用户是否拥有执行该操作所需的权限。服务端路由守卫不要依赖前端路由隐藏。在后端路由定义或控制器入口处进行角色校验。示例Node.js Express中间件const requireAdmin (req, res, next) { if (req.user req.user.role admin) { next(); // 继续执行后续处理 } else { res.status(403).json({ error: Admin access required }); } }; // 管理员专属路由 app.get(/api/admin/users, requireAdmin, adminController.listUsers); app.post(/api/system/config, requireAdmin, adminController.updateConfig);前后端分离架构下的权限设计前端根据用户角色/权限决定菜单和按钮的显示。但后端必须对每一个API请求进行独立的权限校验。前端隐藏只是用户体验后端校验才是安全底线。建议使用JWT等Token机制并在Token中携带用户的角色和权限列表但需注意Token泄露风险关键操作应再次验证。定期进行权限审计与测试开发阶段代码审查时重点关注所有涉及数据查询和功能访问的代码检查是否有所有权和角色校验。测试阶段将越权测试尤其是垂直越权纳入自动化测试用例或手动渗透测试的必测项。上线后定期进行黑盒和白盒的安全扫描模拟低权限用户尝试访问高权限接口。5.3 安全开发习惯默认拒绝所有新的API端点默认设置为拒绝所有访问再显式地添加允许的权限规则。使用安全的框架和库许多成熟的Web框架如Spring Security Django Guardian Laravel Gates/Policies都提供了强大的声明式权限控制机制优先使用这些机制而非自己从头实现。日志与监控记录所有权限校验失败的请求包括尝试访问的资源、用户IP、用户ID便于发现攻击行为和安全审计。越权漏洞的根源在于“信任”的错位——过度信任前端、过度信任传入的参数、过度信任用户不会进行非常规操作。作为开发者必须时刻保持“零信任”心态在服务端对每一次请求都进行严格的、上下文相关的权限审查。作为测试者我们的任务就是扮演那个“不守规矩”的用户用各种方法去挑战系统的信任边界从而帮助它变得更加坚固。