The host threat model
Attack surface of a default server, and assume-breach.
This course assumes the attacker already got in. The hardening course (linux-hard) was about keeping them out: fewer services, tighter sudo, signed modules, a working firewall. This one starts one step later, when prevention has already failed and someone is running code on your host. The job changes from "how do I lock the door" to "what will they do next, what would I see, and what do I do about it." It builds on linux-ess and linux-hard: you should be comfortable with services, accounts, sudo, SSH and auditd rules before you start here. Everything runs on Ubuntu Server 26.04.1 LTS (kernel 7.0.0-34, aarch64 in the lab), with Rocky Linux 10.2 shown where RHEL differs. By the end of this lesson you can enumerate the real attack surface of a default 26.04 server the way an intruder does, and explain why a change to that surface, not the surface itself, is the thing to alert on.
You do not land as root
An attacker who lands on a Linux host almost never arrives as root, the account with user ID 0 that every permission check waves through. They arrive as a nobody: a web app running as its service account gets exploited, a low-privilege key is copied off a laptop, a poisoned dependency runs inside whatever account pulled it in. To see that starting position, create a stand-in service account, become it, and ask the two questions an intruder asks first: who am I, and what can I touch. The account is a throwaway made for this lesson; the terminal removes it again at the end.
uid=999(tm-www) is a system account (the numbers on your machine will differ; useradd --system picks a free ID from the system range below 1000) with no login shell and no membership in sudo or adm, the groups that would grant administrative power or the right to read system logs. It cannot read /etc/shadow, the file that holds password hashes, so the read is denied. What it does have is code execution and time, and a competent intruder spends that time looking, not smashing. The rest of this lesson is that look, run against a default install so the numbers are real.
The attack surface of a default server
The first thing worth mapping is every program that runs as root regardless of who starts it: the SUID-root binaries, which a normal user can launch and which run with root's authority for their lifetime. "SUID, SGID and the sticky bit" in Linux essentials explains the bit and walks through the default 26.04 set; here you read that set the way an intruder does. List it with one command.
This is the baseline from the essentials lesson, unchanged on a fresh install. An intruder reads it for what is missing as much as for what is there: there is no pkexec and no polkit agent helper. That matters, because the most famous local-root bug of the last decade, PwnKit (CVE-2021-4034, a flaw in pkexec), lived in a SUID binary that a default 26.04 server does not even carry. PwnKit is history worth knowing, not a live finding here.
SUID is not the only road up. File capabilities split root's power into separate keys (about forty of them), and a binary can carry one without ever being SUID, which means it never appears in the scan above. getcap is the only way to see them.
ping holding cap_net_raw is why it no longer needs to be SUID: it gets the one capability it needs to open a raw socket and nothing more. snap-confine carrying a long list including cap_setuid and cap_sys_admin looks alarming but is expected on a snap-enabled image; the =p suffix means the caps are permitted but not switched on at launch. The lesson on local privilege escalation (linux-det/peloc) takes capabilities apart properly. For now the point is that your surface has two layers, and a scan of one is not a scan of the other.
The last part of the surface is what the machine exposes to the network. List the sockets that are accepting connections.
A default server is quiet: sshd on port 22, and systemd-resolved's stub resolver on 127.0.0.53 and 127.0.0.54, which only listen on the loopback address and are not reachable from outside ("Networking basics" in Linux essentials reads these columns line by line). Port 22 shows two owners, sshd and systemd, because on Ubuntu SSH is socket-activated. Ask systemd how.
ssh.socket is the enabled unit and holds port 22. Accept=no means systemd does not start anything per connection: on the first connection it starts ssh.service once, the sshd listener in the ss output, and passes it the listening socket (systemd.socket(5)). That is why ssh.service reads disabled yet is active, and why both processes hold the socket. From then on sshd itself hands each connection to its own sshd-session process. For a defender this changes one habit: systemctl stop ssh.service does not close port 22, because the next connection starts the service again; taking SSH off a host means stopping and disabling ssh.socket. Every open port is an entry point to account for, and every one that appears later without a change record is a question.
The credential that is already root
Escalation is easiest when there is nothing to escalate: an account the intruder already controls that can become root through sudo. On a default cloud image the first administrative account can do exactly that, and sudo -l prints its own rights.
(ALL) NOPASSWD: ALL is the line that matters to an intruder: this account runs anything as root with no password prompt, so whoever steals its key or session has an instant, gate-free path to root. On a cloud image cloud-init writes that grant for its default user (on this lab machine the lab's own drop-in grants it to deploy); "Users, groups and account lifecycle" in Linux hardening shows where such grants live and how to remove one safely. Using the grant is logged, and the next lesson, "The intrusion chain on Linux", reads that log line.
Where detection comes from, and why a change is the whole signal
On RHEL and Rocky the audit daemon (auditd), which collects the kernel's audit records and writes them to disk, is installed and enabled out of the box. On Ubuntu it is not.
A default Ubuntu server has the kernel's audit subsystem but no auditd to collect from it, so auditctl is not even on the path. Adding real host detection on Ubuntu starts with sudo apt-get install auditd; on RHEL 10 (audit 4.0.3) it is already running. Later lessons install it and build the rules. The mindset to carry forward is the one every command above pointed at: the surface itself, the SUID set, the capabilities, the listeners, the sudo grants, is not the alert, because a working server is full of legitimate power. The alert is the difference from a known-good baseline. That is what makes a detection survive a careful attacker: you key on the change they cannot avoid making, not on any name or string they get to choose.
Try this
On a lab machine, record the privileged surface exactly as this lesson did: save the output of find / -xdev -perm -4000 -type f and sudo getcap -r / to a file, and note the listeners from sudo ss -tlnp. Then create a harmless SUID file with sudo install -m4755 -o root /usr/bin/true /var/tmp/.probe, re-run the find, and confirm the new line appears. Remove it with sudo rm /var/tmp/.probe and confirm it is gone. You have just done, by hand, what the baseline-and-diff job in linux-det/pedetect does on a schedule: the file you planted is the change, and the change is the finding.
Takeaway
Know the default attack surface of your platform by sight, keep a baseline the host itself cannot rewrite, and alert on the difference, not the list. Prevention has a failure rate above zero, so budget real effort for seeing the intruder who already got in.
passwd must write /etc/shadow and mount changes the mount table; a blanket strip breaks ordinary use and finds nothing.pkexec and no polkit agent helper, so the PwnKit surface is simply absent.pkexec is not in the default set, so treat the CVE as a lesson in how one SUID binary became instant root, not a finding on this host./usr/sbin/nologin and which is in neither sudo nor adm. A colleague says the intruder is therefore stuck inside the application. What does the foothold view in this lesson say?nologin is only the login shell; it refuses an interactive login, but code running as the account can still start programs directly./etc/shadow are closed to it, as the lesson's cat showed.