Kernel & credential escalation
Kernel exploits, cron, PATH, and secrets.
You have locked the place down. File permissions are tight, sudo (the tool that lets a user run a few chosen commands with elevated power) is scoped to a handful of commands, and the firewall only opens the ports you meant to open. An attacker who lands on the box as an ordinary user is stuck at their own privilege level. Now they have two roads up. They can attack the one piece of software that every other program is forced to trust, the kernel. Or they can hunt for a key that somebody left lying around on disk or in memory. This lesson walks both roads from the attacker's point of view, so you can stand on the other side and watch for the same footprints.
The One Guard Everyone Obeys
Every door in a building has its own lock. But there is one electrician who controls the power to all of them, and every lock obeys the power. The kernel (the core part of the operating system that talks directly to the hardware and decides which process is allowed to do what) is that electrician. Root (the account that can do anything on the box), your user, a background service, all of them hand their requests to the kernel and trust the answer it gives back. A bug in the kernel's own code turns that trust into a skeleton key.
The dangerous class here is local privilege escalation (a flaw that lets a program already running on the machine gain more power than it was handed). You have probably heard the famous ones. Dirty COW, short for dirty copy-on-write, was a 2016 kernel bug catalogued as CVE-2016-5195. A CVE (Common Vulnerabilities and Exposures) ID is the public catalog number stamped on each known bug, so anyone can look it up and, often, download a working exploit. That one lived in the kernel's copy-on-write memory handling and let an ordinary user overwrite files they were only allowed to read. Dirty Pipe (CVE-2022-0847, a 2022 bug in how the kernel handles pipe buffers) did much the same thing. Overwrite the right file and you are root. These exploits are public, they are reliable, and attackers weaponize them within days of disclosure. You cannot configure your way out of a bug in the code itself. The sysctl hardening (tuning the kernel's own runtime settings) from the earlier course raises the bar and shrinks what an exploit can reach, but the only real fix is a patched kernel.
An attacker's first move is to read the kernel version and pick a public exploit that matches it. You run the exact same command for the opposite reason. You want to find every host whose kernel carries a known local-root CVE and patch those first. There is a catch in that output that trips up a lot of teams, and it is sitting right there in the version numbers.
Jobs That Run as Root Without Looking
A note is stuck to the fridge, and the most powerful person in the house will carry out whatever it says, no questions asked. Whoever can edit the note controls that person. On a Linux box, the note is a scheduled job. Two systems schedule work: cron (the classic Unix job scheduler that runs commands at set times) and systemd timers (the modern equivalent built into systemd, the program that boots the machine and supervises its background services). A job that runs a script as root is only as safe as the answer to one question: can anyone below root change what that script does?
# /etc/cron.d/backup: nightly web-root backupPATH=/opt/scripts:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin*/5 * * * * root backup.sh
Read that job closely. Every five minutes, root runs a command called backup.sh. Not /opt/scripts/backup.sh with a full path, but backup.sh by bare name, and the job sets its own PATH (the ordered list of directories the shell searches to find a command you typed by name) with /opt/scripts at the very front. The same work often has a systemd timer behind it too, which you can list directly.
[Unit]Description=Nightly web-root backup[Service]Type=oneshotExecStart=/opt/scripts/backup.shUser=root
Both the cron entry and the timer point at the same script running as root. So the whole question of safety collapses into permissions. Who can write backup.sh, and who can write the directory it lives in? Check the script, and check every directory in that job's PATH.
Calling a Command by Its First Name
There is a second way in here, and it lives in that PATH line. Shout "Sam, grab the mail" across a crowded room and whoever answers to Sam does the job. If two people answer to Sam, the closer one wins. When a script runs tar instead of /bin/tar, the shell does the same thing: it walks each directory in PATH in order and runs the first file named tar that it finds. Look at what backup.sh actually calls.
#!/bin/sh# back up the web root, keep one archive per daycd /var/www/htmltar czf /backups/www-$(date +%F).tgz .
tar and date are both called by bare name. The job's PATH starts with /opt/scripts, which you can write to. Drop a file called tar into /opt/scripts, mark it executable, and the next run of this root job executes your tar instead of the real one. You never even had to touch backup.sh. This is why a writable directory anywhere in a root job's PATH is as dangerous as a writable script, and why hardened jobs use absolute paths and set a clean, root-only PATH.
Going Through the Drawers for Keys
Escalation on this box is not the only prize, and often it is not the real one. A burglar who cannot crack your safe will happily pocket the spare keys in the kitchen drawer, because those keys open other doors. The same is true here. A host holds credentials to other systems, and every one of them is a way off this machine and onto the next. So an attacker greps the filesystem for anything that smells like a password or a key.
Read those results the way an attacker does. The SSH (Secure Shell, the standard way to log into a remote machine) private keys in /home/deploy/.ssh open whatever servers trust them. The AWS (Amazon Web Services) keys in ~/.aws can read or spin up cloud resources far beyond this box. The environment dump is the sharp one. Secrets handed to a service as environment variables sit in plain text inside that process's /proc/PID/environ (a virtual file the kernel exposes for every running process), readable by root and by the user the service runs as. Compromise the service account and you inherit every secret in its environment, including the JWT (JSON Web Token, a signed token that proves who a request is from) signing key that lets you mint your own valid sessions. One foothold, and the host hands over the keys to everything it was talking to.
Two checks belong in your weekly routine, and both come straight out of this lesson. Run systemctl list-timers --all and read /etc/crontab together with /etc/cron.d to get the full list of what runs as root on a clock, then check the ownership and mode of every script and every directory in each job's PATH. And compare uname -r against the version of the installed linux-image package on each host. When apt reports a fixed kernel but uname still shows the old one, you have found a privilege escalation that is already sitting in your fleet, booted and waiting for someone to notice.
Try this
Work through “Going Through the Drawers for Keys” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: a patched kernel you haven't rebooted into is still the old kernel. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.