Quick Context: Okay, so recently, we come across some fancy NFT project wherein “Students” are invited to join “Quizzes” and “Projects” to “Graduate”.
A “Graduate” means whitelisted for the mint of the NFT collection.
Our Goal
Our goal is to get into the top leaderboard so we can ensure our whitelist slot. And we want this by all means, so we use our hacker instinct to get advantage on the quiz.
However, we wouldn’t wanna overkill the contest. We didn’t spawn bots to automatically answer the quizzes (which is easy to do), so we just sticked with our bare hands, manually answering the quizzes. And we just stick to one-to-one account to human. We don’t want to disrupt the experience of other people.
The quiz
The quiz is a client sided web app. Meaning, all of the password for the quiz and questions are given to client without levels of authorization. Below are the steps of our reconnaissance and enumeration to extract the password and the set of question for a quiz.
Cracking the Password
Every quiz has different password. And our goal is to crack the password before the quiz starts (hours before the quiz so we have the chance to crack it).
Upon logging-in and browsing to /quiz page, we could see a web api requests. We can see that a request has a response that includes juicy information. We saw a json response that includes quiz details and we write down the _id and the password to our notes.
$2a$10$msFPZnG.NKHaCcVupGsQyuvpB8IwtZ7v3UxPBwf3fXe8hGdCMEwsu
The password is a bcrypt hash.
The first thing we did was to list all possible passwords and try to compare them against the hash.
But sadly, we didn’t got any “possible password” correct.
What is Bcrypt?
The input to the bcrypt function is the password string (up to 72 bytes), a numeric cost, and a 16-byte (128-bit) salt value. The salt is typically a random value. The bcrypt function uses these inputs to compute a 24-byte (192-bit) hash. The final output of the bcrypt function is a string of the form:
$2<a/b/x/y>$[cost]$[22 character salt][31 character hash]
For example, with input password abc123xyz
, cost 12
, and a random salt, the output of bcrypt is the string
$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
\__/\/ \____________________/\_____________________________/
Alg Cost Salt Hash
Where:
$2a$
: The hash algorithm identifier (bcrypt)12
: Input cost (212 i.e. 4096 rounds)R9h/cIPz0gi.URNNX3kh2O
: A base-64 encoding of the input saltPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
: A base-64 encoding of the first 23 bytes of the computed 24 byte hash
The base-64 encoding in bcrypt uses the table ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
,[9] which is different than RFC 4648 Base64 encoding.
Back to our discussion
So now we know the basics of bcrypt, we could now start attacking the password hash.
Well, luckily, we got a tool named hashcat.
Without having any more ideas about the password, we can now use the bruteforce technique.
We also know that the password only contains numbers.
So we could go bruteforce increment from ZERO until 10^n. Where n is the number of digits.
hashcat.exe -a 3 -m 3200 --increment --increment-min 1 --increment-max 8 $2a$10$msFPZnG.NKHaCcVupGsQyuvpB8IwtZ7v3UxPBwf3fXe8hGdCMEwsu ?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d
Here, we tell hashcat that our attack mode is Brute-force (-a 3), increment each password iteration (–increment), start from 1 digit (–increment-min 1), end the iteration with maximum of 8 digit (–increment-max 8), password hash that we found earlier ($2a$10$msFPZnG.NKHaCcVupGsQyuvpB8IwtZ7v3UxPBwf3fXe8hGdCMEwsu) and the pattern that we want our hashcat to follow (?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d?d).
To know more about hashcat, check this out: https://hashcat.net/wiki/doku.php?id=hashcat
And after some couple of minutes, we cracked the hash!
It took only 8 minutes for my GTX1050 to crack a 5-digit password. But it would definitely lasts more longer if the password was longer than 5-digit.
Luckily, the password for this quiz is shorter than the first set of quizzes so we are able to bruteforce this in a very small amount of time.
Extracting Questions
We found a page where we can browse the quiz. We just enter the password that we found for this quiz.
The web app then make a request to the web api and we could see a juicy information here that includes the quiz questionnaires (testData).
We just parse the testData. And boom! Successfully extracted the PASSWORD and the QUESTIONS.
Conclusion
I understand the intention of the developer that they don’t want the participants kinda “DDoS” their servers by having a lot of authentication and authorization though their servers. They just give all their password and quiz data to the client because they want the validation to be on client’s side and not having loads to their server.
The web app’s architecture, does not really abide the Zero Trust Security because they just make the client’s authorized themselves and “trusts” them without proper validation.
Thanks for reading this short writeup!
I hope you enjoy and see you on my next writeup!