WebGoat 2023 越权漏洞实战:从原理到防御的访问控制安全指南 1. 项目概述为什么是WebGoat与Broken Access Control如果你刚接触Web安全或者想找一个能系统性、手把手教你理解“越权”这个老大难问题的靶场那WebGoat 2023版的Broken Access ControlBAC访问控制缺陷章节绝对是你绕不开的必修课。我干了这么多年渗透测试和安全开发BAC这类漏洞在真实漏洞赏金Bug Bounty和渗透测试项目中出现的频率高得吓人但它的原理又往往被初学者觉得“太简单”而忽视结果就是阴沟里翻船。WebGoat这个靶场的好处在于它不是一个冷冰冰的漏洞列表而是把BAC拆解成一个个具体的、有场景的挑战让你在“闯关”的过程中自己把坑踩一遍印象绝对深刻。简单说Broken Access Control就是指一个系统没能正确地实施权限管控导致用户能够执行其本不被允许的操作。比如你用一个普通用户的账号通过修改请求参数看到了管理员的后台数据或者删除了别人的订单。这听起来好像没啥技术含量不就是改个ID吗但实战中它可能隐藏在复杂的业务逻辑、前后端交互的缝隙里形态千变万化。WebGoat 2023的BAC章节就模拟了这些经典场景。通过通关它你不仅能明白怎么“黑”进去更能从防御者角度理解一个健壮的权限系统到底该怎么设计。这无论对你未来做渗透测试、代码审计还是自己开发安全的应用都至关重要。2. 环境准备与靶场初探2.1 WebGoat 2023的部署与启动工欲善其事必先利其器。WebGoat的部署非常友好对新手极其友好。官方推荐使用Docker这也是目前最主流、最不容易出问题的方式。你不需要在本地折腾Java环境或者复杂的配置。首先确保你的机器上已经安装了Docker和Docker Compose。打开终端Linux/macOS或命令提示符/PowerShellWindows执行以下命令来拉取并启动WebGoat 2023docker pull webgoat/webgoat-2023:latest docker run -it -p 8080:8080 webgoat/webgoat-2023:latest这两条命令的含义是第一条从Docker仓库下载最新的WebGoat 2023镜像第二条以后台交互模式运行这个容器并将容器内部的8080端口映射到你本机的8080端口。执行后你会看到控制台开始输出启动日志。当你看到类似Started WebGoat in X.XXX seconds的提示时就说明启动成功了。注意如果你的本地8080端口已被其他程序比如另一个Web服务占用启动会失败并报端口冲突。这时你需要换个端口比如改成-p 8081:8080那么访问地址就变成了http://localhost:8081。接下来打开你的浏览器访问http://localhost:8080。你会看到WebGoat的登录界面。第一次使用需要点击“Register a new user”来创建一个账户。这里我建议你注册一个容易记住的比如用户名test密码test123。注册成功后用这个账户登录你就进入了WebGoat的主界面。主界面左侧是课程菜单找到“Access Control Flaws”这个大类展开后就能看到一系列关于Broken Access Control的课程了。我们的实战通关将主要围绕这里的挑战展开。2.2 核心工具配置浏览器与代理要深入分析漏洞光靠浏览器点点是不够的。我们需要“看见”浏览器和服务器之间到底传输了什么数据。这就需要用到代理工具。最经典、最强大的莫过于Burp Suite社区版对个人学习和测试完全免费。安装Burp Suite去PortSwigger官网下载社区版安装包并安装。配置浏览器代理启动Burp Suite在Proxy - Options选项卡下确保代理监听在127.0.0.1:8080默认。然后你需要配置你的浏览器以Chrome为例Firefox类似的代理设置指向这个地址和端口。更常用的方法是安装Burp Suite提供的浏览器扩展“FoxyProxy”或直接使用Burp内置的浏览器Burp Suite 2023后版本自带。安装Burp证书为了能拦截和解密HTTPS流量虽然WebGoat默认是HTTP但养成好习惯你需要访问http://burp下载Burp的CA证书并导入到你的浏览器或系统的受信任根证书颁发机构中。具体步骤Burp的启动向导会有提示。开始拦截确保Burp Suite的“Intercept is on”按钮是按下状态显示为橙色。这时你在浏览器中对WebGoat的所有操作请求都会先经过Burp你可以查看、修改之后再转发给服务器。除了Burp浏览器自带的开发者工具F12打开也是必不可少的尤其是“网络Network”和“控制台Console”标签页用于观察前端发起的请求和可能的JavaScript逻辑。3. Broken Access Control漏洞原理深度拆解在动手之前我们必须把原理吃透。BAC不是一个单一的技术漏洞而是一类设计缺陷。OWASP TOP 10 2021将它列为第一风险其核心问题在于服务器在决定是否允许一个请求时所依赖的权限判断逻辑存在缺陷导致攻击者可以绕过预期的权限检查。3.1 垂直越权与水平越权这是BAC最常见的两种形式必须分清楚。水平越权指相同权限等级的用户之间能够非法访问彼此的资源。这是WebGoat BAC章节的重点。核心问题服务器在处理请求时过度信任客户端提交的身份标识如用户ID、订单号而没有在服务端二次校验“当前登录的用户是否有权操作这个ID对应的资源”。经典场景你登录后浏览器地址栏显示http://site.com/user/profile?userId123你把自己的userId从123改成124如果服务器直接返回了用户124的个人信息而没有检查“当前登录人是否是124”这就是水平越权。你越权访问了同是普通用户的别人的数据。垂直越权指低权限用户能够执行高权限用户如管理员才能执行的操作。核心问题权限检查的缺失或绕过。比如一个功能按钮或API接口前端根据用户角色隐藏了但后端接口没有做角色校验攻击者可以直接构造请求访问该接口。经典场景普通用户通过直接访问/admin/deleteUser这个URL成功删除了其他用户。WebGoat的挑战主要围绕水平越权展开因为它在业务系统中更为普遍和隐蔽。3.2 漏洞产生的根本原因从开发角度BAC漏洞通常源于以下几个坏习惯“隐藏即安全”认为把管理员链接在前端隐藏起来display:none或通过角色判断不渲染就安全了后端接口完全敞开。过度信任客户端参数直接从HTTP请求参数URL参数、POST表单、Cookie、JWT令牌的未验证声明中获取资源ID或操作指令并直接用于数据库查询如SELECT * FROM orders WHERE order_id ${request.orderId}。缺乏服务端会话上下文绑定执行操作时没有将操作与当前已验证的用户会话进行强关联。例如删除文章时代码只检查了“文章ID是否存在”没检查“当前用户是否是文章作者”。复杂的业务逻辑漏洞权限检查发生在流程的A点但实际数据操作在B点中间的状态变化可能导致权限被绕过。这属于更高级的逻辑漏洞。理解了这些我们再去看WebGoat的题目就不是盲目试参数了而是带着“它这里可能信任了什么不该信任的参数”、“它的权限检查逻辑可能放在哪一步”的思路去分析。4. 实战通关WebGoat BAC挑战逐项击破下面我将挑选WebGoat 2023 BAC章节中最具代表性的几个挑战带你一步步通关并详细解释每一步背后的原理和思考过程。请确保你的WebGoat和Burp Suite已经就绪。4.1 挑战一基于ID的参数篡改Insecure Direct Object References这是水平越权最直白的体现。场景登录WebGoat后进入BAC章节的第一个挑战。页面可能显示你的个人资料URL看起来像/WebGoat/access-control/user-info?userId你的ID。通关步骤与思考观察用浏览器开发者工具的网络面板或者直接看地址栏找到当前请求中标识你身份的参数比如userId、accountNumber等。页面上显示了你的姓名、邮箱等信息。假设服务器可能直接使用这个userId参数去数据库查询并返回用户信息。那么如果我修改这个参数为另一个值呢测试在Burp Suite拦截状态下将这个请求发送到“Repeater”模块这样方便反复修改测试。在Repeater中找到userId参数将其数值修改为一个你猜测可能存在的其他用户ID比如递增1如果当前是123就改成124。发送与验证点击“Send”发送修改后的请求。观察响应Response面板。如果服务器返回了另一个用户的完整信息如不同的姓名、邮箱那么漏洞就存在了。深入尝试不止一个ID。你可以写一个简单的脚本或者利用Burp的“Intruder”模块对userId参数进行数字遍历批量获取用户数据。这就是一个简单的信息泄露漏洞。防御思路后端必须进行权限校验。伪代码应该是// 错误示范存在漏洞 User user userRepository.findById(request.getUserId()); return user; // 正确示范安全 User currentUser getCurrentUserFromSession(); // 从会话获取当前登录用户 User targetUser userRepository.findById(request.getUserId()); if (targetUser null) { throw new ResourceNotFoundException(); } // 关键检查请求的用户ID必须等于当前登录用户的ID if (!targetUser.getId().equals(currentUser.getId())) { throw new AccessDeniedException(You are not allowed to view this users info.); } return targetUser;这个挑战的目的是让你最直观地感受“改个参数就能看到别人数据”的威力。4.2 挑战二多阶段操作中的权限绕过这个挑战比上一个稍微复杂涉及操作流程中的权限检查。场景模拟一个“转账”或“修改属性”的多步操作。第一步页面A让你选择目标第二步页面B确认并执行。权限检查可能只在第一步进行了。通关步骤与思考流程梳理正常走一遍流程。比如先访问页面A在下拉框里你只能看到和你相关的几个选项比如你自己的几个文件。你选择一个提交跳到页面B页面B显示了上一步选择项的详情并有一个“确认”按钮。抓包分析在每一步都用Burp拦截请求。重点看从A到B的请求它向服务器提交了什么很可能是一个资源ID如fileId100。页面B的响应这个页面是如何知道要显示哪个资源的可能是通过上一步提交的ID也可能是服务器在会话Session里存了一个状态。查看页面B的HTML源码看有没有隐藏表单域input typehidden包含了资源ID。寻找漏洞点漏洞可能出现在两个地方在A到B的请求中篡改在Burp中拦截从A到B的请求将fileId修改为另一个你不该访问的ID比如另一个用户的文件ID200然后放行。如果页面B显示了文件200的详情说明第一步的权限检查形同虚设或者服务器完全信任了这个ID。直接访问B页面并指定参数尝试不经过页面A直接在浏览器地址栏构造页面B的URL并带上参数如/WebGoat/access-control/confirm?fileId200。如果成功访问说明页面B接口本身就没有做权限校验这是一个“不安全的直接对象引用”加上“缺失功能级访问控制”的组合漏洞。完成攻击在页面B执行最终操作如确认修改、转账。如果后端在最终执行操作时依然没有校验权限那么攻击就成功了。实操心得在多步流程中服务器必须在每一步、特别是最终执行操作的端点都重新进行基于当前会话的权限校验。绝不能假设“用户是从上一步合法过来的”。攻击者完全可以跳过中间页面直接伪造请求访问任何一步。4.3 挑战三基于用户角色的访问控制缺失功能级控制这个挑战涉及垂直越权关注的是“功能”或“接口”的访问权。场景WebGoat可能会模拟一个论坛普通用户有发帖、回帖的界面和接口管理员有删除帖子、管理用户的界面和接口。前端的“删除”按钮只对管理员角色显示。通关步骤与思考权限枚举首先以普通用户身份登录浏览整个应用。用Burp的“Target” - “Site map”功能爬取整个网站结构或者手动点击所有可见的链接和功能让Burp记录下所有的请求。识别特权端点在Burp的Site map或Proxy历史记录中仔细查看记录的URL和请求。你需要寻找那些看起来像是管理功能的端点比如路径中包含/admin/、/delete、/config、/users等关键词的URL。即使你的普通用户界面没有触发这些请求它们也可能因为静态资源引用、前端代码注释或之前的爬取而出现在记录里。直接访问测试在Burp的Repeater中尝试直接向这些疑似管理接口发送请求。关键点在于要携带上你当前普通用户的会话CookieBurp会自动带上。比如构造一个DELETE /WebGoat/api/posts/123或GET /WebGoat/admin/users的请求。分析响应如果返回“403 Forbidden”或“401 Unauthorized”说明后端做了基本的角色校验。如果返回“404 Not Found”可能是路径不对或者接口确实不存在。如果返回了成功状态码如200并包含了管理数据或者执行了管理操作返回“删除成功”那么恭喜你找到了一个严重的垂直越权漏洞服务器只依赖前端隐藏了功能后端接口完全没有验证调用者的角色。尝试POST操作对于删除、创建等操作通常是POST、PUT、DELETE方法。你需要模拟这些请求。可以先用普通用户身份正常操作一个自己可用的功能如发帖抓取这个POST请求的格式然后修改其URL和目标参数指向管理接口再发送。防御思路后端每一个接口API Endpoint的入口处都必须进行声明式的或编程式的角色/权限检查。在Spring Security中可以使用PreAuthorize(hasRole(ADMIN))这样的注解。绝不能依赖前端控制。4.4 挑战四JSON Web Token (JWT) 篡改攻击现代Web应用常用JWT作为身份令牌。如果实现不当JWT也可能成为BAC的突破口。场景WebGoat可能会提供一个使用JWT的API。登录后你从服务器获得一个JWT用于后续API调用。通关步骤与思考获取并解码JWT登录后用Burp拦截任何一个API请求在Authorization头或Cookie中找到JWT。JWT由三部分组成Header.Payload.Signature用点分隔。将Payload部分中间部分进行Base64Url解码在线工具或Burp的Decoder模块都可以。你会看到类似{sub:user123, role:user, exp:171...}的JSON内容。分析Payload重点关注声明Claims中的权限相关字段如role、groups、isAdmin等。当前你的role很可能是user。尝试篡改漏洞可能源于两种错误配置使用弱密钥或空密钥如果服务器使用了弱密钥如secret或甚至没有验证签名alg: none攻击现代库已基本免疫你可以直接修改Payload将role改为admin然后重新生成签名如果密钥可猜或利用“none”算法。逻辑依赖未验证的声明更常见的是服务器正确验证了JWT签名确保令牌未被篡改。但是它在处理业务逻辑时直接从JWT的Payload中读取role字段来判断权限而没有去数据库或会话中再次查询用户的实时角色。这意味着如果一个用户的角色被管理员在后台从user提升为了admin但该用户旧的JWTrole仍为user在过期前依然有效他将继续拥有旧权限。反之如果被降权旧令牌依然可能拥有高权限。不过对于攻击者来说直接修改已签名的JWT是不可行的。WebGoat中的可能挑战靶场可能会模拟第一种情况弱密钥让你破解密钥或利用“none”算法。你需要将JWT头部的alg改为none删除签名部分然后提交给服务器。或者它可能模拟一个逻辑服务器信任JWT中的某个字段如userId来查询资源而你可以通过注册不同账号获得不同JWT通过对比分析尝试构造一个能越权访问的Payload但这需要服务器签名密钥通常不可能。更可能的挑战是让你意识到JWT中的信息在过期前是“静态”的权限变更无法及时生效从而理解使用JWT时需要在服务端维护权限状态的重要性。防御思路永远使用强密钥并安全保管。避免使用none算法。关键JWT宜用于身份认证Authentication证明“你是谁”而不应作为权限Authorization决定“你能做什么”的唯一来源。重要的权限判断服务端应基于JWT中的用户标识如sub实时查询数据库或缓存中的用户角色信息。5. 漏洞挖掘技巧与自动化辅助手动通关让我们理解了原理但真实环境中目标庞大需要一些技巧和工具来提高效率。5.1 手工测试思维模型面对一个未知系统可以按以下步骤系统性寻找BAC漏洞身份映射注册两个同权限的账号A和B以及一个高权限账号Admin如果可能。用Burp记录下每个账号正常操作的所有流量。参数收集从A账号的请求中提取所有标识用户、资源、操作的参数。如userId,orderId,documentId,actionedit,rolesubmitter。交叉测试在Burp Repeater中将A账号请求中的会话Cookie或Token替换为B账号的但保持其他参数不变发送请求。观察是否能用B的权限操作A的资源水平越权。或者用A的会话去请求原本只有Admin才能访问的URL垂直越权。状态篡改关注那些可能影响权限的状态参数比如在请求中尝试添加admintrue 或者将isApprovedfalse改为true。遍历ID对于数字型ID使用Burp Intruder进行简单遍历观察响应长度和状态码的变化寻找异常点如返回其他用户数据。5.2 利用Burp Suite高级功能Comparer比较两个不同用户如A和B访问同一功能时服务器响应的差异。有时越权访问返回的数据结构可能略有不同。ScannerBurp的专业版扫描器可以自动检测一些常见的BAC问题如可预测的资源ID。社区版虽无主动扫描但手动测试已足够深入。Extensions (BApps)安装如AuthMatrix,Autorize等扩展。这些插件能帮助你更方便地管理不同角色的会话并自动重放请求进行权限测试极大提升效率。例如Autorize可以让你配置一个低权限用户的Cookie然后用它来自动测试所有高权限请求快速找出缺失权限控制的端点。5.3 代码审计视角作为开发者或安全审计人员在代码里找BAC要关注以下几点控制器入口查看每个API控制器方法。是否在方法开始处进行了权限校验校验是硬编码还是通过注解注解是否生效数据库查询查看数据访问层DAO/Repository。SQL语句或ORM查询中WHERE条件是否包含了用户身份约束例如应该是WHERE id ? AND owner_id ?而不是简单的WHERE id ?。服务层逻辑在复杂的业务方法中权限检查是否在每一步关键操作前都执行了是否存在先检查后状态又被修改的“时间竞争”问题URL模式与配置检查安全框架如Spring Security, Shiro的配置。是否对所有管理路径/admin/**都配置了正确的访问规则是否有配置遗漏6. 防御方案设计与最佳实践攻防一体理解了怎么攻击才能更好地防御。一个健壮的访问控制系统应该遵循“最小权限原则”和“纵深防御”思想。6.1 服务端强制检查黄金法则永不信任客户端所有关于用户身份、资源归属、操作权限的判断必须在服务端进行。前端的一切隐藏、禁用、展示都只是用户体验优化不是安全措施。默认拒绝所有接口默认情况下都应拒绝访问只有显式配置了允许规则的才能通过。基于角色的访问控制与基于属性的访问控制结合RBAC适用于功能菜单、页面级别的粗粒度控制。如“管理员角色可以访问用户管理页面”。ABAC适用于数据行级别的细粒度控制。如“用户只能修改自己创建的、且状态为‘草稿’的订单”。这需要在业务逻辑中实现复杂的规则引擎。6.2 技术实现要点统一的权限校验框架使用成熟的框架如Spring Security的PreAuthorize、PostAuthorize并在所有需要权限控制的入口方法上声明。避免在业务代码中散落着if-else权限判断。资源所有权绑定在数据模型设计中尽可能为资源添加“所有者ID”owner_id字段。任何数据操作都必须在查询条件中附加owner_id currentUserId。对于更复杂的关系如共享文档需要有单独的资源-用户关系表来管理权限。使用不可预测的标识符避免使用自增整数作为资源ID在URL中暴露。可以使用UUID、随机字符串或加密的ID如Hashids增加攻击者猜测和遍历的难度。但这只是“障眼法”不能替代服务端校验。记录审计日志对所有敏感操作登录、数据访问、修改、删除进行详细日志记录包括操作时间、用户、IP、操作类型、资源ID等。这有助于在发生安全事件后进行追溯和分析。定期进行权限复审与测试在开发过程中进行专门的访问控制测试。可以使用单元测试模拟不同角色用户访问接口。在上线前进行渗透测试重点检查越权漏洞。6.3 针对JWT场景的特别建议短期令牌与刷新令牌机制将访问令牌Access TokenJWT有效期设置较短如15分钟同时使用一个独立的、可撤销的刷新令牌Refresh Token来获取新的访问令牌。当用户权限变更时使该用户的刷新令牌失效这样他下次刷新时就会失败需要重新登录获取新权限的令牌。服务端权限缓存在服务端维护一个“用户ID - 最新权限”的缓存如Redis。处理请求时先验证JWT签名然后根据JWT中的用户ID去缓存查询实时权限进行判断。当管理员修改用户权限时同步更新此缓存。通关WebGoat的Broken Access Control章节就像完成了一次系统的权限安全军训。它把那些看似简单的“改个ID就能黑进去”的场景拆解成一个个具体的实验让你在动手过程中深刻理解漏洞的成因和危害。记住访问控制不是某个单一的功能而是渗透在系统每一个业务逻辑中的思维模式。在以后看代码或者做测试的时候多问一句“这里服务器真的检查权限了吗” 这个习惯能帮你发现无数潜在的安全风险。