Linux+ Break Room · Case 07
The invisible hand
The permissions say yes. Something you can't see says no.
A fortune teller program, breakroom-seer, can read one vision but not the prophecy, even though both files are readable by everyone. The church bell service is supposed to ring on port 99, but it isn't root, so the kernel won't let it. Meanwhile an ordinary-looking program can read any file on the system, secrets included. And the oracle only answers over HTTPS with a certificate from someone called Agatha, which expired long ago. Find the invisible hand.
⚠️ 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.
- Needs AppArmor (on by default in Ubuntu and Debian, but off on Raspberry Pi OS, so use a VM for this one) and
libcap2-bin,openssl,curl. - It creates two small copies of
catin/usr/local/bin, an AppArmor profile, a system userbellringer, two services, and one line in/etc/hosts(oracle.haunted).--cleanremoves all of it.
Get the case
Rule 4: read it before you run it, less breakroom-mission-07.sh. The top of the file lists everything it creates and how --clean removes it.
curl -O https://arunnetworkingpro.com/labs/breakroom-mission-07.sh
sudo bash breakroom-mission-07.sh
At any point, ask the house how many ghosts are left:
sudo bash breakroom-mission-07.sh --check
Part A: the seer
Permission says yes
ls -l /srv/breakroom-07/
breakroom-seer /srv/breakroom-07/vision.txt
breakroom-seer /srv/breakroom-07/prophecy.txtSame permissions, different answer. Who said no? Find the evidence.
Hint
When permissions aren't the reason, a security layer is. AppArmor writes its refusals to the kernel log: sudo journalctl -k | grep DENIED (nothing there? try sudo journalctl -g DENIED). It names the program (profile=) and the file (name=). sudo aa-status lists the loaded profiles.
Change the rule, keep the guard
Let the seer read the prophecy, while its profile stays in enforce mode. Turning AppArmor off isn't a fix.
Hint
Profiles live in /etc/apparmor.d/, named after the program's path. Look for a deny line: deny always wins over allow. After editing, reload just that profile: sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.breakroom-seer.
Part B: powers without root
The bell won't ring
breakroom-07-bell keeps failing. Find why, and let it use port 99 without running it as root.
Hint
sudo journalctl -u breakroom-07-bell shows the error. Ports below 1024 need a capability, CAP_NET_BIND_SERVICE, not full root. Give it to the exact program the unit runs: sudo setcap cap_net_bind_service+ep <file>. (Or in the unit: AmbientCapabilities=CAP_NET_BIND_SERVICE.) Then restart and nc 127.0.0.1 99.
A dangerous superpower
Somewhere on the system, an ordinary program can read every file, even /etc/shadow, for any user who runs it. Find it and take the power away.
Hint
sudo getcap -r / 2>/dev/null lists every file with capabilities. ping having cap_net_raw is normal. Something with cap_dac_read_search is not. sudo setcap -r <file> removes them.
Part C: the oracle's certificate
Read the certificate
curl https://oracle.haunted:8443curl refuses. Who is the certificate for, and when did it expire?
Hint
openssl s_client -connect oracle.haunted:8443 </dev/null | openssl x509 -noout -subject -dates. Or read the file itself: openssl x509 -in /etc/breakroom-07/oracle.crt -noout -subject -dates.
A new certificate
Make a new self-signed certificate for oracle.haunted, valid for a year, put it where the oracle expects it, restart, and make curl trust it:
curl --cacert /etc/breakroom-07/oracle.crt https://oracle.haunted:8443Hint
sudo openssl req -x509 -newkey rsa:2048 -nodes -days 365 -keyout /etc/breakroom-07/oracle.key -out /etc/breakroom-07/oracle.crt -subj "/CN=oracle.haunted" -addext "subjectAltName=DNS:oracle.haunted". Modern clients check the subjectAltName, not the CN. Then sudo systemctl restart breakroom-07-oracle.
✅ How you know you won
sudo bash breakroom-mission-07.sh --check says Verdict: 5/5. The invisible hand was Linux security all along. Case closed.
Answer key (no peeking until you've tried)
A1: the kernel log shows apparmor="DENIED" ... profile="/usr/local/bin/breakroom-seer" name="/srv/breakroom-07/prophecy.txt". A2: in /etc/apparmor.d/usr.local.bin.breakroom-seer, replace audit deny /srv/breakroom-07/prophecy.txt r, with /srv/breakroom-07/prophecy.txt r,, then apparmor_parser -r. B1: PermissionError: [Errno 13] binding port 99. sudo setcap cap_net_bind_service+ep /usr/local/lib/breakroom-07/python3, restart. B2: /usr/local/bin/breakroom-peek cap_dac_read_search=ep; sudo setcap -r /usr/local/bin/breakroom-peek. C1: subject=CN = agatha.beyond, notAfter=Feb 1 00:00:00 2025 GMT. C2: the openssl req -x509 ... command from the hint, then restart the oracle.
💥 Let it haunt you again
Run the script again. The haunting starts over, fresh:
sudo bash breakroom-mission-07.shNow do it without the hints. And when you're done for good: sudo bash breakroom-mission-07.sh --clean.
🧠 The ghost, explained
Verdict: not a ghost. The invisible hand was three real security layers above plain permissions: AppArmor, capabilities and TLS certificates.
Permissions are only the first bouncer. Mandatory access control (AppArmor on Ubuntu and Debian, SELinux on Red Hat and Fedora) adds a second list of rules per program, which even root can't ignore. When ls -l says yes but the program gets "Permission denied", check the kernel log for DENIED (AppArmor) or AVC (SELinux, with ausearch and sealert).
deny wins, and it's quiet. An AppArmor deny rule overrides any allow, and plain deny doesn't even log. That's why this case used audit deny: so there was evidence to find.
Capabilities slice root into small pieces. Binding a low port, sending raw packets, reading any file: each is its own capability. Giving a program exactly the one it needs beats running it as root. But a capability like cap_dac_read_search on a common tool is a backdoor, which is why getcap -r / belongs in every security audit.
A certificate is a signed name with an expiry date. Clients check three things: is it still valid, does the name (subjectAltName) match the address I asked for, and do I trust whoever signed it? Self-signed certs pass the third check only when you tell the client to trust them (--cacert).
← Case 06 · All cases · Case 08 → · 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.