ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WebGoat越权靶场实战:从访问控制失效到IDOR与垂直越权

WebGoat越权靶场实战:从访问控制失效到IDOR与垂直越权

刚把WebGoat 2023里的Broken Access Control模块完整走了一遍,从最开始对着页面发呆,到后面闭着眼也能猜到它要考什么。这个模块是我见过最适合用来理解“越权”概念的靶场之一,它把隐藏字段、对象直接引用、缺失功能级访问控制、多步骤流程绕过这类问题拆成了一节节小课,每一节都能亲手复现一个真实漏洞场景。如果你正准备入门Web安全,或者已经刷过一些靶场但总觉得对“权限校验”理解不透,这篇东西应该能帮你省不少时间。文章按我实际通关的顺序来写,不跳步,每关会给出思路、请求样例和判定条件,最后附了一堆我自己踩过的坑。整篇面向的是手上有一个可用靶场、想搞清楚Broken Access Control到底考什么的读者,你不需要有很深的安全基础,能看懂简单的HTTP请求就够。

1. 环境准备与工具清单

1.1 启动靶场:几行命令跑起来

WebGoat是一个Java写的Web漏洞训练靶场,我这边用的2023版本,拿到的是一个可执行的jar包。启动之前先确认本机装了JDK,版本建议用11以上,实测在JDK 17上跑得很顺。启动命令很直接:

java -jar webgoat-2023.x.jar --server.port=8080

看到日志里出现Started WebGoat in xx seconds,就说明起来了。浏览器访问http://localhost:8080/WebGoat,会自动跳到登录/注册页面。这里有个很容易忽略的细节:如果8080端口被别的程序占了,启动会直接报错。我第一跑的时候就是本地一个服务占了8080,导致白等了一分钟才反应过来。换端口再启动就行,比如--server.port=8888,后面所有请求路径里也要跟着改端口。

也有人喜欢用容器方式起靶场,一条命令就能拉起来,本质是一样的,关键是容器里端口要映射到宿主机。无论哪种方式,只要浏览器能打开登录页,环境就算准备完毕。

1.2 不可缺少的抓包工具

练Broken Access Control,浏览器开发者工具只能算辅助,真正的主力是HTTP拦截代理。简单说,这类工具站在浏览器和服务器中间,可以看到浏览器发出去的每一个请求,也可以手动改完再放行。没有它,后面一半以上的关卡都玩不了。

代理端口尽量不要也选8080,和靶场端口撞了会很混乱。我常用的是把代理监听端口设为8888,然后在浏览器代理设置里填上127.0.0.1:8888。这里有一个真实的坑:不少浏览器默认忽略本机代理地址,需要在浏览器设置里手动把“回环流量走代理”打开,否则抓不到包。

如果靶场访问地址是HTTPS,还需要额外安装代理工具生成的CA证书到浏览器受信任列表。好在WebGoat默认走HTTP的多,这个坑遇到的人不多。但是提前把证书这事搞清楚,后面自己玩别的HTTPS靶场会少走很多弯路。

1.3 账号准备与角色概念

进入WebGoat后先注册一个用户,很多同学直接用自己的邮箱随便注册一个,这在前面课程没问题,但到了Broken Access Control后半段,尤其是垂直越权和水平越权相关关卡,系统要求你以特定身份登录,比如guest、alice、tom、admin这一类预置账号。我不止一次因为当前登录账号不对,怎么做都判定失败,后来才意识到是自己用错身份了。

我的做法是:把预置账号当成通关凭证,每进入一个新的子课程,先看一眼页面提示里要求用谁的身份。不要嫌麻烦,登出、清理Cookie、再登录,这几步在后面的关卡几乎成了标准动作。有些浏览器开多个标签页共享会话,登出操作只在一个标签页生效,另一个标签页还是旧会话,排查问题时要格外留意这一点。

2. 访问控制漏洞的底层原理

2.1 访问控制失效到底是什么

