一、先搞懂:GraphQL 到底是什么?

在聊漏洞之前,先科普下 GraphQL 的核心逻辑,老手可直接跳过——毕竟很多开发者都在用 GraphQL,却未必真正理解它的架构特性,这也是漏洞频发的根源。

GraphQL(Graph Query Language,图查询语言),是由 Facebook 2012 年内部研发、2015 年开源的 API 查询语言与服务器端运行时,核心定位是替代传统 REST API,解决 REST 接口“数据冗余”“多接口多请求”的痛点,目前已被 GitHub、PayPal 等众多平台广泛采用。

和 REST API 相比,GraphQL 有两个最核心的特点,也是它的优势,同时也是安全漏洞的重灾区:

1. 单一端点,所有操作统一入口

REST API 是“多接口多路径”,比如查询用户用 /api/user、修改密码用 /api/user/updatePwd、删除内容用 /api/article/delete,可以通过 URL 路径直接做权限区分;而 GraphQL 所有操作(查、改、删、添、上传)都走同一个端点(通常是 /graphql),后端无法通过 URL 路径判断操作权限,只能依赖 Resolver(解析函数)做校验。

2. 客户端按需请求,灵活性极高

REST API 会固定返回整包数据(比如请求/api/user/1,会返回用户所有字段),容易造成数据冗余;而 GraphQL 允许客户端自主指定需要的字段,比如只查用户名和邮箱,就不会返回密码、手机号等多余信息,极大提升了网络效率,尤其适合移动端场景。

举个简单的 GraphQL 查询示例(查询用户信息):

query {
  user(id: "1") {
    name  # 只请求用户名
    email # 只请求邮箱
  }
}

响应结果会和查询结构完全一致,不多返回任何冗余数据——这是它的优势,但也埋下了安全隐患:灵活性越高,权限控制的难度就越大,一旦校验缺失,攻击者就可能利用该漏洞获取非授权数据或操作。

补充一句:GraphQL 并非数据库查询语言(如 SQL),而是客户端与服务端之间的数据交互协议。其权限控制的核心在于 Resolver 的实现。

二、步入正题:GraphQL 权限缺失,隐患不容忽视

网上关于 GraphQL 安全的文章,大多集中在“信息泄露”“内省查询”“深度嵌套 DoS”这三类,却极少有人关注 Mutation(增删改操作)的权限控制缺失——而这正是我近期在真实渗透测试中发现的致命漏洞,也是本文的核心:

GraphQL 权限控制若存在缺失,各类操作(查、改、删、添、上传、查看个人信息)都能被越权执行,任意用户密码重置就是其中典型且高危的场景。

先从最直观、最高危的「任意用户密码重置」说起——这是我第一个发现的漏洞,也是最容易被忽视、网上报道极少的场景。

1. 实战漏洞:任意用户密码重置(无限制、无验证)

本次测试的系统的 GraphQL 接口为 POST /mmm-dgraph-backend-http-server/graphql,核心漏洞出在 updateUser 这个 Mutation 操作上(用于修改用户信息)。

1.1 漏洞触发场景

系统新用户注册时,默认密码为 6 个 6,登录后会强制修改新密码。此时抓包发现,修改密码的请求正是调用 updateUser 接口,传入 loginUserName(目标账号)、newPassword(新密码)两个核心参数,无任何其他校验。

关键发现:无论目标用户是否改过密码、改了多少次、密码多复杂,只要传入其账号,就能直接覆盖重置密码——学生、老师、管理员等各类角色均可能受影响,无需原密码、无需短信/邮箱验证码,普通用户即可操作。

1.2 越权重置 Payload 示例
mutation UpdateUser($targetAccount: String!, $newPwd: String!, $updateTime: Date!) {
  updateUser(
    filter: { account: $targetAccount },
    set: {
      password: $newPwd,
      updatedAt: $updateTime
    }
  ) {
    user {
      account
      updatedAt
    }
  }
}

请求变量(自定义目标账号和新密码):

{
  "targetAccount": "targetUser",  # 目标账号(示例,非真实账号)
  "newPwd": "examplePwd123.",    # 自定义新密码(示例)
  "updateTime": "2026-04-12T10:00:00Z"
}
1.3 漏洞本质(核心逻辑问题)

这个漏洞的本质,不是某个接口的“小bug”,而是 GraphQL 权限设计的根本性逻辑错误,后端 Resolver 代码的核心问题的是:只校验“用户是否登录”,不校验“当前用户是否有权限修改该账号”,也不校验“数据归属权”。

用伪代码还原漏洞现场(错误逻辑):

# 漏洞代码(后端 Resolver 逻辑)
def resolver_updateUser(args, context):
    # 只校验是否登录,不校验操作权限
    if not context.current_user:
        return PermissionDenied("请先登录")
    # 直接根据传入的 loginUserName 定位用户,覆盖密码
    loginUserName = args['filter']['login_user_name']
    newPassword = args['set']['password']
    db.user.update_one(
        {"login_user_name": loginUserName},
        {"$set": {"password": hash(newPassword)}}
    )
    return {"success": True}

