☰
【SRC实战复盘】某高校期刊投稿系统水平越权(IDOR)漏洞:一次中危漏洞的完整挖掘记录
2026/9/26 11:10:30 网站建设 项目流程

目录

一、前言

二、知识梳理:IDOR与水平越权

三、漏洞复现


一、前言

在 Web 安全领域,访问控制失效(Broken Access Control)常年稳居 OWASP Top 10 的第一位。它不像 SQL 注入、命令执行那样"炫技",却是真实业务系统中最常见、也最容易被开发者忽略的一类问题——接口本身工作正常、功能也完全正常,唯独少了一行"这个资源到底是不是你的"的校验。

其中,水平越权(IDOR,Insecure Direct Object Reference)是最典型的一种:服务端直接用客户端传入的对象标识(自增 `id`、订单号、文件名……)去数据库里取数据,却不校验该对象是否归属于当前登录用户。攻击者只要把参数里的数字改一改,就能读到、甚至改掉别人的数据。

本文记录的是我在一次授权测试中发现的一个真实案例:某高校主办的中文科技期刊投稿采编系统,普通作者登录后,仅需篡改"撤稿"请求中的稿件 `id` 参数,就能查看并撤回(删除)其他作者的投稿稿件

二、知识梳理:IDOR与水平越权

1、认证 ≠ 授权

认证(Authentication):解决"你是谁",比如登录、Cookie、Token;

授权(Authorization):解决"你能动什么",比如"这篇稿子是不是你的"。

很多系统只做了认证,却默认"登录了就是自己人",于是在授权环节留下缺口。

2、垂直越权 vs 水平越权

垂直越权:低权限用户干了高权限用户才能干的事,如普通用户调用了管理员接口;

水平越权:同一权限级别的用户之间越界,A 能操作 B 的数据——本文要讲的就是这一种。

3、IDOR(不安全的直接对象引用)

当服务端接口直接使用"客户端可控的、可预测的对象标识"来定位数据,并且**不做归属校验**时,就构成 IDOR。典型特征就是:URL 或表单里出现 `id=123`、`orderNo=2025xxx`、`file=xxx.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?id=6154`

撤稿请求地址为 `/author/AuthorCG.aspx?id=6153&scriptname=qwe`

`id` 是稿件编号,直接以明文形式出现在 URL 中,而且很可能是自增的。

这就是 IDOR 的经典信号——对象的引用标识(`id`)由客户端直接提供。此时只需问一句:服务端有没有校验这个 `id` 属于当前登录用户?接下来就是验证。

5.4 步骤三:拦截并篡改 `id`

开启 Burp 的拦截功能,在账号 A 下点击"撤稿",可以看到两个请求包都携带了 `id` 参数。按照测试思路,我把两个请求包中的 `id` 分别修改为想要操作的目标稿件 id(即账号 B 的稿件 id)。

以其中一个请求为例(已脱敏,仅保留结构):

```http

POST /author/ScriptInfoEditor.aspx?id=6153 HTTP/1.1

Host: www.xxx.edu.cn

Cookie: ASP.NET_SessionId=******; CUSTOMASPXAUTH=******

Content-Type: application/x-www-form-urlencoded

Content-Length: 7378

Cache-Control: max-age=0

Origin: https://www.xxx.edu.cn

Upgrade-Insecure-Requests: 1

User-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

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Sec-Fetch-Site: same-origin

.......

```

要点只有两个:请求携带的是账号 A 的合法会话(`ASP.NET_SessionId` / `CUSTOMASPXAUTH`),而 URL 里的 `id` 被换成了账号 B 的稿件编号。如果服务端只看 `id` 不看归属,那么"用 A 的会话操作 B 的稿件"就会成功。

5.5 步骤四:第一次放行——先观察现象

放行第一个请求后,页面确实产生了变化,但只是列表里的标题发生了变化,URL 中的 `id` 并没有变成我们想操作的那个 id。

这说明:单次放行并没有让操作彻底生效,该功能实际上涉及多个请求的先后配合(一个负责回显/选中,一个负责真正提交)。于是继续对第二个请求包做同样处理。

复盘小记:越权测试时,不要因为"第一次没成功"就放弃。很多业务功能的写操作是"分步提交"的,需要把整条链路都改到位。观察响应差异(标题变了但 URL 没变),正是判断"还差一步"的依据。

5.6 步骤五:确认撤稿并二次拦截

接着点击"确定撤稿",在弹窗中填写撤稿原因,同时开启拦截。此时抓到的请求为:

```http

POST /author/AuthorCG.aspx?id=6154&scriptname=qwe HTTP/1.1

Host: www.xxx.edu.cn

Cookie: ASP.NET_SessionId=******; CUSTOMASPXAUTH=******

Content-Type: application/x-www-form-urlencoded

Content-Length: 3919

Origin: https://www.xxx.edu.cn

User-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 的"便利条件"。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询