Hi everyone, this is one of my pentest stories about a client had rolled out a platform which allowed users to apply for a credit card. This pentest was done a week before the launch of the product. So, we were a bit short on time.

About the product

The application allowed users to apply for credit cards. If the user was already a customer of the company, their information would auto-populate. If not, they would be prompted to fill in all the information. Once, all the information was filled by the user, their information would be saved and forwarded to an internal system for review.

The APIs were built using API Gateway which were communicating with an internal GraphQL client. If the user was already an existing customer, their information was being fetched from DynamoDB.

None
Over simplified architecture diagram

GraphQL Testing

When I started testing, I saw that they were using GraphQL. How did I spot this? There are a lot of ways to find out. The easiest would be to look for the presence of /graphql endpoint. This won't always be the case, but I've always seen this endpoint for all the GraphQL pentests that I've performed.

So, I started with GraphQL test cases. I was able to spot a few issues which would could lead to denial of service of the server.

Overview of GraphQL

GraphQL uses operations to define the action being performed. These operations are primarily classified into queries and mutations. A query is used to retrieve data and can be compared to a GET request in REST, as it only fetches information from the database. A mutation, on the other hand, is used to modify data. It supports actions such as creating, updating, and deleting records, making it similar to a POST request in REST.

You can learn about queries and mutations here and here respectively.

Actual bug

While performing these GraphQL test cases, I saw an X-API-Key header with a value. I checked my Burp History and saw that the X-API-Key was same across all the requests.

An important point to note here is that you don't need to be authenticated to submit the credit card information. So, that's weird. How's the web server sending this X-API-Key value to the user? The only possible things I can think of are — response headers or the key being hardcoded in JavaScript files.

I started the credit card application flow and began checking all the response headers one by one. I couldn't find any of the responses setting the X-API-Key . I checked the browser storage for the key, but there too, I couldn't find it.

So, this only leaves the JavaScript files, and indeed, it was hard-coded in one of the the JavaScript files.

PII Disclosure

But it's only the key that's hardcoded right? What can I use it for? Here's where we can use GraphQL introspection. GraphQL introspection, if enabled, allows users to fetch the GraphQL schema. The schema contains all the queries and mutations supported by the endpoint.

One can use the following introspection query to list all the operations supported by the GraphQL endopint.

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

There were only a handful of query and mutation operations supported. Glancing over those, I saw one called exportBulkData. One good thing about the introspection query is that it tells you the input arguments that are expected by the operation and also the data fields that can be returned by invoking the GraphQL operation. As input, the operation only expected a phone number. That's it!

So, without delay, I created the exportBulkData operation on BurpSuite and passed my phone number as an argument. It returned my user details. I used the same operation on all the other test phone numbers that I had to confirm it. The operation indeed just returned the user details without any authorization checks.

Going through the GraphQL schema, I spotted another queryoperation which allowed users to fetch all the phone numbers saved in the DynamoDB database.

The client ended up removing these a couple of operations just before the launch, as they had no time to remediate these.

Hope you enjoyed reading the article. Please consider subscribing and clapping for the article.

In case you are interested in CTF/THM/HTB writeups consider visiting my YouTube channel.