Broken Access Control翻译过来是损毁的访问控制,更常见的叫法是越权。它指的不是密码被爆破、也不是提交框里有恶意脚本,而是服务器对“当前用户能不能做这件事”或者“当前用户能不能看这个数据”的校验做漏了。比如一个普通用户能访问管理员页面,或者登录A账号能看到B账号的订单,都属于访问控制失效。

很多新手容易混淆认证和授权这两个概念。认证解决的是“你是谁”,通常是登录、写入Session;授权解决的是“你能干什么”,也就是对每个请求判断角色和资源归属。WebGoat这里的靶场题目,绝大多数不是要你绕登录,而是要你绕过“授权”这一步。

你去看很多现实漏洞报告,会发现越权漏洞的危害被严重低估。水平越权能扒出大批同级别用户的个人资料,垂直越权直接就是一个普通用户变管理员,最严重的能拿到系统底层权限。所以这个模块被放在安全风险榜非常靠前的位置,一点不冤。

2.2 水平越权、垂直越权与IDOR的关系

访问控制漏洞常被分成三类,这在后面通关时是选路线的关键。

水平越权指相同权限级别的用户之间互相访问数据,经典场景是学生A修改请求里的学号,看到了学生B的成绩单。垂直越权指低权限用户拿到高权限能力,比如访客角色直接调用“删除用户”接口。还有一类很隐蔽但出现频率极高的IDOR,全称是Insecure Direct Object Reference,意思是“不安全的直接对象引用”。

IDOR的本质是服务器把数据库里的主键、文件ID这一类对象标识直接暴露在URL或请求参数里,并且没有校验当前用户是否拥有这个对象。它可能造成水平越权,也可能配合角色漏洞变成垂直越权。在WebGoat里,IDOR单独占了一课,后续的水平越权/垂直越权关卡也建立在相同思路上。记住一句话:看到请求里出现用户ID、订单号、文件名这类参数,第一反应应该是“能不能改成别人的”。

2.3 客户端信任边界的误区

接触这套靶场之后,最大的认知转变是:永远不要相信从浏览器发过来的东西。隐藏字段、下拉框选项、按钮权限显示,这些都只存在于客户端,用户改起来只需要一个抓包工具。

前端隐藏一个菜单,不等于后端没有这个接口;JS提示你无权限,不等于服务器会拒绝你;页面把价格字段设成readonly,不等于提交的时候不能改。WebGoat有不少关卡故意在前端放一个障眼法,比如一个靠前端判断角色才显示的按钮,目的就是让你体会“前端限制根本不是限制”。

真正的权限边界只应该在服务端画出来。所有敏感操作、敏感数据接口,服务端在收到请求后都应该重新鉴权,而不是相信请求里带的角色字段或者某个隐藏参数。

3. 通关实战:隐藏字段与IDOR

3.1 Hidden Fields:改一次隐藏参数就能过去

进入Broken Access Control,最开始两课相对温和,其中Hidden Fields这节课很有代表性。页面看起来像一个商品购买表单,里面有商品名称、数量,还有个不出现在页面上的字段。用抓包工具继续往下点,请求里会看到形如price=199&quantity=1的参数,而price并不是页面上明显可编辑的输入,只是藏在HTML表单里的<input type="hidden">。

做法不复杂:抓包拦截这个提交请求,把price改成0或者一个很小的数,放行后看响应。如果服务端没有再次校验,就会按你提交的价格处理,通关提示随即弹出。这个关卡之所以经典,是因为它完美演示了“客户端提交的数据直接被信任”的典型问题。

在这个关卡里还要注意一个细节:有些题改价格前,服务器会先查一次数据库里的价格然后和你提交的一起判断。如果你的请求里既有id又有price,那可以把眼光放到id上,改成别的商品ID再组合提交,多试试总有一个参数是信任链上的突破口。版本不同参数名可能不同,可能是price、total、amount、role,但思路一致:在关键业务参数上做文章,观察服务端反应。

3.2 IDOR:对象引用直接暴露在URL里

