freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

从零开始 Attack the GraphQL API
2024-12-30 21:13:43
所属地 北京

0x00 Introduction

GraphQL 是由 Facebook 研发的 API 查询语言,旨在提高客户端和服务器之间的通信效率。在某些时候,REST API 需要被多次调用才能返回所需要的资源,而 GraphQL 允许客户端明确指定其所需的数据,可用于精确查询数据。GraphQL 官方文档:https://graphql.org/,中文文档:https://graphql.cn/

如下是一个简单的例子,query表示查询操作,{}表示选择集,Book表示要查询的对象,id: "1"表示查询指定参数 id 为 1,nameauthor表示要查询的字段。

query {
Book(id: "1") {
name
author
}
}

为了后续更好的学习和理解 GraphQL Attack,需要学习几个 GraphQL 的相关概念,here we go。

1. Schema

Schema(模式)在 GraphQL 中是一种数据模型的定义,描述了客户端可以访问和操作的数据结构。就像是数据库的架构图或 API 文档,规定了可以查询(query)和修改(mutation)的数据类型以及它们之间的关系。一个 Schema 中主要包括三种操作类型,分别为:querymutationsubscription

这么说有点抽象,举个具体的例子,如下:定义了一个名为Book的对象类型,该类型有 4 个字段,分别是idtitleauthoryear。可以简单的类比为一个 Book 类,但这个类只有属性没有方法。注:这些类型都是在服务器上通过 SDL(Schema Definition Language)进行定义。

type Book {
id: ID!
title: String!
author: String!
year: Int
}

