前言
某次项目中遇到graphql,感觉之前没有系统的总结学习过,所以补充记录一下
[!NOTE]
个人感觉就是一个ORM框架
GraphQL
是 Facebook 开发的一种 API 的查询语言,与 2015 年公开发布,是 REST API 的替代品。GraphQL
既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL
对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
官网:graphql.org
中文网:graphql.cn
环境搭建
遇到的情况相对较少,所以建议自己搭建一个测试环境,一般尽量使用docker来节省我们的时间,我使用的是graphql-engine
创建一个docker-compose.yaml
文件,写入如下内容(需要依赖数据库postgreql,所以建议用docker-compose来)
version: '3.6'
services:
postgres:
image: postgres:9.6
restart: always
environment:
- "POSTGRES_PASSWORD=password" #"password"为要设置的数据库密码
ports:
- "5432:5432" #前:后 前为宿主机端口号,后为容器端口号 端口映射
volumes:
- ./db_data:/var/lib/postgresql/data #数据库目录映射 前为宿主机,后为容器
graphql-engine:
image: hasura/graphql-engine:latest
ports:
- "9080:8080"
depends_on:
- "postgres"
restart: always
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:password@host.docker.internal:5432/test #连接数据库,其他的不用管,注意里面数据库密码要符合上文
HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console
HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
## uncomment next line to set an admin secret
HASURA_GRAPHQL_ADMIN_SECRET: password #"password"为hasura登录密码
执行如下命令进行下载启动镜像
docker-compose up -d
这个时候访问9080
是打不开的,因为环境还有点问题,查看容器日志,可以发现是数据库的问题,没有test
这个数据库,我们连接进去创建一个即可
docker logs a2a
可以用图形化界面navicat
,也可以连接进postgresql容器内部创建数据库
create database test;
重启容器graphql-engine
docker restart a2abf9bc3297
然后访问http://localhost:9080/
即可
登陆后
bingo!!!
基础使用
简单的介绍下使用即可,就没记录基础的内容了,比如类型有哪些等等。。。
添加数据
默认是没有内容的,可以自己添加一个示例,往下滑可以看到添加内容的SQL语句
-- To uninstall demonstration:
-- Delete the `_helloworld` schema from the expanded page on the left
-- or you may run: `DROP SCHEMA IF EXISTS _helloworld` from the SQL tab to the left
--
-- Please be careful before running, as all data in the schema will be removed
-- Create Schema
CREATE SCHEMA _helloworld;
-- Create Tables
CREATE TABLE _helloworld.author (
id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE _helloworld.article (
id serial PRIMARY KEY,
title text NOT NULL,
rating integer DEFAULT NULL,
author_id integer REFERENCES _helloworld.author(id) NOT NULL
);
-- Insert Seed Data
INSERT INTO "_helloworld"."author" ("name") VALUES
('Coleman Spickett'),
('Gallard Dreye'),
('Alysa Beecker'),
('Kellie Owbridge'),
('Mischa Sabban'),
('Jacquenetta Devo'),
('Martie MacKintosh'),
('Babb Attree'),
('Mitchel Andrews'),
('Jodie Deschelle');
INSERT INTO "_helloworld"."article" ("author_id", "rating", "title") VALUES
(3, 3, 'Oh, hi , Marty. I didn''t hear you come in. Fascinating device, this video unit.'),
(1, 1, 'Erased from existence.'),
(6, 2, 'No no no, Doc, I just got here, okay, Jennifer''s here, we''re gonna take the new truck for a spin.'),
(2, 4, 'Do you mind if we park for a while?'),
(9, 1, 'Why am I always the last one to know about these things.'),
(4, 1, 'Hey, George, buddy, you weren''t at school, what have you been doing all day?'),
(9, 2, 'Yeah, but you''re uh, you''re so, you''re so thin.'),
(10, 5, 'Right, okay, so right around 9:00 she''s gonna get very angry with me.'),
(10, 5, 'Well looky what we have here. No no no, you''re staying right here with me.'),
(7, 1, 'Of course I do. Just a second, let''s see if I could find it.'),
(9, 1, 'Right, okay, so right around 9:00 she''s gonna get very angry with me.'),
(6, 3, 'Marty, you''re acting like you haven''t seen me in a week.'),
(5, 3, 'Can I go now, Mr. Strickland?'),
(9, 5, 'Yes, yes, I''m George, George McFly, and I''m your density. I mean, I''m your destiny.'),
(4, 4, 'We all make mistakes in life, children.'),
(9, 3, 'Hey, George, buddy, you weren''t at school, what have you been doing all day?'),
(3, 4, 'Uh, you mean nobody''s asked you?'),
(8, 3, 'Just turn around, McFly, and walk away. Are you deaf, McFly? Close the door and beat it.'),
(5, 1, 'His head''s gone, it''s like it''s been erased.'),
(2, 5, 'Wait a minute, what are you doing, Doc?'),
(2, 4, 'Marty, you interacted with anybody else today, besides me?'),
(4, 2, 'I''m telling the truth, Doc, you gotta believe me.'),
(4, 2, 'Yeah, but you''re uh, you''re so, you''re so thin.'),
(10, 4, 'Alright, McFly, you''re asking for it, and now you''re gonna get it.'),
(9, 3, 'And Jack Benny is secretary of the Treasury.'),
(10, 3, 'Why am I always the last one to know about these things.'),
(3, 5, 'Crazy drunk drivers.'),
(3, 1, 'Why am I always the last one to know about these things.'),
(9, 3, 'What''s that thing he''s on?'),
(5, 3, 'Um, yeah well I might have sort of ran into my parents.'),
(4, 3, 'Sam, here''s the young man you hit with your car out there. He''s alright, thank god.'),
(10, 5, 'A block passed Maple, that''s John F. Kennedy Drive.'),
(4, 1, 'Erased from existence.'),
(1, 5, 'What''s that thing he''s on?'),
(6, 3, 'Then how am I supposed to ever meet anybody.'),
(4, 2, 'You know, Doc, you left your equipment on all week.'),
(3, 5, 'Right, okay, so right around 9:00 she''s gonna get very angry with me.'),
(5, 3, 'Mr. McFly, Mr. McFly, this just arrived, oh hi Marty. I think it''s your new book.'),
(4, 1, 'Alright, McFly, you''re asking for it, and now you''re gonna get it.'),
(1, 5, 'Oh, hi , Marty. I didn''t hear you come in. Fascinating device, this video unit.'),
(2, 2, 'Can I go now, Mr. Strickland?'),
(10, 3, 'Hey, George, buddy, you weren''t at school, what have you been doing all day?'),
(3, 1, 'Wait a minute, what are you doing, Doc?'),
(9, 1, 'I don''t know, but I''m gonna find out.'),
(6, 4, 'Well, uh, listen, uh, I really-'),
(5, 3, 'Hey kid, what you do, jump ship?'),
(1, 3, 'Yeah, but you''re uh, you''re so, you''re so thin.'),
(9, 1, 'Wait a minute, wait a minute. 1:15 in the morning?'),
(5, 4, 'Hey kid, what you do, jump ship?');
query查询数据
先举个例子,这里不需要自己构造语句,点点点填空就行
实例:查询id>5
的结果的name和id,并以降序desc
排序
query MyQuery {
_helloworld_author(order_by: {id: desc}, where: {id: {_gt: 5}}) {
name
id
}
}
转换成SQL语句就是,可见用graphql语法要简短很多很多
SELECT
coalesce(
json_agg(
"root"
ORDER BY
"root.pg.id" DESC NULLS FIRST
),
'[]'
) AS "root"
FROM
(
SELECT
row_to_json(
(
SELECT
"_1_e"
FROM
(
SELECT
"_0_root.base"."name" AS "name",
"_0_root.base"."id" AS "id"
) AS "_1_e"
)
) AS "root",
"_0_root.base"."id" AS "root.pg.id"
FROM
(
SELECT
*
FROM
"_helloworld"."author"
WHERE
(("_helloworld"."author"."id") > (('5') :: integer))
) AS "_0_root.base"
ORDER BY
"root.pg.id" DESC NULLS FIRST
) AS "_2_root"
我们分析一下上面的graphql语句,就很清楚用法了
query MyQuery {
_helloworld_author(order_by: {id: desc}, where: {id: {_gt: 5}}) {
name
id
}
}
_helloworld_author
函数名,可以随便取(这里是graphql-engine
根据数据库数据自动生成的)- 函数括号中,代表传入的参数
- 最后的
name
和id
,就是要查询的结果
也可以用传入变量的方式查询内容
mutation修改数据
[!NOTE]
最下面可以直接选择
Mutation
示例:添加作者test,id为1099
mutation MyMutation {
insert__helloworld_author_one(object: {id: 1099, name: "test"}) {
id
name
}
}
语句也很简洁明了,分析一下也和查询类似,数据库中成功添加进数据
漏洞利用
敏感信息泄漏与越权
GraphQL是一门自带文档的技术。我们有时候会需要去问 GraphQL Schema 它支持哪些查询,GraphQL 通过利用内省系统,可列出 GraphQL中所有Query
、Mutation
、ObjectType
、Field
、Arguments
。
查询存在的类型:
{
__schema {
types {
name
}
}
}
测试过程中,我们可以根据获取到的接口和参数,去构造query查询,以便寻找敏感信息,如email
、password
、secret
等,可以多关注废弃字段deprecated fields
,也可以构造mutation语句去越权修改数据等
一些工具可以生成文档来辅助我们测试,工具列表:
1、https://github.com/2fd/graphdoc
[!TIP|style:flat]
建议用下面的可以自动补全和发起请求的其他工具
2、chrome插件:https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij
如果graphql调用的接口需要鉴权,可以使用burp在中间进行处理
3、https://github.com/apollographql/apollo-client-devtools
也可以自动补全,部署环境比上面的chrome插件稍微复杂点
4、https://github.com/skevy/graphiql-app
和前面功能一样,但是是单独的APP,所以感觉最好用吧
5、InQL Scanner
burp插件
https://github.com/doyensec/inql
Express-GraphQL Endpoint CSRF漏洞
大概就是:默认graphql查询时,都是依赖于json数据格式进行传输给后端的,但是使用Express-GraphQL
的时候,给json转换成form表单格式的数据也可以提交给后端正常处理,也就可以直接用burp生成的CSRF POC进行CSRF攻击了
原数据包:
POST /? HTTP/1.1
Host: graphqlapp.herokuapp.com
Origin: https://graphqlapp.herokuapp.com
User-Agent: Graphiql/http
Referer: https://graphqlapp.herokuapp.com/
Cookie: [mask]
Content-Type: application/json
Content-Length: 108
{"query":"mutation {\n editProfile(name:\"hacker\", age: 5) {\n name\n
age\n }\n}","variables":null}
修改后也能使用的数据包:
POST /? HTTP/1.1
Host: graphqlapp.herokuapp.com
Origin: https://graphqlapp.herokuapp.com
User-Agent: Graphiql/http
Referer: https://graphqlapp.herokuapp.com/
Cookie: [mask]
Content-Type: application/x-www-form-urlencoded
Content-Length: 138
query=mutation%20%7B%0A%20%20editProfile(name%3A%22hacker%22%2C%20age%3A%20
5)%20%7B%0A%20%20%20%20name%0A%20%20%20%20age%0A%20%20%7D%0A%7D
GraphQL注入漏洞
和SQL注入类似,都是通过修改执行的语句,改变执行语句的语义,达到攻击者想要的结果,比如修改数据、查询更多内容等
正常修改mutation语句
mutation MyMutation {
update__helloworld_author(where: {id: {_eq: 1099}}, _set: {name: "testtest"}) {
returning {
id
name
}
}
}
恶意注入后
[!TIP]
先闭合前面的语句,再加函数或者其他内容即可,注入的恶意内容为
``` } insert__helloworld_author(objects: {id: 1088, name: “injection”}){ returning { name }
mutation MyMutation { updatehelloworld_author(where: {id: {_eq: 1099}}, _set: {name: “aaa”}) { returning { id name } } inserthelloworld_author(objects: {id: 1088, name: “injection”}){ returning { name } } }
![image-20220207154005294](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990874330-9fb81bf2-2ffe-4913-b295-e83d145e2b0d.png)
---
> [!NOTE]
> 修复可以使用graphql参数化查询
> ![image-20220207154642820](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990875955-81555c5a-790e-4713-b92f-79d10d1150b5.png)
### debug模式下的信息泄漏
一般来说生产环境都需要禁用开发模式的,在`Graphene-Django`下开启debug模式,将会造成SQL语句信息泄漏<br />参考:[https://docs.graphene-python.org/projects/django/en/latest/debug/](https://docs.graphene-python.org/projects/django/en/latest/debug/)<br />![image-20220207155254779](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990877487-0e7e789f-37b7-4098-a359-e3056b1e6f67.png)
{
A example that will use the ORM for interact with the DB
allIngredients { edges { node { id, name } } }
Here is the debug field that will output the SQL queries
_debug { sql { rawSql params sql } } }
```
嵌套拒绝服务
根据服务器资源情况来看,如果有大量占用内存的情况,都可能导致服务器宕机。在Graphql查询的时候,如果有大量的嵌套,那么也有可能会造成拒绝服务漏洞。
如下:可以使用作者信息查询到文章信息,使用文章信息也可以查询到作者信息,一直无限制嵌套下去,就可能导致服务器宕机。