Insecure Direct Object References这节课比Hidden Fields多绕几个弯,完整版通常分两到三个子任务。第一个子任务是查看某个指定用户的资料。进入课程后,页面上会有一个类似“View Profile”的按钮,点一下会在当前页展示一份用户信息,同时浏览器的地址栏里能看到一个很关键的URL片段:

/WebGoat/IDOR/profile/2342444

最后那串数字是数据库里的用户主键。在这个场景里,它就是“直接对象引用”。如果你是按正常操作点的,看到的当然是有权限访问的那个用户。接下来的操作就是用抓包工具或者直接在浏览器地址栏改这个ID,把它换成附近的整数,比如2342443、2342384、2342389,刷新页面看返回内容。

我习惯用抓包工具的Repeater功能来枚举,比在地址栏一下一下改高效得多。把请求发到Repeater,每次只改末尾的ID段,发几次之后就能看到另一个用户的信息出现在响应体里。这时两个用户的信息对比一下,你会发现哪些字段是这个人独有的,题目要求找的“目标用户标志信息”基本就在里面。这一关成功地让你理解:服务端只查了WHERE id = ?,却没加AND owner = 当前用户。

这里有一个实际操作心得:不要一上来就猜一个巨大的随机ID,先看看当前ID是几位数,然后在它附近前后各枚举几十个。WebGoat的虚拟员工ID往往就在一个很窄的区间内,盲目爆破只会让日志刷屏并增加出问题概率。

3.3 IDOR删除案例:接口直接暴露的越权

IDOR这一课的第二个经典子任务,是让你以普通用户身份删除其他用户。页面上虽然提供删除功能,但正常操作只会删除自己能删的记录,这样并不会通关。真正的考点是请求里的ID参数能不能指向别人。

在页面抓包会看到一个删除请求指向类似地址:

DELETE /WebGoat/IDOR/delete?id=2342444

这里id是你的用户ID。把这个ID改成其他用户的ID再发送,如果响应显示删除成功或者返回了对方用户的信息,就说明服务端对删除操作也没有做归属校验。这种漏洞在真实系统中非常致命,因为删除是破坏性操作,一旦接口被越权调用,数据恢复都是大问题。

操作时有一个细节:删除请求可能要求特定的HTTP方法,比如必须是DELETE,如果你用GET去试,有时候会被405拦下来。我的建议是把抓包工具里的请求方法改成跟原请求一致,只改参数。如果服务器对方法校验不严,那么POST、GET、DELETE都可以试一遍,这种尝试本身也是垂直越权关卡里的常见考点。

4. 通关实战:接口级与业务流越权

4.1 Missing Function Level Access Control:直接调管理接口

Missing Function Level Access Control翻译过来是“缺失功能级访问控制”,表现出来的问题更像是一张门禁卡没设置权限,谁捡起来都能刷开管理员办公室。这一课配了一个用户管理界面,页面上提示你当前是普通用户,系统没有给你放管理菜单的入口。

普通用户在主界面里确实找不到“用户管理”相关的按钮,但管理功能肯定是一个真实存在的接口。用抓包工具观察页面加载时的所有请求,或者直接看前端源码里有没有注释掉的链接,你会发现一个类似这样的地址:

GET /WebGoat/access-control/users

拿当前普通用户的Cookie直接访问这个URL,如果返回的是用户列表JSON,那就说明服务端没有校验当前角色,任何登录用户都能访问用户管理功能。这一关后面的子任务大同小异:要求你以普通用户身份去创建一个新用户、修改用户角色。本质上就是对着这个接口发POST、PUT请求。

我用抓包工具构造了一个添加用户的请求:

POST /WebGoat/access-control/users HTTP/1.1 Host: localhost:8080 Cookie: JSESSIONID=<当前会话> Content-Type: application/json {"username":"pwnuser","password":"pwn123456","role":"admin"}

发送后如果服务器返回“用户新增成功”或者页面上出现了你创建的用户,就代表垂直越权链路已经打通了。这一课给我的启发很大:功能在前端被藏起来,并不代表后端接口不存在。很多系统只做了菜单权限,却没在真正处理业务的Controller上做角色注解,导致前端看不到、后端不设防。