一句话总结:只认账号,不认人;只认登录,不认权限;无旧密码、无二次验证,直接覆盖——这属于典型的“失效的访问控制”(OWASP Top 1 2021),也是 GraphQL Mutation 操作中常见的安全疏漏。

2. 延伸风险:权限缺失可能导致全功能越权

发现任意用户密码重置漏洞后,进一步测试发现:权限控制存在缺失,GraphQL 的各类操作都能被越权执行——这是由 GraphQL 单一端点的架构特性决定的,也是目前相关安全报道中较少提及的风险点。

结合本次测试场景,以及 GraphQL 通用安全特点,若权限控制不到位,以下操作均可能出现越权情况(普通用户操作其他用户或管理员数据):

2.1 越权查看:获取任意用户/管理员敏感信息

通过 queryUserqueryAllStudents 等 Query 操作,普通用户可直接查询其他用户的手机号、身份证号、成绩、考勤等敏感信息,甚至可通过 queryAllAdmins 获取所有管理员账号信息——这正是我之前发现的“全校身份信息泄露”漏洞,与密码重置漏洞形成“组合拳”:先批量获取账号,再批量重置密码。

越权查看 Payload 示例:

query {
  user(login_user_name: "admin_001") {  # 管理员账号
    name
    identity_card  # 身份证号(明文)
    mobile_number  # 手机号(明文)
    role           # 角色信息(管理员)
  }
}
2.2 越权修改:篡改任意用户信息

除了重置密码,通过 updateUser 接口,还可能修改任意用户的手机号、邮箱、头像、角色等信息——例如将普通用户角色改为“admin”获取管理员权限,或修改他人手机号接收验证码,进而接管账号。

2.3 越权删除:删除任意用户/内容

若系统存在 deleteUser(删除用户)、deleteArticle(删除文章)等 Mutation 操作,普通用户可能直接删除其他用户的账号、发布的内容,甚至删除管理员账号,影响系统正常运行。

2.4 越权上传:上传恶意文件到任意目录

若系统存在 uploadFile 接口,普通用户可能上传恶意文件(如webshell)到管理员目录、其他用户空间,进而获取服务器权限,对系统造成进一步渗透影响。

2.5 越权发布:冒充他人发布内容

通过 createArticlecreateNotice 等接口,普通用户可能传入他人的账号 ID,冒充老师、管理员发布通知、公告,甚至发布不良信息,造成负面影响。

这里有一个核心结论,也是本文最有价值的观点(建议单独加粗放在文章中):

GraphQL 越权并非单点漏洞,可能引发系统性安全风险。若权限控制缺失,查看、修改、删除、上传、发布、重置密码等操作,都可能被越权执行。

3. GraphQL 易出现全功能越权的原因

核心原因在于:开发者对 GraphQL 权限设计的理解存在疏漏,容易混淆“登录校验”和“权限校验”,具体主要有3点:

1. 误区一:“只要用户登录了,就可以调用所有接口”——忽略了“数据归属权”,比如普通用户只能操作自己的账号,不能操作他人的;

2. 误区二:“GraphQL 只需要做入口校验”——忽略了 GraphQL 单一端点的特性,无法通过 URL 控制权限,必须在每个 Resolver、每个字段上做细粒度校验;

3. 误区三:“Mutation 操作和 Query 操作权限一致”——忽略了 Mutation 是“写操作”(增删改),风险远高于 Query(读操作),需要更严格的权限控制和二次验证。

对比 REST API 和 GraphQL 的权限控制差异,更能理解这个问题:

对比维度

REST API

GraphQL

权限控制方式

可通过 URL 路径、HTTP 方法做粗粒度控制

只能通过 Resolver 做细粒度控制(字段级、操作级)

操作入口

多接口、多路径

单一接口(/graphql)

开发者易犯错误

路径权限配置遗漏

只做登录校验,不做数据归属权校验

三、漏洞危害与实战利用链

2. 完整利用链(从信息泄露到系统接管)

结合本次测试发现的两个核心漏洞,完整利用链如下(可直接写进漏洞报告):

Step 1:通过 GraphQL 内省查询(未关闭),获取系统所有 Query、Mutation 接口,确认 queryAllStudents(批量查询用户)和 updateUser(修改用户)接口的存在;

Step 2:调用 queryAllStudents 接口,传入 first: 100000offset: 0,批量获取全校所有用户的 login_user_name(学号/手机号);

Step 3:调用 updateUser 接口,批量重置所有用户(含管理员)的密码,自定义新密码;

Step 4:用重置后的密码,登录任意目标用户账号,接管账号后,可能进一步越权修改、删除数据或上传文件,对系统安全造成严重影响。

四、漏洞修复建议方案