定义了Book类型后,为了供查询,还需在服务端定义一个Query类型(查询类型是每个 Schema 中必有的,参考:https://spec.graphql.org/draft/#sec-Root-Operation-Types)。

type Query {
# 查询所有书籍,返回包含所有书籍的数组
books: [Book!]!

# 按照书籍 ID 查询单本书籍
bookById(id: ID!): Book

# 按作者查询书籍,返回该作者的所有书籍
booksByAuthor(author: String!): [Book!]!

# 查询在指定年份之前出版的书籍
booksBeforeYear(year: Int!): [Book!]!
}

如果想要修改某条数据,还需要定义Mutation类型(可选,如果需要修改数据,就得定义该类型)。

type Mutation {
# 添加一本新书
addBook(id: ID!, title: String!, author: String!, year: Int): Book!

# 更新书籍的信息
updateBook(id: ID!, title: String!, author: String!, year: Int): Book!

# 删除一本书
deleteBook(id: ID!): Boolean!
}

除此之外,还有一个Subscription类型可以根据业务需求进行定义(可选)。

type Subscription {
# 订阅新增书籍事件
bookAdded: Book!

# 订阅删除书籍事件
bookDeleted: ID!
}

好了,将上述定义的BookQueryMutationSubscription四个对象类型组合在一起,就构成了一个完整的 GraphQL Schema。

2. Type

数据模式(Schema)的抽象是通过 Type 来描述的,每个 Type 有若干个 Field(字段)组成,每个 Field 又分别指向某个 Type。Type 可分为:ScalarObjectInterfaceUnionEnumInput这几类,重点放到Scalar Type(标量类型)和Object Type(对象类型)。

Scalar Type包括:StringIntFloatBooleanEnumID,比如下述代码中的ID!String!Int都是标量类型(后面跟着!符,表示该字段不能为空),除此之外还可以声明新的标量(不是重点)。Object Type可以用来表达一些复杂的数据类型,比如下述代码中的Book就是对象类型,包含了 4 个 Field(字段),每个 Field 又指向了某个 Type。

type Book {
id: ID!
title: String!
author: String!
year: Int
}

字段的类型一般使用标量类型,但也可以是另一个对象类型。

type User {
id: ID
name: String
}

type Book {
id: ID!
title: String!
author: User
}

3. Operations

GraphQL 规范中主要包括三种操作类型,分别是:Query(查询,用于从服务器查询数据)、Mutation(变更,用于操作服务器上数据,包括:添加、修改、删除等)、Subscription(订阅),这三者也被称为root operation type,名为根操作类型。

简单来说就是,虽然定义了Book类型,但是这个类型不能直接被访问和操作,需要通过规范中的 Query、Mutation、Subscription 三个根操作类型来实现,且这三个根操作类型需要显式的被定义,无法直接使用。注:在 Schema 中除了 Query 是必须有的外,另外两种是可选的。

3.1 Query

定义根查询类型时,可以根据需要自定义命名(默认为Query),自定义命名后,需要在schema定义中进行标识。只能根据Query定义的格式来进行查询。

# scheme 定义用于指定 Query、Mutation、Subscription 三个操作类型的入口
# 使用的是默认命名(Query、Mutation、Subscription),则可以省略 schema 定义

schema {
query: RootQueryType
mutation: RootMutation
}

type RootQueryType {
books: [Book!]!

# 根据 ID 来查询书籍
bookById(id: ID!): Book
}

type Book {
id: ID!
title: String!
author: String!
year: Int
}

type RootMutation {
# 添加一本书
addBook(title: String!, author: String!, year: Int): Book!

# 更新一本书的信息
updateBook(id: ID!, title: String, author: String, year: Int): Book!

# 删除一本书
deleteBook(id: ID!): Boolean!
}

type Subscription {
# 订阅新增书籍事件
bookAdded: Book!

# 订阅删除书籍事件
bookDeleted: ID!
}

GraphQL Query 类似于 RESTful API 的 GET 请求,主要是用于向服务器获取指定的数据。上述是服务器定义的 GraphQL Schema,下述是在客户端发起查询请求。如下三种查询方式都是通用的。

# 查询所有书籍
query {
books{
id
title
author
year
}
}

# 根据 ID 进行查询
query {
bookById(id: "1") {
id, title, author, year
}
}

# 根据作者查询书籍
query {
booksByAuthor(author: "JJ1ng") {
id title year
}
}

# 根据指定年份查询书籍
query {
booksBeforeYear(year: 2025) {
id
title
author
}
}

在进行 Query 查询操作时,可以省略该关键词,但对于 Mutation、Subscription 操作不能省略。

query {
bookById(id: "1") {
id
title
author
year
}
}

# 上述可改为如下形式
{
bookById(id: "1") {
id
title
author
year
}
}

并且可以给查询自定义命名(称为操作名称),可写可不写。如下述的getBookById

query getBookById {
bookById(id: "1") {
id
title
author
year
}
}

3.2 Mutation

GraphQL Mutation 主要用于编辑、修改、删除等操作,Mutation 同样可以使用类型、名字、结构等内容来指定被修改的数据。如果在 Schema 中定义了 Mutation 操作类型,那么就可以对服务器上的数据进行预定义的修改。

mutation {
addBook(title: "1984", author: "George Orwell", year: 1949) {
id
title
author
year
}
}

3.3 Subscription

GraphQL Subscription 用于实时推送数据更新。客户端可以订阅某些事件,当服务器端数据发生变化时,服务器会主动向客户端推送更新。主要用于实时交互的场景。

subscription {
bookAdded {
id
title
author
}
}

4. Introspection

Introspection(内省)是 GraphQL 的内置功能,是用于查询服务端已定义好的 Schema 有关的信息,且在内省系统中还内置了很多元数据类型,包括:__schema__type__typeKind__field__inputValue__enumValue__directive。想了解完整的结构以及每个字段的具体含义,可参考文档:https://spec.graphql.org/draft/#sec-Schema-Introspection,如下介绍几个常用的元数据类型的原型。

type __Schema {
description: String
types: [__Type!]!
queryType: __Type!
mutationType: __Type
subscriptionType: __Type
directives: [__Directive!]!
}

type __Type {
kind: __TypeKind!
name: String
description: String
fields(includeDeprecated: Boolean = false): [__Field!]
interfaces: [__Type!]
possibleTypes: [__Type!]
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
inputFields(includeDeprecated: Boolean = false): [__InputValue!]
ofType: __Type
specifiedByURL: String
}

type __Field {
name: String!
description: String
args(includeDeprecated: Boolean = false): [__InputValue!]!
type: __Type!
isDeprecated: Boolean!
deprecationReason: String
}

__schema结构中,queryTye是用于表示 GraphQL 服务的查询类型,并且该对象类型为__Type,而__Type类型中包含了一个字段name,该字段用于记录查询类型的入口,还包含一个fields字段,该字段一个集合,记录查询类型所支持的查询字段。如下请求用于查询 GraphQL 服务的查询类型名(如果未修改,则默认是Query),以及其所有的字段和字段的类型。

{
__schema {
queryType {
name
fields {
name
type {
name
}
}
}
}
}

与之类似的还有mutationTypesubscriptionType对象。

{__schema{mutationType{name, fields{name type{name}}}}}
{__schema{subscriptionType{name, fields{name type{name}}}}}

__schema结构中,还有一个types字段,该字段是一个集合,记录了 Schema 中所有的类型(包括:对象类型、标量类型、元数据类型)。

{
__schema {
types {
name
}
}
}

__schema结构中的types字段,类型为__Type,而__Type类型又包含一个fields字段。在fields字段中存在一个name字段,该字段用于记录字段的名字,也就是说通过如下查询,能获取到 GraphQL 服务所有的类型,和这些类型包含哪些字段。

{
__schema {
types {
name
fields {
name
}
}
}
}

查询指定类型的所有字段。

{
__type(name:"user"){
fields{
name
}
}
}

0x01 Vulnerability

GraphQL 测试工具:

1. Detection

GraphQL 通过类型和名字来定义如何处理查询,而不是像 RESTful API 那样是通过端点和 HTTP 请求方法来处理查询。也就是说,所有的 GraphQL 操作都使用同一端点(可以理解为同一 URL,通常请求方法为 POST)。常见的端点字典:https://github.com/danielmiessler/SecLists/blob/fe2aa9e7b04b98d94432320d09b5987f39a17de8/Discovery/Web-Content/graphql.txt

在找到 GraphQL 端点后,可以通过简单的测试来探测目标是否使用 GraphQL 服务,请求类型为application/json,如果没有正确的回显,可以尝试不同的请求的方式以及不同的请求类型。

{"query": "{}"}
{"query": "{__schema}"}
{"query": "{__typename}"}
{"query": "{__schema{types{name}}}"}
{"query": "{__schema{queryType{name}}}"}
{"query": "{__schema{types{name,fields{name}}}}"}

响应如下则表示为 GraphQL 服务。元数据字段__typename用于返回当前对象的类型,存在于任何的 GraphQL 服务中。

2. Info Gathering

由于在真实环境中是无法知道目标 GraphQL Schema 所支持的查询类型和字段名,但可以通过内省查询来获取这些信息(类似于 Swagger API 说明文档)。除此之外,内省查询还会泄漏一些敏感信息,所以在真实环境中,记得禁用内省查询。

{"query": "query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}"}

query {__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}

也可以直接通过 BurpSuite 内置的功能来进行内省查询。

如果目标禁用内省查询,可以通过工具 Clairvoyance 来进行自动化收集 API 信息。

# 安装
pip install clairvoyance

# 使用
clairvoyance https://xxx.com/graphql -o schema.json

除此之外,还可以通过爆破的形式来获取目标的信息,参考下述“Information Disclousure”一节。字典:https://github.com/Escape-Technologies/graphql-wordlist

3. Visualizing

内省查询的结果往往很长而难以处理,可以使用 graphql-voyager 来可视化 Schema 实体之间的关联。可通过在线网站来实现,地址:https://graphql-kit.com/graphql-voyager/。将内省查询的结果复制到箭头处即可。

4. DVGA

DVGA(Damn Vulnerable GraphQL Application,易受攻击的 GraphQL 程序)是一个 GraphQL 的靶场环境,可用于学习和锻炼 GraphQL 安全相关的技能,靶场地址:https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application

GraphQL 只是一种 API 查询技术,同样会存在常规的漏洞,比如:涉及到数据库就可能会存在 SQL 注入、涉及到远程加载就有可能存在 SSRF、涉及到 JWT 就有可能存在 JWT 相关的漏洞,所以在遇到 GraphQL 时,除了其特有的漏洞外,还可以根据实际情况去针对性的测试其他漏洞。

安装步骤如下,执行完后访问 5013 端口即可。

git clone https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application.git && cd Damn-Vulnerable-GraphQL-Application
docker build -t dvga .
docker run -d -t -p 5013:5013 -e WEB_HOST=0.0.0.0 --name dvga dvga

4.1 Information Disclosure

点击“Private Pastes”可抓到 GraphQL 的请求。

4.1.1 Introspection

首先通过“内省查询”并使用可视化工具来查看 GraphQL 实体之间的关联。

4.1.2 GraphiQL

GraphiQL(GraphQL Interface)是一个集成开发环境的测试页面,可用于构造查询并实时查看结果,并且会提供有关 GraphQL API 的描述信息,这可能会导致信息泄漏。常见的路径包括:

/graphiql
/playground
/v1/graphql
/v2/graphql
/console

还会泄漏有关的调试信息。

4.1.3 Suggestions

可以从服务端返回的提示信息中,挖掘到有用的信息。可以收集相关的字典,在没有内省查询的情况下,可以通过枚举来挖掘目标的信息。

4.2 Denial of Service

GraphQL API 容易受到 Dos 攻击,这是因为 GraphQL 支持多种特性,比如:批量查询,这能够让 GraphQL 请求可以包含多个查询,如果没有限制则会导致 Dos 攻击。

4.2.1 Batch Query Attack

4.2.2 Deep Recursion Query Attack

从“内省查询”的可视化图例可以看到,对象 PastesObject 引用 OwnerObject,在对象 OwnerObject 中又引用了 PastesObject,这样无限到套娃的查询会导致 Dos 攻击。

4.2.3 Resource Intensive Query Attack

如果可以未授权进行系统更新,且系统更新会使得网站无法使用,那么会导致 Dos 攻击。

4.2.4 Field Duplication Attack

大量重复的字段。

4.2.5 Aliases based Attack

GraphQL 查询中不支持多个相同的属性,但可以通过别名的方式来实现,这同样会导致 Dos 攻击。

4.2.6 Directives Overloading

当查询请求中包含大量的指令时,会导致 Dos 攻击。

4.2.7 Pagination Limit Bypass

分页限制绕过是攻击者试图绕过服务器端 GraphQL 实现中分页控件设置限制的一种攻击类型。当攻击者操纵 GraphQL 查询中的分页参数以绕过预期的页限制并检索比服务器预期的更多的数据时,就会发生这种攻击。具体可参考:https://www.imperva.com/blog/graphql-vulnerabilities-common-attacks/

4.3 SSRF

在任意存在远程加载的地方,都可以尝试 SSRF 攻击。

4.4 Code Execution

4.5 Injection

4.5.1 XSS

当未对提交的数据进行清洗时,也会导致 XSS 攻击。

4.5.2 Log Spoofing&Log Injection

对于 GraphQL 的查询和变更操作,可以赋予一个操作名称,这是为了注明请求的大致内容以及方便调试。如下,给查询请求起一个GetUser的操作名称,这可以很方便的看出请求的内容是获取用户的信息。

query GetUser {
User {
id
name
pass
}
}

但由于操作名称是自定义的,攻击者可以通过修改操作名称来误导管理人员,从而避免被发现。比如如下是一个变更操作,但是操作名称却为GetUser,这样可以一定程度上的误导管理人员。

mutation GetUser {
updateUser(name:"admin", pass:"123456") {
id
name
pass
}
}

4.5.3 SQLi

所有存在与数据库交互的地方,都有 SQL 注入的风险,GraphQL API 也不例外。

4.6 Authorization Bypass

4.6.1 JWT Token Forge

首先通过createUser接口来新建一个用户。

然后通过login接口来返回 JWT。

将 JWT 中的 identity 改为 admin。

最后用伪造的 JWT,通过接口me拿到管理员的密码。

4.6.2 Interface Protection Bypass

在测试页面查询被拒绝。

可以修改Cookieenable来实现绕过。

4.6.3 Query Deny List Bypass

创建 GraphQL API 黑白名单是防止 API 被恶意查询的一种普遍方法。但使用黑名单会很容易被绕过。可以看到systemHealth是不允许被访问,存在于黑名单。

可通过添加操作名称来绕过黑名单的限制。

4.7 Miscellaneous

4.7.1 Query Weak Password Protection

由于 GraphQL 支持别名的特性,可以在一次请求中进行多次查询,所以如果网站对枚举有限制,可以尝试使用别名来进行爆破攻击。

4.7.2 Arbitrary File Write // Path Traversal

0x02 PortSwigger

通用思路都是通过“内省查询”来获取到类似于 API 说明文档的数据,根据这些数据来构造相关查询以获取目标敏感信息。靶场地址:https://portswigger.net/web-security/all-labs#graphql-api-vulnerabilities

1. Accessing private GraphQL posts

第一步,通过“内省查询”来获取目标 GraphQL 的信息。

第二步,将查询结果通过在线网站来进行可视化查看。网站地址:https://graphql-kit.com/graphql-voyager/

最后一步,构造请求来获取敏感信息。在“GraphQL”面板处可以很方便的进行修改。

2. Accidental exposure of private GraphQL fields

使用“内省查询”来获取目标 GraphQL 的信息,并通过在线网站来可视化实体之间的关联。

构造请求来获取敏感信息。

3. Bypassing GraphQL introspection defenses

为了防止攻击,开发者们往往会过滤__schema这样的字符,用以阻止“内省查询”。作为攻击人员可以尝试如下方式来进行绕过。

  • 添加些特殊字符,如:空格、换行、注释等
  • 将请求方法改为 POST
  • 将请求类型改为x-www-form-urlencoded

首先找到 GraphQL 的 Endpoint。

在进行探测时,提示不允许内省查询。

__schema{换行分隔后,可绕过检测。

将内省查询的结果并保存到 json 格式的文件中,随后导入 Inql 插件,该插件可以根据内省查询的结果构造出相应的操作。Inql 插件下载地址:https://github.com/doyensec/inql

通过内省查询的结果可以知道DeleteOrganizationUserInput类型一个 Int 类型的 id 参数。

4. Bypassing GraphQL brute force protections

GraphQL 查询中不支持多个相同的属性,如下查询是一个错误的例子

query getProductDetails {
getProduct(id: 1) {
id
name
}
getProduct(id: 2) {
id
name
}
}

如果想要实现上述的需求,可以通过 Aliases(别名)来实现。

query getProductDetails {
product1: getProduct(id: 1) {
id
name
}
product2: getProduct(id: 2) {
id
name
}
}

返回的结果大致为如下形式:

{
"data": {
"product1": {
"id": 1,
"name": "Juice Extractor"
},
"product2": {
"id": 2,
"name": "Fruit Overlays"
}
}
}

许多网站都有多种防爆破手段,一些限制手段是限制 HTTP 的请求数量,而不是单次请求中的操作数量。在 GraphQL 中的“别名”能够在单次请求中发送多次查询请求,这能有效的绕过方爆破手段。

在进行密码爆破时,提示错误次数过多,封禁一分钟。

根据“别名”来进行爆破。

5. Performing CSRF exploits over GraphQL

对于请求类型是application/jsonPOST请求,是无法实现 CSRF 攻击的,由于application/json类型的请求无法通过网页表单的形式发送。即便通过 JavaScript 形式发送 JSON 格式的请求,也会受到同源策略的限制。但如果 GraphQL 服务端不验证请求类型,且没有对 CSRF 做相关的防护,那么也会导致 CSRF 漏洞。

登录后,拿到修改用户邮箱的数据包。

由于是application/json类型的数据包,无法直接进行 CSRF 攻击。但 GraphQL 端点同样支持x-www-form-urlencoded的格式。

0x03 Root-Me

靶场地址:https://www.root-me.org/zh/%E6%8C%91%E6%88%98/%E7%BD%91%E7%BB%9C-%E6%9C%8D%E5%8A%A1%E5%99%A8/?titre_co=graphql

1. Introspection

通过内省查询获取目标信息,并通过 Graphql-vayager 可视化。

2. Injection

该关卡限制了直接与 GraphQL 端点的交互。

通过猜想可能是将提交的数据q=China拼接到正常的查询请求中,然后在由服务端进行查询。

通过构造 Payload 成功验证先前的猜想。

查询 Schema 所有的类型以及字段。

3. Mutation

直接查询 flag 提示没有权限。

在执行完 Mutation 操作后,会返回执行的结果,也就是说 Mutation 也能实现查询的功能,只不过查询的内容是 Mutation 操作后的内容。如下所示,可以看到执行完 Mutation 操作后,将 Mutation 的结果进行了返回。

通过内省查询的结果可以知道,flag 字段在 Nude 对象中且 Nude 对象可以通过 nudeId 来进行控制。虽然没有权限查询 Nude 对象的内容,但根据 Mutation 操作会返回其结果这一特性,可以泄露 flag 的内容。

0x04 Reference

https://graphql.org/

https://spec.graphql.org/

https://www.freebuf.com/articles/377142.html

https://xz.aliyun.com/t/14913

https://www.cnblogs.com/zhibing/p/17350053.html

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL%20Injection

https://medium.com/search?q=Hacking+GraphQL+for+Fun+and+Profit

https://www.imperva.com/blog/graphql-vulnerabilities-common-attacks/

https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html

https://github.com/nicholasaleks/graphql-threat-matrix

# GraphQL安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录