如果你在寻找漏洞利用方式,请参考下面的文章
GraphQL API 漏洞 |网络安全学院
GitHub - swisskyrepo/PayloadsAllTheThings: A list of useful payloads and bypass for Web Application Security and Pentest/CTF
GraphQL 查询(Query)
GraphQL 既不是架构,也不是数据库。它是一种 查询语言 和 运行时环境,用于 API 的数据查询和操作。
假设你有一个使用关系型数据库(如 MySQL 或 PostgreSQL)的应用,数据库中存储了用户信息和文章数据。你可以通过 GraphQL 构建一个 API,它允许客户端按需查询用户信息和文章内容:
- 你可以通过 GraphQL 查询来请求某个用户的基本信息和他的文章:
{
user(id: 1) {
name
email
posts {
title
content
}
}
}
- GraphQL 会将这个请求转发到服务器端,服务器端会通过数据库查询(例如 SQL 查询)获取数据,然后将结果返回给客户端。
响应可能看起来是这样的
{
"data": {
"user": {
"name": "张三",
"email": "zhangsan@example.com",
"posts": [
{
"title": "GraphQL简介",
"content": "GraphQL是一种用于API的查询语言和运行时环境。"
},
{
"title": "Apollo Server使用指南",
"content": "Apollo Server是一个用于构建GraphQL服务器的库。"
}
]
}
}
}
所以,GraphQL 是一种 灵活的数据查询工具,它位于应用的 API 层,能让客户端高效地与不同的数据源交互,但它本身不负责数据库的存储和管理。
GraphQL 结构与类型信息
在 GraphQL 中,结构 和 类型信息 主要指的是 API 中的 数据模型 和 查询接口,它们描述了你可以查询的数据的组织形式和每个数据字段的类型。通过这些信息,客户端能够了解如何正确地查询 API,以及 API 返回的每个字段的数据类型是什么。下面是对这些术语的详细解释。
1. GraphQL API 的结构
结构 指的是 GraphQL 服务的 整体设计,包括:
- 类型(Types):GraphQL API 中定义的各种数据类型。例如,
User
、Post
、Comment
等,通常表示你在应用中处理的数据模型。 - 字段(Fields):每个类型下的字段,这些字段是 GraphQL 查询的结果。例如,一个
User
类型可能有id
、name
、email
等字段。 - 查询(Queries)、变更(Mutations)、订阅(Subscriptions):这些是你可以对 API 执行的操作。查询用于获取数据,变更用于修改数据,订阅用于实时接收数据更新。
2. GraphQL API 的类型信息
类型信息 指的是 API 中各个类型的具体定义和它们之间的关系。这些信息包括:
2.1 基本类型(Scalar Types)
GraphQL 提供了几种基本的标量类型,用于描述单一值的类型:
- Int:整数
- Float:浮动点数
- String:字符串
- Boolean:布尔值(
true
或false
) - ID:唯一标识符,通常用作主键(类似于
UUID
)
2.2 对象类型(Object Types)
对象类型是 GraphQL 中最常见的类型,用于定义一个包含多个字段的数据结构。例如,一个 User
类型可能定义如下:
type User {
id: ID!
name: String!
email: String
posts: [Post]
}
User
类型包含了几个字段,如id
(ID 类型,必需)、name
(String 类型,必需)、email
(String 类型,非必需)、posts
(一个 Post 类型的数组,表示该用户的所有帖子)。ID!
表示id
字段是必需的(非null
)。posts
字段是一个 数组,并且是Post
类型的数组,这意味着每个用户可能有多个帖子。
2.3 输入类型(Input Types)
输入类型用于描述查询或变更请求中传递的数据结构。例如,一个 CreateUser
类型可以定义如何创建一个用户:
input CreateUser {
name: String!
email: String!
}
CreateUser
类型定义了创建用户时需要提供的字段,name
和email
都是必需的。
2.4 枚举类型(Enum Types)
枚举类型用于定义一组固定的值。例如,定义用户角色的枚举类型:
enum Role {
ADMIN
USER
GUEST
}
Role
枚举类型包含了三个可能的值:ADMIN
、USER
和GUEST
,这些可以用于限制字段的输入值。
2.5 接口类型(Interface Types)
接口类型允许你定义一组共享字段的类型。比如,我们可以定义一个 Node
接口,使得多个类型都继承这个接口,保证它们有相同的字段:
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
type Post implements Node {
id: ID!
title: String!
}
Node
接口有一个id
字段,任何实现了这个接口的类型(如User
或Post
)都必须拥有id
字段。
2.6 联合类型(Union Types)
联合类型允许一个字段返回多种类型中的任意一种。与接口类似,但联合类型不强制实现相同的字段,只是定义了多个可能返回的类型:
union SearchResult = User | Post
SearchResult
可以是User
或Post
,具体返回哪一个类型由实际查询的结果决定。
GraphQL 内省(Introspection)
GraphQL 内省(Introspection) 是 GraphQL 的一个强大特性,它允许客户端查询 GraphQL API 的 结构 和 类型信息,而不仅仅是查询应用程序的数据。通过内省,客户端可以动态地了解 GraphQL API 的模式、类型、查询、变更等元数据。
内省的工作原理
GraphQL 内省是通过一种特别的查询实现的,通常叫做 内省查询(Introspection Query)。这些查询返回关于 GraphQL API 的详细信息,比如:
- 类型:查询支持的类型(如
Query
、Mutation
、Subscription
类型及其字段)。 - 字段:每个类型下的字段及其类型。
- 输入类型:GraphQL API 中支持的输入类型。
- 枚举类型:GraphQL API 中定义的枚举类型及其可能的值。
- 错误信息:每个字段可能返回的错误类型。
简单来说,内省使得客户端能够自我探索 API 的结构,而不需要依赖文档或其他外部信息。
一个典型的内省查询例子
一个典型的内省查询示例如下:
{
__schema {
types {
name
}
}
}
这个查询会返回所有在 GraphQL API 中定义的类型的名称。你可以利用内省查询获得更详细的信息,比如查看每个类型的字段、字段的返回类型、查询或变更的定义等。
常见的内省查询示例
-
获取所有类型:
{ __schema { types { name } } }
这个查询返回 API 中的所有类型。
-
获取类型字段:
如果你想查询某个特定类型的字段,可以使用类似如下的查询:{ __type(name: "User") { fields { name type { name } } } }
这个查询返回
User
类型的字段及其类型。 -
获取某个字段的详细信息:
比如查询某个字段的数据类型和输入类型:{ __type(name: "User") { fields { name args { name type { name } } } } }
某些 GraphQL 服务器允许禁用内省查询,尤其是在生产环境中,以提高安全性。例如,使用 Apollo Server 时,你可以通过设置 introspection
配置来禁用内省:
const server = new ApolloServer({
schema,
introspection: false, // 禁用内省
});
GraphQL 突变(Mutation)
GraphQL 突变(Mutation) 是 GraphQL 中的一种操作类型,用于 修改数据。与查询(Query)不同,查询用于 获取数据,而突变用于对数据进行 创建、更新或删除 操作。
1. 突变的基本概念
在 GraphQL 中,Mutation
是一种特定的操作类型,就像 Query
用来请求数据一样,Mutation
用来处理数据修改请求。每个突变操作通常会返回修改后的数据,或者返回表示操作结果的信息。
突变操作 的目标通常是数据库的更改,比如:
- 创建一个新记录(如添加用户、发布文章等)。
- 更新一个已有记录(如修改用户信息、修改文章内容等)。
- 删除一个记录(如删除用户、删除文章等)。
2. GraphQL 突变的定义
在 GraphQL schema 中,Mutation
类型是专门用来定义数据修改操作的。例如:
type Mutation {
createUser(name: String!, email: String!): User
updateUser(id: ID!, name: String, email: String): User
deleteUser(id: ID!): Boolean
}
这里定义了三个突变操作:
createUser
:创建一个新的用户,返回一个User
类型的对象。updateUser
:更新用户的信息,接受用户的id
和可选的name
、email
,返回更新后的User
对象。deleteUser
:删除一个用户,返回一个布尔值(true
或false
),表示删除操作是否成功。
3. 突变的执行
执行突变时,你会发送一个包含特定操作和参数的请求。例如,要创建一个用户,你可以这样写:
mutation {
createUser(name: "Alice", email: "alice@example.com") {
id
name
email
}
}
在这个突变请求中:
mutation
关键字表示这是一个突变请求。createUser
是我们在 schema 中定义的突变操作。name
和email
是createUser
操作需要的参数。- 你可以指定想要返回的字段,比如返回新创建的用户的
id
、name
和email
。
4. 突变的响应
执行突变后,服务器会返回执行结果。例如,上面的 createUser
突变成功时,服务器可能返回如下结果:
{
"data": {
"createUser": {
"id": "1",
"name": "Alice",
"email": "alice@example.com"
}
}
}
返回的数据会包含你在请求中指定的字段(如 id
、name
、email
)。
5. 突变的特点
- 数据修改:突变通常用于改变数据的状态(创建、更新、删除)。
- 副作用:突变操作通常会有副作用,可能涉及到数据库更新、缓存修改等。
- 同步与返回:突变操作通常会返回修改后的数据,或者操作是否成功的反馈(如布尔值)。
- 可以返回多个字段:虽然突变修改了数据,但你依然可以指定哪些字段返回给客户端,确保客户端可以在修改后获得最新的数据。
6. 突变和查询的区别
- 查询(Query):用于 读取数据,无副作用,不会改变服务器上的任何数据。
- 突变(Mutation):用于 修改数据,会产生副作用,可能涉及创建、更新、删除操作。
一个典型的查询请求示例:
query {
getUser(id: "1") {
id
name
email
}
}
一个典型的突变请求示例:
mutation {
updateUser(id: "1", name: "Alice Updated", email: "aliceupdated@example.com") {
id
name
email
}
}
7. 如何处理多个突变
GraphQL 允许你在一个请求中执行多个突变,但它们的执行是 顺序的,并且所有的突变都必须成功,才能返回一个完整的响应。比如,你可以在一个请求中同时更新用户的 name
和 email
:
mutation {
updateUser(id: "1", name: "Alice Updated") {
id
name
}
updateUser(id: "1", email: "aliceupdated@example.com") {
id
email
}
}
8. 突变和错误处理
突变操作有可能会失败,通常由于参数不正确、数据不存在或者权限问题等。GraphQL 的错误处理机制允许返回详细的错误信息。例如,如果 updateUser
操作传入了无效的 id
,响应可能如下:
{
"errors": [
{
"message": "User not found",
"locations": [{ "line": 2, "column": 3 }],
"path": ["updateUser"],
"extensions": {
"code": "USER_NOT_FOUND"
}
}
],
"data": null
}