4.2 Multi-Step Process:多步骤流程绕过

Multi-Step Process这节课玩的是业务流程上的漏洞。它模拟了一个包含多个步骤的场景:选商品、填信息、确认、提交。页面一步步带你走,但后端并没有把整个流程的状态锁在服务器上,而是隐含地信任前端传过来的步骤编号和汇总字段。

实际操作中,我先把整个流程完整走一遍,同时用抓包工具记录每一步的请求。重点观察两个地方:有没有随请求一起提交的累计金额、折扣、数量之类的字段;有没有类似step=1、step=2这样的步骤标识。常见做法是在第一步请求里就把最终步骤该提交的参数一并带上,比如直接在第一步提交step=4的参数集合,看服务器会不会跳过错觉校验直接进入完成状态。

更常见的变形是把折扣字段改成负数。靶场里曾经出现过的场景是:系统根据商品数量自动计算总价,前端把“总价”和“优惠比例”当隐藏参数传给后端。你把优惠比例改成-100,总价就变成了负数,系统不但不校验,还会按负数入账。负“付款”这种反直觉结果,恰好暴露了服务端对计算参数的绝对信任。

这一关的系统判定往往需要看到最终的“购买成功”页面才算通过,所以要仔细看加载了哪个成功回调。我踩过的坑是:改完参数后只看到了HTTP 200,就以为OK,但页面没有真正跳转,通关标记也没亮。处理方法是除了看响应码,还要在前端显示层面确认流程已经走完。

4.3 垂直越权与水平越权扩展练习

模块后半段的垂直越权和水平越权课程,其实是把前面IDOR、Missing Function Level的知识合并成更接近实战的场景。垂直越权关卡里,系统给了你一个低权限账号,又暗示存在一个只有管理员能做的操作。找接口的过程和4.1差不多,但多了一道限制:即使你找到了管理员接口,直接发送请求可能被拦。

这时候就要做HTTP层面的绕过尝试。我当时的顺序是:先用原请求原样发送,看服务器返回什么;如果返回403 Forbidden,就把请求方法从GET换成POST、从POST换成PUT、甚至试试HEAD;如果还不行,就检查是不是存在Origin、Referer这类来源校验,把请求里的这些头删除再放行。WebGoat这类靶场里,经常藏着某个方法在鉴权逻辑中漏掉的情况,多试几种组合,通关条件很快会出现。

水平越权关卡则更像前面IDOR的加强版。登录用户A后,用抓包工具找到查看个人信息的请求,请求参数里必然有一个用户标识字段。把它改成用户B的标识,如果响应里带出了B的数据,就说明水平校验缺失。要注意的是,这类关卡经常要求你在同一个角色层级内操作,如果改错成管理员ID,可能因权限过高反而被限,本质上还是先确认一下目标ID属于哪个用户组。

在这里我形成了一个习惯:凡是遇到越权关卡,先画一条线——当前用户、目标用户、接口URL、参数位置。把四个要素列出来,再开始改包,思路会清晰很多。

5. 实战问题排查与避坑指南

5.1 通关状态不刷新/不判定的排查

通关最令人崩溃的情况是:操作明明生效了,页面却显示未完成。我遇到的第一个原因是登录身份不对。WebGoat每个课程都有独立完成状态不假,但很多题目是在“特定用户”的前提下设计的。你用注册的新用户完成了操作,系统却期望你是内置角色,自然不给你通关。

第二个原因是缓存。浏览器对某些请求做了本地缓存,你改包发送了新的请求,但页面展示的仍是旧的响应。解决办法是打开开发者工具的“Disable cache”选项,并且每次操作后硬刷新页面。

第三个原因是步骤遗漏。有些课程有好几个子页面,通关条件是在最后一个子页面出现的。你只完成了前半部分,右侧菜单的勾自然不会亮。我的建议是每做完一步就刷新一下左侧课程列表,看勾选状态有没有变化,及时定位还没完成的子步骤。

