3 minutes
Hack.lu 2020: Confessions
This is a writeup for “Confessions”, the first web challenge I solved. I was luckily able to finish this challenge in a couple hours, so I could focus my attention to the other super interesting web problems. Confessions was a nice dive into some simple GraphQL manipulation and baby crypto.
Let’s Begin!
The confessions webpage was a message-generation application that would hash (in sha-256) your message based on the title and content of it. Under the hood we see the nature of how these messages are stored:
// talk to the GraphQL endpoint
const gql = async (query, variables={}) => {
let response = await fetch('/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
operationName: null,
query,
variables,
}),
});
let json = await response.json();
if (json.errors && json.errors.length) {
throw json.errors;
} else {
return json.data;
}
};
// some queries/mutations
const getConfession = async hash => gql('query Q($hash: String) { confession(hash: $hash) { title, hash } }', { hash }).then(d => d.confession);
const getConfessionWithMessage = async id => gql('mutation Q($id: String) { confessionWithMessage(id: $id) { title, hash, message } }', { id }).then(d => d.confessionWithMessage);
const addConfession = async (title, message) => gql('mutation M($title: String, $message: String) { addConfession(title: $title, message: $message) { id } }', { title, message }).then(d => d.addConfession);
const previewHash = async (title, message) => gql('mutation M($title: String, $message: String) { addConfession(title: $title, message: $message) { hash } }', { title, message }).then(d => d.addConfession);
The application uses graphQL as an intermediary to the database. Specifically, the client “talks” to graphql via the \graphql
endpoint. If we take a look at what happens when we create a message:
The construction of this “mutation” object is common in graphQL implementations. However, to actually “talk” to the endpoint, you don’t always need to generate a mutation object.
What else can we ask of the endpoint? If we perform some regular introspection into their graphQL infrastructure, we see something interesting (I’m using the graphQL IDE here to avoid having to worry about formatting in an API request):
The description on the query’s field accessLog
is the big hint here. Let’s expand upon accessLog
further:
{
accessLog{
name
timestamp
args
}
}
We recieve a list of hashes, also in sha-256. Running the first 4 hashes through an online sha-256 database reveal to me that they decrypt to “f”, “fl”, “fla”, then finally “flag”. The full flag of this challenge is obviously the very last hash in this accessLog
, so now we must bruteforce the incoming letters (which isn’t too crazy. If we operate under the assumption that the flag has letters that are all lowercase, numbers 0-9, and maybe the -
or _
chars, we are dealing with 38 bits of entropy per letter, which is manageable. Additionally, we are aware by convention that all flags start with flag{
and the last char is }
, so we can already figure out what the 5th hash decrypts to). Special thanks to teammate Filip for explaining to me how to do it :-)
from hashlib import sha256
from pwn import *
alphanumerics = "abcdefghijklmnopqrstuvwxyz1234567890{}_-"
hashes = ["""all the hashes found in accessLog. There were a lot."""]
flag = b''
for hash in hashes:
for c in alphanumerics:
m = sha256()
m.update(flag + c.encode("utf-8"))
testhash = enhex(m.digest())
if (testhash == hash):
flag += c.encode("utf-8")
print (flag)
continue
And that’s that! A quick and straightforward challenge. It was a good warmup into the other excellent web problems I saw :)