Signal9 // defensive linux · graded on machine state

July Season: Ghost in the Machine

Three compromised boxes, three flags buried in the aftermath. Hunt the persistence, read the logs, and recover what the intruder left behind — all on sandboxes you fully control. Defensive practice only.

Forensics / IR · 3 challenges · Archived season — write-ups below.

Play the CTF →

The Quiet Cron

incident_response · Medium · 20-30 min

This web server makes a brief outbound connection every few minutes, then goes quiet — and nobody admits to scheduling anything. Find the persistence the intruder planted, follow it to the note it drops, and recover the flag. You have full root on this isolated, offline box.

Write-up

Goal

Find the attacker's cron-based persistence and recover the note it drops. The flag only exists on disk because the planted job wrote it — you have to actually follow the persistence chain.

1. Enumerate all scheduled tasks, not just your own

A user's `crontab -l` is only part of the picture. System-wide schedules live elsewhere — enumerate broadly:

ls -la /etc/cron.d /etc/cron.* /etc/crontab 2>/dev/null
for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u "$u" 2>/dev/null; done

One drop-in stands out: /etc/cron.d/0systemd-update — a system-looking name that sorts first and is easy to overlook.

2. Inspect the job

cat /etc/cron.d/0systemd-update
# */3 * * * * root /usr/local/bin/.sync

It runs a hidden (leading-dot) binary every three minutes as root. The dot is the tell — legitimate system tooling rarely hides itself.

3. Follow the payload to the note

cat /usr/local/bin/.sync
# writes a note under /root/.cache and logs via logger
cat /root/.cache/.beacon_note

The note contains the flag. Submit it.

Takeaway

Persistence hunting is about knowing every place a schedule can hide: /etc/cron.d, /etc/cron.{hourly,daily,...}, /etc/crontab, per-user crontabs, and systemd timers. A hidden binary run as root on a short interval is a classic beacon pattern — and the defensive fix is to neutralize both the schedule and the payload.

Sudo Misery Loves Company

persistence · Hard · 25-35 min

An analyst flagged that a low-privilege service account can somehow run a root shell. Someone widened its sudo rights and hid the change in plain sight. Track down the misconfiguration, understand how it grants escalation, and recover the flag it guards.

Write-up

Goal

A low-privilege service account can escalate to root through a hidden sudo rule. Find the misconfiguration, understand the escalation, and recover the flag it guards.

1. Check what the account may run

sudo -l
# or inspect the drop-ins directly:
ls -la /etc/sudoers.d/
cat /etc/sudoers.d/*

The interesting rule isn't in the main /etc/sudoers — it's a drop-in granting NOPASSWD on a binary that looks innocent but can write files or spawn a shell as root.

2. Turn the allowed binary into root access

Any binary that can execute a shell, write to an arbitrary path, or read arbitrary files is game over when it runs as root. GTFOBins-style abuse: if the allowed command can spawn a subshell or copy a file, use it to read the root-only flag.

# example shape — the exact binary is on the box:
sudo /the/allowed/binary <args that read or exec>
# recover the flag from where only root can read it
sudo cat /root/flag.txt

Takeaway

NOPASSWD sudo grants are audited by reading every file under /etc/sudoers.d, not just /etc/sudoers. Grant the narrowest possible command, never a binary that can shell out, and prefer full paths with locked-down arguments. The defensive win here is spotting the over-broad rule before an attacker does.

The Log Never Lies

incident_response · Easy · 15-20 min

A brute-force wave hit this host's SSH, and exactly one attempt succeeded. The attacker cleared their shell history, but the auth log remembers. Reconstruct the timeline: find the source that broke in, the account it landed on, and the flag left in that user's home.

Write-up

Goal

Reconstruct an SSH brute-force from the auth log: find the attacking source, the account that fell, and the flag in that user's home. The shell history was cleared — but the auth log remembers.

1. Find the brute-forcer

Failed and accepted logins both land in the auth log. Count failures by source IP to find the burst:

grep 'Failed password' /var/log/auth.log | grep -oE 'from [0-9.]+' | sort | uniq -c | sort -rn | head
# (or: journalctl -u ssh | grep 'Failed password' ...)

One IP dwarfs the rest — that's the attacker.

2. Find the account that fell

The first 'Accepted password' from that same IP is the moment of compromise:

grep 'Accepted password' /var/log/auth.log | grep '<attacker-ip>'
# -> Accepted password for <user> from <attacker-ip>

3. Recover the flag

ls -la /home/<user>/
cat /home/<user>/<the-flag-file>

Takeaway

Auth logs are the timeline of every login attempt. Aggregating failures by source finds brute forces; the first success from that source is the pivot. Clearing shell history doesn't touch the auth log — which is exactly why centralized, tamper-resistant logging matters for detection.

‹ all CTF seasons