5.2 抓包过程最常见的三类问题

第一类是代理端口和靶场端口冲突导致页面打不开。解决方案很简单:让靶场端口和代理端口保持不同,浏览器代理设置里确认无误即可。

第二类是只抓到页面加载请求,没抓到具体的操作请求。很多新手开了拦截,却发现点击按钮后什么都没发生。这通常是因为浏览器对按钮的POST请求走了异步JavaScript,需要确认抓包工具里没有过滤掉某些域名或文件类型。我一般把过滤器清空,先抓全量请求再逐步缩小范围。

第三类是修改请求后服务器返回异常,比如500、参数解析失败。这种情况大概率是改了字段但没改对应的Content-Type,或者JSON格式少了个引号。发送前先检查请求体语法,尤其注意双引号和花括号的闭合。

5.3 版本差异与信息收集方法

WebGoat 2023与旧版相比,课程名称和请求路径会有细微差别。比如旧版把某些课放在“Access Control”里,新版可能直接叫“Broken Access Control”菜单;有些旧版关卡里的参数叫userId,新版变成了id。如果照着网上的老文章操作发现请求路径404了,不要慌,通常是路径结构变了。

我的信息收集方法是:登录进靶场后,先打开浏览器开发者工具的Network面板,把课程页面的所有请求都扫一遍。很多隐藏接口在页面初始化时会自动请求一次,即使前端不显示,网络面板里也会暴露路径。这个技巧能解决80%“找不到接口”的问题。另外,右键查看页面源代码,搜索hidden、access-control、admin这些关键词,也经常能翻出线索。把这些线索汇总起来,比端着老教程盲改要好很多。

6. 从通关到落地的安全修复思路

6.1 服务端授权是唯一信任边界

通关越多,越能体会一个结论:所有涉及权限判断的逻辑都必须写在后端。前端那些根据角色隐藏菜单、置灰按钮的方案,只有体验价值,没有安全价值。开发接口时,每一个敏感操作前都应有一个“我是谁”的身份识别和角色判定步骤。

我自己看代码时有个习惯:从路由层往Controller层走一遍,找到每个操作入口,确认有没有加统一的鉴权拦截。如果某个接口只靠前端URL隐藏来保护,那这个漏洞在这套靶场里一定走不过两关。在真实项目里,这几乎等于给攻击者送分。

6.2 对象级权限校验模板

IDOR类问题的最佳解法是对象级权限校验。核心原则是:查询数据时,在SQL或查询逻辑里带上当前用户的归属条件。

举个例子,如果有一个接口根据文件ID返回文件内容,正常逻辑应该是“先找到文件,再判断文件owner是否等于Session里的userId”。很多开发者喜欢先查文件再判断,结果在查询阶段就泄露了文件内容。正确顺序是先确定当前用户允许访问哪些文件,再执行查询。代码上可以抽象成一个方法,专门做“资源归属校验”,所有涉及业务对象ID的接口统一调用,能省掉一大半越权风险。

对于垂直越权,则要在服务端给接口和角色做矩阵映射,明确哪些角色能访问哪些方法和路径。Web框架里的拦截器、过滤器、注解都可以实现,关键是覆盖全,别只给管理页面加了限制而忘了背后的接口。

6.3 后续进阶方向

这套靶场通关只是起点,后面可以做三件事巩固:第一,拿一个模拟项目X做一次权限遍历测试,把所有带用户ID、订单号、文件ID的接口列出来,逐个尝试越权。第二,结合代码审计工具,查一下项目里有没有未统一鉴权的接口,这个在真实工作中很重要。第三,把眼光从Web接口扩展到文件下载、对象存储这些场景,IDOR不止存在于数据库主键,还常见于文件名、路径、云存储对象键。

我还建议把通关过程中修改过的请求保存成一份清单,包括原始请求、修改后的请求、响应结果。日后复盘时,这份清单比通关截图有价值得多。它记录的是你从“看到参数”到“想到越权”的完整链路,而这种链路思维,才是这套靶场真正想训练的东西。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进