Exactly what a penetration tester need to know about GraphQL API, nothing more or less!
What is GraphQL?
GraphQL is a query language created by Facebook for APIs and a runtime for fulfilling those queries
with your existing data.
GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.
What are main differences between REST API and GraphQL?
→ REST APIs communicate via HTTP requests to perform standard database functions like creating, reading, updating, and deleting records (CRUD) within a resource.
→ GraphQL is a query language and a set of tools that operate over a [single] endpoint.
→ For REST call you need to know the URL of each resource, HTTP methods used on each resource and the schema of each resource.
→ For GraphQL call u need to know only the URL of the API, and the schema
→ EX: In REST call, if you want to register a user u send a request to [/api/v2/register] and if u want to sign-in u send request to [/api/v2/login] and if u want to reset your password
you send another request to [/api/v2/reset-password].
Every resource has its own schema and own method. But, In GraphQL there is only one endpoint that u can send all requests to it!
what are the components of the GraphQL?
→ GraphQL server-side: where it resolves the operations and provides the requested data/operation.
→ GraphQL client-side: any application wants to interact with GraphQL API to do some operations.
what are the main operations of the GraphQL?
there are 3 main types:
→ Query: for fetching data using specifically defined query operations.
→ Mutations: for modifying any data (create/update/delete) in database using operations.
→ Subscriptions: for receiving real-time messages from the back-end.
Example of GraphQL structure:
I used [ https://lucasconstantino.github.io/graphiql-online/ ] to practice fastely on GraphQl , u can use the SWAPI GraphQL on GitHub to practice more deeply. “see the references and resources section“
request section:
-----------------
query JustForTest($test: ID!, $inclang: Boolean!){
country(code: $test) {
name
capital
phone
languages@include(if: $inclang){
name
native
}
currency
}
}
=================================================================
variable section:
-----------------
{
"test": "EG",
"inclang": false
}
→ query: the operation type [query, mutation, subscription]
→ JustForTest: operation name: it’s just a name to differentiate your operation, suppose u have 100 different operation, how u can differentiate each one from another?
→ ($test: ID!, $inclang: Boolean!): your arguments!
$test: ID!: we declared a variable named $test of the type ID!
$inclang: Boolean!: we declared a variable named $inclang of the type Boolean!
→ For now we have a query operation named JustForTest that takes 2 parameters called test and inclang
→ Now what data we want to query? we put the data we want ‘fields’ in {} and if we need a specific peace of data from data retrieved ‘sub-fields’ we put it in {}
→ country: the field we want to get.
→ (code: $test): a parameter named code tells the graphQL API to get only the country that have the code $test ‘that we identified as a variable in the root operation’
→ name, capital, and phone: all sub-fields we need to get from the country field
→ languages@include(if: $inclang): like the programming if statement, include and get the languages field if the $inclang variable is true
→ {“test”:”EG” and “inclang”:”false”}: the variables we declared, it’s the time to assign it!
The response of the above request was as following:
{
"data": {
"country": {
"name": "Egypt",
"capital": "Cairo",
"phone": "20",
"currency": "EGP"
}
}
}
what if we changed the country to Argentina “AR” , and put the inclang variable to true?
{
"data": {
"country": {
"name": "Argentina",
"capital": "Buenos Aires",
"phone": "54",
"languages": [
{
"name": "Spanish",
"native": "Español"
},
{
"name": "Guarani",
"native": "Avañe'ẽ"
}
],
"currency": "ARS"
}
}
}
Aliases …
→ as we see in the previous example of Argentina, the API retrieved exactly what we asked for and also the fields name, but what if the developer want to change the retrieved names?
Aliases gives the developers the ability to change the data retrieved names
EX: when we query country field we can try replace it with “TheNameWeWant: country” as following request and response
request section:
-----------
query JustForTest($test: ID!, $inclang: Boolean!){
CountryField: country(code: $test) {
CountryName: name
CountryCapital: capital
CountryPhoneCode: phone
CountryLang: languages@include(if: $inclang){
name
native
}
currency
}
}
=================================================================
variable section:
-----------------
{
"test": "EG",
"inclang": false
}
=================================================================
response section:
---------------
{
"data": {
"CountryField": {
"CountryName": "Egypt",
"CountryCapital": "Cairo",
"CountryPhoneCode": "20",
"currency": "EGP"
}
}
}
Hacking GraphQL API
default endpoints:
→ by default, the endpoint of the GraphQL API will be one of the following:
[“/graphql”, “/graphiql”, “/graphql/console”, “/graphql.php”, “/graphiql.php”, “/explorer”, “/altair”, “/playground”, “/graphql-explorer”, “/graphiql/”]
Introspection:
→ It’s often useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system!
→ Introspection system is enabled by default, but developers can turn it off.
→ through introspection, u can get all details about the GraphQL schema, including fields, types, sub-fields and more.
To get fields and sub-fields names:
{
__schema{
types{
name,
fields{
name
}
}
}
}
With this query you can extract all the types, it’s fields, and it’s arguments (and the type of the args). This will be very useful to know how to query the database.
{
__schema{
types{
name,
fields{
name,
args{
name,
description,
type{
name,
kind,
ofType{
name,
kind
}
}
}
}
}
}
}
Get everything through introspection query without fragmentation:
{__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}}
Get everything through introspection and fragmentation:
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { 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
}
}
}
}
}
}
}
}
What if the introspection is off?
-> we make errors to get information about the schema such as the following:
the query: query={bankname}
the error: “Cannot query field \”bankname\” on type \”Query\”. Did you mean \”banks\”?”
now we got the correct field name via error “banks”.
-> we abusing the already defined fields and sub-fields that we get from requests and abusing mutations.
Attacks?
There are many vulnerabilities can raise from GraphQL mis-configuration such as the following:
-> BAC and IDOR.
-> Information Disclosure.
-> SQL Injection.
-> CUD “creating, updating, and deleting” data via abusing mutations.
-> Authorization bypass.
-> Batching Attacks.
Real Scenarios and vulnerabilities that I found while penetration testing a web app of one of the biggest FinTech organizations.
[Information Disclosure]
→ lets call it migo.com
→ after information gathering and nuclei tests, I found that they depends on GraphQL as their API and the GitHub repo that I found made me sure of it!
→ BTW, I found TS scripts, default credentials and some configs on the GitHub repo, and that repo wasn’t tied directly to the migo organization.
→ I found the API endpoint as [ https://subdomain.migo.com/graphql ] after visiting it
I found an error “Must provide query string.”
→ I provided the query string [ https://subdomain.migo.com/graphql?query={bank} ] and I got an error
“”Cannot query field \”bank\” on type \”Query\”. Did you mean \”banks\”?””
→ I changed the query field to banks and I got an error
“”Field \”banks\” of type \”[BankType]\” must have a sub selection.””
→ After some error enum, the final query was
[ https://subdomain.migo.com/graphql?query={bank{id,bankName}} ]
and I got all banks and more details.
[BAC by updating account usernames]
→ I found a sub that has a small communication application that manage developers within migo organization to communicate with each other.
→ I’ve managed to create an account on their app
→ I tried to change my email in the profile section settings and while Intercepting the request I found that the mutation request sent to the GraphQL API and after that, the Query request sent to get all the account details with their variable names including sensitive information.
→ I found that there is no function available to users to change their username and usernames are assigned automatically.
→ I tried to change my email again and while intercepting the request i changed the email variable to username variable, and I’ve managed to change the username to anything I want.
→ everyone sign-in with his email, but i cannot get anyone email! I tried to sign-in with the username and I’ve managed to do this so, I didn’t need anyone email because I’ve managed to change their username and login with their usernames! ^_^
[Partial account access]
→ when mutation operation sent to the GraphQL API, it included the id of the current user, I tried to change the id to another account but it gave me 401 unauthorized!
→ yup I was disappointed, but i didn’t give up!
→ From account details and information I got after every successful mutation request, I found a variable let’s call it “anotherId” and doesn’t has the same value of the id variable.
→ I sent the mutation request again to change my username
but I replaced the id with the anotherId of another account and BINGO!
→ I’ve managed to change other accounts username,
I’ve managed to force anyone follow anything or un-follow and other things ^_*
[References and resources]
→ I used [ https://lucasconstantino.github.io/graphiql-online/ ] to practice on GraphQL
→ Want to practice on another example? [ https://studio.apollographql.com/sandbox/explorer ]
→ want to get more in-depth practice with the server-side GraphQL and Configurations?
[ https://github.com/graphql/swapi-graphql ]
→ No thing better than the documentation! [ https://graphql.org/learn/ ]
→ Batching Attack [ https://lab.wallarm.com/graphql-batching-attack/ ]
→ OWASP Cheat Sheet
[ https://cheatsheetseries.owasp.org/cheatsheets/GraphQL_Cheat_Sheet.html ]
→ BugCrowd video [ https://youtu.be/NPDp7GHmMa0 ]
→ HackTricks GraphQL
[ https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/graphql ]
→ GraphQL 101 part2
[ https://cloudsek.com/graphql-101-heres-everything-you-need-to-know-about-graphql-part-2/ ]
Finally …
Thanks for reading, I hope you enjoyed my first and small research and learned something new!
Have a question?
Twitter: @exzandar
Leave a comment