针对 GraphQL 权限缺失导致的全功能越权,修复的核心是“做细粒度的权限控制”,从紧急修复到长期加固,分两步落地:

1. 修复建议

1. 强数据归属权校验(核心修复):所有 Query、Mutation 接口,必须校验“当前登录用户 ID == 目标用户 ID”,普通用户只能操作自己的账号,管理员只能操作授权范围内的账号;

正确 Resolver 逻辑(伪代码):

def resolver_updateUser(args, context):
    currentUser = context.current_user  # 当前登录用户
    # 1. 校验是否登录
    if not currentUser:
        return PermissionDenied("请先登录")
    # 2. 定位目标用户
    targetUser = db.user.find_one({"login_user_name": args['filter']['login_user_name']})
    # 3. 核心校验:只能修改自己的账号(管理员需额外判断角色)
    if currentUser.id != targetUser.id and currentUser.role != "admin":
        return PermissionDenied("无权限修改该用户信息")
    # 4. 校验原密码(新增)
    if not verify_password(args['oldPassword'], targetUser.password):
        return PermissionDenied("原密码错误")
    # 5. 执行密码更新
    db.user.update_one(...)
    return {"success": True}

2. 增加二次验证:修改密码、删除账号、修改角色等敏感操作,必须增加原密码校验、短信/邮箱验证码二次验证,禁止无验证直接操作;

3. 限制批量操作:单次请求只能操作一个用户/一条数据,禁止批量修改、批量删除;

4. 关闭生产环境内省查询:禁用 __schema__type 等内省查询,避免接口结构泄露,不给攻击者“全图透视”的机会;

5. 敏感字段脱敏:身份证号、手机号等敏感信息,仅本人和管理员可查,且必须脱敏(如 1****************9)。

2. 长期加固

1. 实现字段级+操作级双重权限控制:每个字段、每个 Query/Mutation 操作,都要单独做权限校验,不能一刀切;

2. 区分 Query 和 Mutation 权限:Mutation 操作(增删改)的权限控制要比 Query 操作(读)更严格;

3. 做查询复杂度、深度限制:防止深度嵌套查询导致 DoS 攻击,同时限制单次查询的字段数、数据量;

4. 全量日志审计:记录所有 GraphQL 操作(请求 IP、用户、操作内容、时间),便于漏洞溯源和攻击排查;

5. 定期安全测试:针对 GraphQL 接口,重点测试越权、注入、批量操作等漏洞,避免类似问题重复出现;

6. 规范 Resolver 开发:将权限控制逻辑抽离为公共工具函数,避免重复开发导致的校验遗漏,同时将权限控制移至业务逻辑层,而非仅在 Resolver 中简单校验。

五、漏洞挖掘思路

结合本次实战经验,给大家分享一套 GraphQL 越权漏洞的挖掘思路,适用于所有使用 GraphQL 的系统,新手也能快速上手:

1. 第一步:判断系统是否使用 GraphQL——查看请求地址,若存在 /graphql 端点,且请求方法为 POST,大概率是 GraphQL 接口;

2. 第二步:尝试内省查询,获取所有接口结构——通过 __schema 查询,获取所有 Query、Mutation 接口,以及参数、返回字段;

3. 第三步:测试 Query 接口越权——用普通用户登录,尝试查询其他用户、管理员的信息,看是否能成功返回;

4. 第四步:测试 Mutation 接口越权——重点测试 updateXXXdeleteXXXcreateXXX 等操作,尝试操作其他用户的数据;

5. 第五步:测试批量操作——尝试在一个请求中,批量查询、批量修改多个用户的数据,看是否能绕过限制;

6. 第六步:测试敏感操作——重点测试密码重置、角色修改、文件上传等操作,看是否需要二次验证、是否有权限校验。

六、总结与思考

GraphQL 的灵活性是其显著优势,但也使其成为安全漏洞的高发领域。本文所剖析的「GraphQL 权限缺失可能导致全功能越权」问题,之所以相关报道较少,核心原因是:多数开发者、安全从业者,更多关注 GraphQL 的“查询侧”安全,而忽视了“修改侧”(Mutation)的权限控制——这恰恰是容易引发高危风险的环节。

最后,用一句金句总结本文核心,也希望能提醒所有开发者:

GraphQL 安全离不开登录校验、操作权限校验、数据归属权校验,三者缺一不可。任何一项缺失,都可能导致系统出现越权漏洞,给攻击者可乘之机。

希望本文能填补网上 GraphQL 安全领域的相关空白,让更多开发者重视 GraphQL 权限设计,也让更多安全从业者掌握这类漏洞的挖掘思路,共同提升系统安全防护水平。

声明(合规必加)

本文仅用于网络安全技术交流、漏洞防御科普,所有案例均为授权测试或已修复场景。严禁未经授权对任何系统进行渗透测试,违者自行承担法律责任。

创作不易,收藏+关注,后续持续更新实战漏洞分析、渗透测试技巧!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