Linux+ Break Room · Case 05
Doors that open by themselves
Somewhere in this house, a voice whispers "I know you're there..."
Every night, doors in this server creak open on their own. Something is listening on a port nobody opened. You silence it, and a minute later it's back. The old séance board can be read by anyone who walks past. The caretaker's SSH door lets in root, passwords, even empty passwords. And there's a key on the caretaker's ring that belongs to someone called agatha@beyond. Seal the house without locking yourself out. Ghost, or Linux?
⚠️ This case needs sudo
- Play it on a practice VM (a free Ubuntu VM, a spare Pi, a lab node), not a machine you care about.
- Your real SSH, network and firewall are left alone. Everything haunted lives on a pretend address,
10.66.6.6, on a fake network card calledghost0that only the machine itself can reach (a small guard table in nftables,breakroom05guard, makes sure of that). - It creates a user called
caretaker, a second SSH server on port 2222 (only on 10.66.6.6), two tiny Python listeners, and a timer.--cleanremoves all of it, plus your firewall table if you name ithaunted. It leaves the key you make yourself (~/.ssh/breakroom_ed25519); delete that whenever you like. - Tested on Ubuntu 24.04 (Debian should work the same). It needs
openssh-server,nftablesandnc(sudo apt install -y openssh-server nftables netcat-openbsd). If openssh-server wasn't installed before, installing it also starts a real SSH server on port 22: that's normal, and it's yours to keep or remove.
Get the case
Rule 4: read it before you run it, less breakroom-mission-05.sh, especially this one, because it runs as root. The comments list every sabotage.
curl -O https://arunnetworkingpro.com/labs/breakroom-mission-05.sh
sudo bash breakroom-mission-05.sh
At any point, ask the house how many ghosts are left:
sudo bash breakroom-mission-05.sh --check
Part A: who's knocking?
Count the doors
How many ports are open on 10.66.6.6, and which numbers?
Hint
sudo ss -tlnp lists every listening TCP port: -t TCP, -l listening only, -n numbers instead of names, -p the process behind it (needs sudo). Pipe it through grep 10.66.6.6.
Who's whispering?
nc -w 3 10.66.6.6 31337Something answers. Find the systemd service behind port 31337. Its name tries very hard to look innocent.
Hint
ss -tlnp gave you a pid=. systemctl status <pid> tells you which unit that process belongs to.
Silence it
Stop it, and make sure it doesn't start at boot. Then wait a minute and knock again.
Hint
sudo systemctl disable --now <unit> does both. Then sleep 70; nc -zv -w 3 10.66.6.6 31337. Did it stay dead?
Who keeps summoning it?
It came back. Something is raising it on a schedule. Find the ritual and end it for good.
Hint
systemd has its own alarm clocks: systemctl list-timers --all. Disable the timer with --now, then stop the whisper once more. Or go nuclear: systemctl mask makes a unit impossible to start at all.
Part B: shut the doors
Close every door but one
The séance board on port 8080 must keep running, but nobody may reach it. Only door 2222 stays open on 10.66.6.6. Build it with nftables, in your own table called haunted, and only ever match ip daddr 10.66.6.6, so your real network is never touched.
Panic button: sudo nft delete table inet haunted removes everything you added, instantly.
Hint
Start with sudo nft add table inet haunted and a chain: sudo nft add chain inet haunted input '{ type filter hook input priority 0; policy accept; }'. Then add rules in order: let replies through (ct state established,related accept), accept port 2222 to 10.66.6.6, drop everything else to 10.66.6.6. Test with nc -zv -w 3 10.66.6.6 8080 and 2222. If 2222 stops working too, think about the answers coming back to 10.66.6.6.
Silence or a slammed door?
Time a knock on 8080: time nc -zv -w 5 10.66.6.6 8080. Now change your last rule from drop to reject and time it again. What changed, and which would you use facing the internet?
Hint
sudo nft -a list table inet haunted shows each rule's handle number. Delete one with sudo nft delete rule inet haunted input handle <n> and add the new one. Or flush the chain and add all three again.
Part C: exorcise the SSH door
Read the curse
The haunted SSH server reads /etc/breakroom-05/sshd_config. Find the five settings that leave the door wide open.
Hint
Ask sshd what it will really use: sudo sshd -T -f /etc/breakroom-05/sshd_config. Look at root login, passwords, empty passwords, how many tries a guesser gets, and X11 forwarding.
Break the curse
Fix all five, check the file, restart the haunted SSH service, then prove passwords are no longer offered:
ssh -p 2222 -o PubkeyAuthentication=no caretaker@10.66.6.6Hint
Use sudo nano on the config. sudo sshd -t -f /etc/breakroom-05/sshd_config prints nothing when it's happy. Restart breakroom-05-sshd and give it a second. Won when the reply says Permission denied (publickey), with no password in the list.
The stranger's key
The caretaker's authorized_keys holds a key from agatha@beyond. Nobody knows who that is. Remove it.
Hint
The file is /home/caretaker/.ssh/authorized_keys, one key per line; the comment at the end says whose it is. sudo nano it, or sudo sed -i '/agatha@beyond/d' the file.
Your key, your door
Make yourself a key, add it to the caretaker's list, and log in with it:
ssh-keygen -t ed25519 -f ~/.ssh/breakroom_ed25519 -N ''
sudo tee -a /home/caretaker/.ssh/authorized_keys < ~/.ssh/breakroom_ed25519.pub
ssh -i ~/.ssh/breakroom_ed25519 -p 2222 caretaker@10.66.6.6 whoamiIt still says no, even with the right key. Why? Fix it until the last command prints caretaker.
Hint
sshd writes down why it refused you: sudo journalctl -u breakroom-05-sshd -n 20. Look for "bad ownership or modes". sshd wants the .ssh folder at 700 and authorized_keys at 600, owned by the caretaker.
✅ How you know you won
sudo bash breakroom-mission-05.sh --check says Verdict: 7/7. Not a ghost. It was Linux all along. Case closed.
Answer key (no peeking until you've tried)
A1: three doors: 2222, 8080, 31337. A2: breakroom-05-whisper.service ("Totally normal system helper"). A3: sudo systemctl disable --now breakroom-05-whisper. It's back within a minute. A4: breakroom-05-summon.timer. sudo systemctl disable --now breakroom-05-summon.timer, then stop the whisper again (or mask it). B1: ct state established,related accept, then ip daddr 10.66.6.6 tcp dport 2222 accept, then ip daddr 10.66.6.6 drop. B2: drop hangs until the 5-second timeout; reject says Connection refused instantly. Drop for the internet, reject inside your own network. C1: PermitRootLogin yes, PasswordAuthentication yes, PermitEmptyPasswords yes, MaxAuthTries 10, X11Forwarding yes. C2: set them to no, no, no, 3, no, then sudo sshd -t -f ... and sudo systemctl restart breakroom-05-sshd. C3: delete the agatha@beyond line. C4: Authentication refused: bad ownership or modes. sudo chmod 700 /home/caretaker/.ssh and sudo chmod 600 /home/caretaker/.ssh/authorized_keys.
💥 Let it haunt you again
Run the script again. Every door creaks open again, fresh:
sudo bash breakroom-mission-05.shNow do it without the hints. (SSH will shout REMOTE HOST IDENTIFICATION HAS CHANGED, because every run makes a new host key: ssh-keygen -R '[10.66.6.6]:2222' forgets the old one.) Then try Part B with ufw or firewalld instead of raw nftables. And when you're done for good: sudo bash breakroom-mission-05.sh --clean.
🧠 The ghost, explained
Verdict: not a ghost. Every open door was a program listening on a port, every "resurrection" was a timer, and every way in was a line in a config file.
An open port is just a program listening. ss -tlnp shows every listener, its address, and the process behind it. Give the PID to systemctl status and it tells you which service owns it. That's how you go from "what is this?" to "who put it there?".
Things that come back from the dead have a reason. disable --now stops a service and stops it starting at boot, but anything can still start it on demand. Here it was a timer. On real servers it's timers, cron jobs, other services, or socket activation. systemctl list-timers is your first suspect. mask is the stake through the heart: nothing can start a masked unit.
Firewalls see the replies too. A rule that drops everything to an address also drops the answers coming back to it. That's why real rulesets start with ct state established,related accept: the firewall remembers conversations, so only the first knock of a new one gets judged.
Drop or reject? Drop is silence: the knocker waits and gives up, and learns nothing. Reject answers "go away" instantly. Drop suits the internet (give attackers nothing); reject is kinder inside your own network, where a fast "no" saves a colleague five confused minutes.
sshd config is where the doors really are. sshd -T prints the settings sshd will actually use, after defaults and includes, which is what you should audit rather than the file you think it reads. sshd -t checks a config before you restart, so a typo can't lock you out. And a restart is needed: a fixed file does nothing until sshd reads it.
authorized_keys is the guest list. Every line is a key that opens the door, no password asked. Old keys from people who left are one of the most common real-world holes. And StrictModes makes sshd ignore the whole list if anyone but the owner could edit it: that's why a 777 folder broke your login too.
Other families. Ubuntu's ufw and Red Hat's firewalld (firewall-cmd) both write nftables rules underneath, so what you learned here is what they do for you. On Red Hat, SELinux also stops sshd from listening on a non-standard port like 2222 until you label it with semanage port. That's a whole other ghost: Case 07.
← Case 04 · All cases · Case 06 → · Stuck, or found a better way? Email me
🎉 Got it, thank you!
Your comment just landed in my inbox. I read every one, and I'll reply by email.
🤔 That didn't go through
Something in the form looked off. Check your name, email and comment and try again, or just email me.
🐢 Whoa, slow down
That's a lot of comments in a short time, so the box is taking a breather. Try again later, or email me.
😴 The comment box is napping
My server is taking a quick break, so your comment couldn't be sent. Sorry! Please email me instead.
💬 Leave a comment
Stuck, found a better way, or just built it and want to brag? Tell me. It comes straight to my inbox (nothing is posted publicly), and I'll reply by email.
Your email is only used to reply to you. Never shared, never added to any list.