
1. 项目概述从“猫鱼”到权限边界的攻防实战最近在安全圈里一个叫“catfish”的靶场又火了起来连带“越权漏洞”这个老生常谈的话题也重新被推到了风口浪尖。很多刚入门Web安全的朋友一听到“越权”就觉得概念简单无非就是“不该看的看了不该改的改了”但真上手去挖、去复现、去理解其背后的业务逻辑和代码成因时往往一头雾水。这个“catfish”靶场恰恰就是一个绝佳的、用于深度剖析水平越权漏洞的实战环境。它不是那种一个点击就弹flag的玩具而是模拟了真实业务中用户权限体系的脆弱环节让你能像剥洋葱一样一层层拆解权限校验缺失的后果。简单来说这次我们要聊的就是如何以“catfish”靶场为蓝本彻底吃透水平越权漏洞。我会带你从漏洞原理的底层逻辑开始一步步分析靶场中的权限控制缺陷并手把手演示攻击链的构造。更重要的是我会分享在真实渗透测试和代码审计中寻找这类漏洞的实战思路与排查技巧。无论你是正在打靶场练手的安全新人还是想巩固Web安全基础的老兵相信这篇超详细的拆解都能让你对“越权”有全新的、更深刻的认识。我们不止于“怎么做”更要深究“为什么能这么做”以及“如何从根源上避免”。2. 漏洞核心原理与业务逻辑深度拆解2.1 什么是水平越权一个经典的业务场景类比要理解水平越权我们得先忘掉那些生硬的定义。想象一下你在一家银行每个客户都有一个专属的保险柜。银行的前台系统Web应用应该只允许你通过自己的钥匙身份凭证打开自己的保险柜访问自己的数据。水平越权漏洞就相当于这个系统的柜员犯了个错他虽然检查了你有没有银行的钥匙是否登录但却没仔细核对钥匙上刻的保险柜号码用户ID是不是你正要打开的那个。具体到Web应用里这个“保险柜号码”通常就是URL参数、POST数据包里的user_id、order_id、document_id这类资源标识符。一个存在水平越权漏洞的查看个人资料功能其请求可能长这样GET /api/user/profile?user_id12345。后端代码的逻辑如果是“哦这个请求带了Session用户是登录状态那就把user_id12345的资料返回吧。”——漏洞就产生了。因为后端没有将请求中的user_id12345与当前登录用户的真实ID比如是10001进行比对。攻击者只需要把user_id参数改成12456、12457……就能遍历查看其他所有用户的隐私资料。所以水平越权的本质是权限校验的“纵向通过横向缺失”。系统通过了身份认证你是合法用户但未完成授权校验你是否是这份数据的主人。这与垂直越权普通用户获取管理员权限有本质区别水平越权发生在同一权限等级的用户之间。2.2 Catfish靶场场景模拟漏洞滋生的典型温床“Catfish”靶场之所以成为学习水平越权的经典是因为它精心构建了一个非常贴近现实的业务场景——一个简易的用户中心或订单管理系统。这类系统通常包含以下功能模块也正是漏洞的高发区用户信息管理查看/编辑个人资料、修改密码、查看我的订单/文章。基于ID的资源访问通过id参数直接获取或修改某条具体记录。靶场通常会模拟这样的代码逻辑在处理诸如/user.php?actionviewid1这样的请求时后端脚本user.php可能只验证了用户是否登录isset($_SESSION[username])然后就直接执行了SQL查询SELECT * FROM users WHERE id $_GET[id]。这里缺失了最关键的一步AND id $_SESSION[user_id]。攻击者只需修改URL中的id值就能实现跨用户数据访问。注意在实际靶场或测试中id参数可能不那么明显它可能是数字也可能是经过编码或哈希的字符串但原理不变任何由客户端可控、用于指定唯一资源的标识符在没有与当前会话身份绑定的情况下被使用都是潜在的风险点。2.3 从参数到数据库漏洞触发链路全景分析一次完整的水平越权攻击其链路贯穿前后端。我们以“查看他人订单详情”为例拆解整个流程前端交互用户A登录后在“我的订单”列表点击某个订单。前端JS会构造请求例如GET /order/detail?order_noORD20230001A。请求发起浏览器将包含Session Cookie和参数order_noORD20230001A的请求发送至服务器。后端处理漏洞点接收请求从Cookie中解析Session确认当前登录用户为Auser_id1001。获取参数order_no。【致命缺失】直接拼接SQL查询SELECT * FROM orders WHERE order_no ORD20230001A。或者先从数据库查出订单信息然后没有检查查出来的订单所属用户owner_id是否等于1001。将查询结果订单详情返回给前端。攻击者操作用户A通过浏览器插件抓包获取该请求。他将参数修改为order_noORD20230001B这是用户B的订单号重新发送请求。漏洞触发后端重复第3步流程由于缺乏校验它成功查询并返回了属于用户B的订单详情。水平越权达成。这个链路清晰表明漏洞的核心责任在于服务端业务逻辑。无论前端如何隐藏、混淆ID只要最终决定数据访问权限的判断逻辑放在后端且存在缺陷漏洞就无法避免。3. Catfish靶场实战漏洞挖掘与利用详解3.1 环境搭建与初步侦察首先你需要一个“catfish”靶场环境。通常它可能是一个Docker镜像或PHP源码包。部署成功后我们第一步不是盲目测试而是进行侦察理解应用的功能结构。用户注册与登录创建两个测试账号例如userA(密码: 123456) 和userB(密码: 123456)。这模拟了两个同等权限的普通用户。功能点枚举以userA身份登录后点击所有可点击的链接。重点关注个人中心修改资料、头像、密码。“我的xxx”列表我的帖子、我的订单、我的收货地址。点击列表中的具体条目进入详情页或编辑页。流量抓取开启浏览器开发者工具F12的Network网络面板并勾选“Preserve log”保留日志。重复步骤2的所有操作观察浏览器发起了哪些HTTP请求。特别关注请求URL尤其是包含id,user_id,uid,docid等参数的。请求方法GET还是POST。POST请求的请求体Payload。3.2 关键参数识别与操纵在抓取的流量中寻找那些指向特定资源的请求。例如在“我的文章”列表里点击标题进入文章详情页你可能会看到这样一个请求GET /article.php?actionviewarticle_id5或者一个更RESTful风格的API请求GET /api/v1/users/1001/profile攻击思路登录userA账号访问属于A的资源如article_id5确保能正常看到。然后保持登录状态Session有效直接在浏览器地址栏或使用Burp Suite等工具修改参数值。场景一修改数字ID。将article_id5改为article_id6。刷新页面。如果显示了另一篇文章的内容而这篇内容在userA的列表里并不存在那么极有可能发生了水平越权你看到了userB或其他用户的文章。场景二修改资源路径中的ID。对于/api/v1/users/1001/profile将1001改为1002。如果返回了userB的个人资料信息漏洞存在。实操心得不要只测试相邻ID。有时开发者会通过奇偶、范围等方式做简单过滤虽然不安全。可以尝试跳跃式修改如从5改为100、1000。同时注意观察返回的数据格式。即使页面没有直接显示目标数据但API接口可能返回了JSON数据其中包含了敏感信息这也属于信息泄露式越权。3.3 使用Burp Suite进行高效测试手动修改URL适用于简单场景但高效、批量的测试离不开专业工具。这里以Burp Suite为例配置代理浏览器设置代理指向Burp如127.0.0.1:8080。拦截请求在Burp的Proxy-Intercept标签页确保拦截开启。在浏览器中触发一个带ID参数的请求如查看某条订单。发送到重放模块在Intercept页面看到请求后点击Action选择Send to Repeater。在Repeater中测试在Repeater标签页你会看到捕获的请求。找到目标参数如order_id101将其修改为其他值如order_id102。点击Send按钮发送请求。观察右侧响应体。对比与原始请求order_id101响应的差异。如果两者都能成功返回数据且数据结构相似但内容不同如收货人、商品信息不同则漏洞确认。使用Intruder进行批量探测对于可能存在漏洞的请求在Proxy历史或Repeater中右键选择Send to Intruder。在Intruder-Positions标签Burp通常会自动标记参数。确认order_id等参数被标记为攻击点如§101§。在Payloads标签设置Payload类型。例如使用Numbers类型生成从1到1000步长为1的数字序列。在Options标签可以设置Grep-Match来标记包含特定关键词如“地址”、“手机号”的响应便于快速识别成功请求。开始攻击。Intruder会自动化替换order_id并发送请求。你只需在结果列表中通过状态码、响应长度以及Grep匹配结果快速筛选出那些“本应失败却成功返回了数据”的请求这些对应的order_id就属于其他用户。3.4 绕过常见的前端防御假象有时前端会做一些看似“安全”的处理给新手造成迷惑隐藏字段编辑个人信息时表单里可能有一个input typehidden nameuser_id value1001。新手可能觉得这个值用户改不了。实际上通过Burp拦截提交的POST请求可以直接修改这个user_id的值。下拉框禁用前端JS可能根据登录信息禁用下拉框或填充数据但提交到服务器的参数依然可控。非直接ID参数参数名可能不是简单的id而是key、hash、token等。你需要分析这个值的生成规律。例如它可能是md5(user_id salt)。如果你能注册大量用户观察自己不同账号下该参数的变化或许能推断出算法从而构造其他用户的参数。在靶场中这种设计往往是为了增加难度但原理仍是校验是否在服务端完成。核心准则永远不要相信客户端传来的任何用于权限判定的标识符。你的测试重点必须放在服务端逻辑是否对“请求者”和“资源所有者”进行了强关联校验上。4. 漏洞挖掘的进阶思路与技巧4.1 不限于“增删改查”功能逻辑越权水平越权不仅发生在数据的“读”查看操作上“写”修改、删除操作同样高危且危害更大。修改信息如修改收货地址、绑定手机号、修改密保问题。测试方法用userA抓取修改自己信息的请求包将其中标识资源的参数如address_id替换为userB的地址ID然后提交。如果修改成功即造成了userB信息的篡改。状态操作例如在一个工单系统中“关闭”自己提交的工单。尝试用userA的请求去关闭userB的工单。关联操作例如在一个社交应用中“关注”某个用户。这个操作通常需要传递target_user_id。检查在关注请求中是否可以任意修改target_user_id来实现强制关注或取消关注他人虽然关注他人通常允许但某些敏感场景如黑名单、特别关注可能需要校验关系。测试时要关注应用的所有状态变更点。每一个“动作”背后都可能对应着一个API调用而这个调用可能需要资源标识符。4.2 参数污染与间接引用有时候漏洞点不那么直接。二次参数引用请求参数A不直接用于查询而是用于查询另一张表得到B再用B去查询目标数据。如果第一步查询没有校验A的归属那么通过控制A就能间接控制B最终导致越权。这需要一定的代码推理或黑盒模糊测试。文件路径遍历用户上传头像后查看头像的链接可能是/uploads/avatar/1001.jpg。尝试将1001.jpg改为1002.jpg如果能看到userB的头像这也是一种水平越权信息泄露。虽然通常归类到目录遍历但其权限绕过本质是相通的。JSON/XML参数在POST请求的JSON体或XML中寻找ID字段。例如{action: update_email, new_email: newmail.com, user_token: abc123}这个user_token如果对应着某个用户且服务端仅通过它来识别用户而未与当前会话绑定修改它就可能更新其他用户的邮箱。4.3 工具链辅助与自动化识别除了Burp Suite其他工具也能提升效率浏览器插件如EditThisCookie可以方便地管理和切换Session便于快速在userA和userB身份间切换验证。自定义脚本对于需要大量遍历的测试如订单号不是简单数字而是有一定规律的字符串可以编写Python脚本结合Requests库自动化发送请求并分析响应。被动扫描Burp Suite的Scanner或Auditor插件可以在你浏览过程中自动标记那些包含ID参数的请求并提示可能存在IDOR漏洞不安全的直接对象引用OWASP对这类漏洞的统称。5. 防御方案设计与代码审计视角5.1 根本解决方案服务端强制校验所有防御都必须建立在“不信任客户端输入”的原则上。最有效、最根本的方案是在每一次涉及用户资源访问的数据层操作前加入权限校验逻辑。伪代码示例正确做法// 获取当前登录用户ID $current_user_id $_SESSION[user_id]; // 获取客户端请求的资源ID $requested_order_id $_GET[order_id]; // 首先查询订单并明确指定订单所属用户 $sql SELECT * FROM orders WHERE id ? AND user_id ?; $stmt $pdo-prepare($sql); $stmt-execute([$requested_order_id, $current_user_id]); $order $stmt-fetch(PDO::FETCH_ASSOC); // 然后进行判断 if (!$order) { // 订单不存在或者订单不属于当前用户 die(无权访问或资源不存在); // 应返回统一的错误页面或JSON消息 } // 订单存在且属于当前用户继续后续业务逻辑... echo json_encode($order);关键点将权限校验AND user_id ?与数据查询在同一个数据库操作中原子化完成。避免先查出数据再在代码里用if($order[user_id] ! $current_user_id)判断因为那样在查出数据到判断的极短间隙里数据已经被加载到内存中虽然最终不会返回给用户但可能在某些复杂逻辑中引发意外。5.2 权限验证中间件与框架最佳实践在成熟的MVC框架如Laravel, Spring Security中应利用其提供的权限验证机制。Laravel (Policy)为资源模型如Order定义策略Policy在控制器方法前使用authorize方法。// OrderPolicy.php public function view(User $user, Order $order) { return $user-id $order-user_id; } // OrderController.php public function show(Order $order) // 路由模型绑定会自动注入Order实例 { $this-authorize(view, $order); // 自动执行Policy检查 return view(order.show, [order $order]); }如果用户无权框架会自动抛出403异常。Spring Security (Method Security)使用PreAuthorize注解支持SpEL表达式。GetMapping(/orders/{orderId}) PreAuthorize(orderService.canAccess(#orderId, principal.username)) public Order getOrder(PathVariable Long orderId) { // 能执行到这里说明已经通过权限检查 return orderService.findById(orderId); }在orderService中实现canAccess方法完成业务逻辑校验。核心思想将权限检查逻辑集中化、声明化避免在每一个业务方法里散落着重复的校验代码减少遗漏的可能。5.3 代码审计中如何快速定位漏洞点如果你是开发者或安全审计人员在审查代码时可以按以下模式快速筛查寻找数据查询函数搜索SELECT,find,get,load,query等关键词。追踪参数来源查看查询条件中的变量特别是WHERE子句中的条件是否直接来源于用户输入$_GET,$_POST,$_REQUEST。检查校验逻辑在查询语句的上方寻找是否对“当前用户”与“请求资源”的归属关系进行了判断。如果没有或者判断逻辑存在缺陷如先查询后判断则标记为可疑点。关注ORM操作对于使用ORM如Eloquent, Hibernate的代码查看find($id),findOrFail($id)等方法调用后是否立即跟进了权限校验。ORM的便捷性有时会让开发者忘记手动加入where(user_id, $currentUserId)这样的约束。审查API路由查看RESTful API的路由定义如PUT /users/{id}对应的控制器方法是否接收了id参数。这个id是否被直接用于模型操作而未经验证。5.4 业务设计层面的补充防御除了代码层面的校验业务设计也能增加攻击难度使用不可预测的标识符避免使用自增整数ID改用UUID、雪花算法ID或经过加密的Token作为资源标识符。这无法从根本上防止越权因为校验逻辑才是根本但可以大幅增加攻击者猜测和遍历有效ID的难度。记录详细日志对所有数据访问请求尤其是修改操作记录“谁user_id在什么时间timestamp尝试访问/修改了哪个资源resource_id, resource_type”。当发生水平越权攻击时日志可以提供追溯依据。异常的访问模式如一个用户短时间内访问了大量非连续的、其他用户的资源ID可以通过监控系统告警。权限最小化前端根据用户权限动态渲染界面如只显示“我的订单”按钮是良好的用户体验但绝不能作为安全依据。后端必须对每一个业务接口实施独立的、完整的权限校验。6. 实战问题排查与疑难场景解析6.1 遇到“验证码”或“Token”拦截怎么办在一些敏感操作如修改密码、支付时应用可能会引入一次性Token或验证码。CSRF Token这通常是为了防止跨站请求伪造与水平越权无关。你可以在一次会话中先正常获取一个有效的CSRF Token通常藏在表单里或通过特定API返回然后在构造越权请求时带上这个Token。只要你的攻击请求是从同一浏览器会话发起的CSRF Token就是有效的。操作验证码如果修改他人信息时需要输入验证码而验证码发送到了目标用户的手机或邮箱那么直接越权修改通常无法完成。但这不代表漏洞不存在。漏洞点转移了可能存在一个“绑定手机/邮箱”的功能而这个功能本身存在水平越权允许攻击者将受害者的账户绑定到自己的手机号上从而为后续攻击铺路。因此测试要全面。6.2 响应状态码都是200如何判断是否越权成功这是黑盒测试中常见的问题。攻击者修改ID后服务器返回了200状态码但页面内容可能显示“资源不存在”、“无权访问”或跳转到首页。对比响应长度使用Burp Suite的Comparer工具对比正常访问自己资源的响应包和尝试访问他人资源的响应包。即使状态码相同响应体长度Content-Length或具体内容通常会有细微差别。搜索敏感信息在响应体中搜索可能属于其他用户的独特信息如用户名、邮箱片段、手机号后四位等。关注差异化内容如果应用返回了JSON数据直接对比JSON结构。有时越权访问返回的数据结构可能相同但某些字段如is_owner: false或数据内容不同。时间差攻击盲测在极端情况下服务器对有权和无权访问的处理逻辑可能完全一样都返回“成功”但无数据。这时可以尝试“基于时间的盲注”思路构造一个请求使其在越权成功时触发一个耗时的操作如关联查询一个非常大的表。通过比较响应时间的显著差异来推断权限校验结果。但这需要深入了解业务逻辑难度较高。6.3 水平越权与垂直越权、业务逻辑漏洞的边界这几个概念有时会交织。水平 vs 垂直核心区别在于权限是否升级。水平是同级用户间的横向跨越垂直是向更高权限角色的纵向提升。例如普通用户能访问管理员后台这是垂直越权。水平越权 vs 业务逻辑漏洞很多业务逻辑漏洞表现为一种“非常规”的越权。例如在一个抽奖活动中规则是“每个用户ID只能抽一次奖”。如果校验逻辑是检查“当前用户ID是否已存在于中奖记录表”那么攻击者通过修改请求中的user_id参数就可以冒充其他用户抽奖消耗他人的抽奖机会。这本质上还是水平越权操纵了标识其他用户的参数但发生在特定的业务规则下。所以在测试时思维要打开任何由用户可控的、能影响业务状态或数据归属的参数都是测试对象。6.4 漏洞报告撰写要点当你发现一个水平越权漏洞后清晰专业的报告至关重要。标题简明扼要如“[水平越权] 通过修改order_id参数可查看任意用户订单详情”。风险等级通常定为“中危”或“高危”取决于可操作的数据敏感性查看个人信息为中危篡改密码、地址为高危。详细复现步骤前提注册两个账号A和B。步骤1用A账号登录进入订单列表点击某个订单ID: 100。步骤2抓包获取请求GET /api/order/detail?order_id100。步骤3在Repeater中修改order_id101已知为B用户的订单发送请求。步骤4观察响应成功返回B用户的订单详情附截图。请求与响应数据提供原始的HTTP请求和响应包可脱敏关键信息。漏洞原理简要分析指出服务端未校验当前登录用户与order_id所指向资源的归属关系。修复建议参考上文建议在数据查询时强制关联当前用户ID。我个人在多年的渗透测试中发现水平越权漏洞的修复往往不是技术难题而是意识问题。很多开发者在实现“我的xx”这类功能时潜意识里认为“既然页面链接只展示给登录用户且链接里的ID也是从自己列表里点的那肯定是自己的”从而忽略了服务端的二次校验。作为安全人员我们需要不断提醒团队前端的一切都是装饰后端的校验才是堡垒。每一次对用户资源的访问都必须经过“你是谁”和“这是你的吗”的灵魂拷问。把这个观念植入开发流程比事后修复无数个漏洞要有效得多。