目录一、前言二、知识梳理IDOR与水平越权三、漏洞复现一、前言在 Web 安全领域访问控制失效Broken Access Control常年稳居 OWASP Top 10 的第一位。它不像 SQL 注入、命令执行那样炫技却是真实业务系统中最常见、也最容易被开发者忽略的一类问题——接口本身工作正常、功能也完全正常唯独少了一行这个资源到底是不是你的的校验。其中水平越权IDORInsecure Direct Object Reference是最典型的一种服务端直接用客户端传入的对象标识自增 id、订单号、文件名……去数据库里取数据却不校验该对象是否归属于当前登录用户。攻击者只要把参数里的数字改一改就能读到、甚至改掉别人的数据。本文记录的是我在一次授权测试中发现的一个真实案例某高校主办的中文科技期刊投稿采编系统普通作者登录后仅需篡改撤稿请求中的稿件 id 参数就能查看并撤回删除其他作者的投稿稿件二、知识梳理IDOR与水平越权1、认证 ≠ 授权认证Authentication解决你是谁比如登录、Cookie、Token授权Authorization解决你能动什么比如这篇稿子是不是你的。很多系统只做了认证却默认登录了就是自己人于是在授权环节留下缺口。2、垂直越权 vs 水平越权垂直越权低权限用户干了高权限用户才能干的事如普通用户调用了管理员接口水平越权同一权限级别的用户之间越界A 能操作 B 的数据——本文要讲的就是这一种。3、IDOR不安全的直接对象引用当服务端接口直接使用客户端可控的、可预测的对象标识来定位数据并且**不做归属校验**时就构成 IDOR。典型特征就是URL 或表单里出现 id123、orderNo2025xxx、filexxx.pdf 这类参数改一下就能访问到别人的资源。三、漏洞复现5.1 环境与工具准备测试账号自建两个普通作者账号下文称账号 A、账号 B各自投稿一篇文章用于构造跨用户场景思路很简单用账号 A 的身份去操作账号 B 的稿件。如果成功则越权成立。一般测试情况下不允许更改数据库中的任何数据为了方便复现尽量准备两个账号5.2 步骤一两个账号各自投稿先登录账号 A 和账号 B分别进入作者中心的已投稿件页面并各投一篇稿用于后续对比与验证。从图中可以看到稿件列表中出现了两条稿件记录稿号 202502005投稿时间 2025/2/16 18:31:49和稿号 202502006标题 qweqwe投稿时间 2025/2/16 18:39:45状态均为正在初审。这两篇稿子分属两个不同的账号正是我们要用来验证越权的靶子。5.3 步骤二定位越权点——撤稿功能里的 id进入已投稿件后我注意到列表里提供了撤稿功能。点开稿件详情 / 撤稿操作时一个关键细节出现了稿件详情页地址为 /author/ScriptInfoEditor.aspx?id6154撤稿请求地址为 /author/AuthorCG.aspx?id6153scriptnameqweid 是稿件编号直接以明文形式出现在 URL 中而且很可能是自增的。这就是 IDOR 的经典信号——对象的引用标识id由客户端直接提供。此时只需问一句服务端有没有校验这个 id 属于当前登录用户接下来就是验证。5.4 步骤三拦截并篡改 id开启 Burp 的拦截功能在账号 A 下点击撤稿可以看到两个请求包都携带了 id 参数。按照测试思路我把两个请求包中的 id 分别修改为想要操作的目标稿件 id即账号 B 的稿件 id。以其中一个请求为例已脱敏仅保留结构httpPOST /author/ScriptInfoEditor.aspx?id6153 HTTP/1.1Host: www.xxx.edu.cnCookie: ASP.NET_SessionId******; CUSTOMASPXAUTH******Content-Type: application/x-www-form-urlencodedContent-Length: 7378Cache-Control: max-age0Origin: https://www.xxx.edu.cnUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8Sec-Fetch-Site: same-origin.......要点只有两个请求携带的是账号 A 的合法会话ASP.NET_SessionId / CUSTOMASPXAUTH而 URL 里的 id 被换成了账号 B 的稿件编号。如果服务端只看 id 不看归属那么用 A 的会话操作 B 的稿件就会成功。5.5 步骤四第一次放行——先观察现象放行第一个请求后页面确实产生了变化但只是列表里的标题发生了变化URL 中的 id 并没有变成我们想操作的那个 id。这说明单次放行并没有让操作彻底生效该功能实际上涉及多个请求的先后配合一个负责回显/选中一个负责真正提交。于是继续对第二个请求包做同样处理。复盘小记越权测试时不要因为第一次没成功就放弃。很多业务功能的写操作是分步提交的需要把整条链路都改到位。观察响应差异标题变了但 URL 没变正是判断还差一步的依据。5.6 步骤五确认撤稿并二次拦截接着点击确定撤稿在弹窗中填写撤稿原因同时开启拦截。此时抓到的请求为httpPOST /author/AuthorCG.aspx?id6154scriptnameqwe HTTP/1.1Host: www.xxx.edu.cnCookie: ASP.NET_SessionId******; CUSTOMASPXAUTH******Content-Type: application/x-www-form-urlencodedContent-Length: 3919Origin: https://www.xxx.edu.cnUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61可以看到请求中的 id 已经是我们修改后的目标稿件 id。这一步是临门一脚确认了提交阶段同样没有再做归属校验。5.7 步骤六放行——越权撤稿成功再次放行页面提示发送信息成功并且地址栏中的 id 已经变成了目标稿件的编号。至此证明账号 A 在只持有自己合法会话的情况下成功撤回删除了账号 B 的稿件。水平越权成立。整个利用链可以浓缩为一句话登录任意作者账号 → 发起撤稿 → 把请求里的 id 换成目标稿件 id → 放行 → 他人稿件被删除。四、漏洞原理分析结合抓包结果可以从几个层面解释这个漏洞为什么成立。1、服务端只做认证没做授权系统使用 ASP.NET Forms 认证CUSTOMASPXAUTH 票据确认请求来自一个已登录用户但在涉及稿件操作的接口中没有校验这篇稿件是否属于当前会话对应的用户。这是最根本的原因。2、直接对象引用 可预测的主键稿件 id 直接以明文形式出现在 URL 查询串中且为连续/可预测的数字。攻击者无需猜测、无需爆破只要把数字改一改就能命中别人的稿件。这是 IDOR 的便利条件。
