Users & Permissions
Sudo
Delegating root privileges safely with sudoers, and common misconfigurations.
Nobody should log in as root for daily work, yet admins constantly need root for specific tasks. sudo squares that circle: it runs a chosen command as another user under a written policy, logging who did what. Done well, it is least privilege in action. Done badly, with a blanket NOPASSWD: ALL or a rule pointing at a command that can spawn a shell, it is a gift-wrapped path from an ordinary account to root. This page covers the policy language, safe patterns, and the escalation traps. Snapshot the lab first, because you will write a deliberately broken rule.
sudoers Syntax And visudo
The policy lives in /etc/sudoers and in drop-in files under /etc/sudoers.d/. Never edit them with a plain editor: use visudo, which checks syntax before saving so a typo cannot lock you out of sudo entirely.
A rule has the form: who host = (as-whom) commands. The default Ubuntu rule that makes the sudo group work looks like this:
sudo grep -E '^%sudo' /etc/sudoers
Expected output:
%sudo ALL=(ALL:ALL) ALL
Read it as: members of group sudo (% marks a group), on ALL hosts, may run as (ALL:ALL) any user and group, the command ALL. That is broad by design for an admin group but it is the opposite of least privilege for a single delegated task.
Prefer drop-in files for custom rules; they are easier to manage and remove. Create one with the drop-in-aware editor:
sudo visudo -f /etc/sudoers.d/deploy
Add a narrow rule, save, and confirm sudo parses the whole config:
sudo visudo -c
Expected output:
/etc/sudoers: parsed OK
/etc/sudoers.d/deploy: parsed OK
Least Privilege vs Blanket Grants
The core decision is how much you hand out. A least-privilege rule names the exact command (with a full path) that a user may run as root, and nothing else. A blanket rule hands over the whole system.
| Rule | Meaning | Verdict |
|---|---|---|
alice ALL=(root) /usr/bin/systemctl restart nginx | Restart nginx only, as root | Least privilege, good |
alice ALL=(root) NOPASSWD: /usr/bin/apt update | Update packages, no password | Narrow but skips auth |
alice ALL=(ALL:ALL) ALL | Anything, as anyone | Full admin |
alice ALL=(ALL:ALL) NOPASSWD: ALL | Anything, no password | Effectively passwordless root |
The bottom two rows are the ones attackers hope to find. NOPASSWD: ALL means anyone who gets a shell as alice, even briefly through a bug, becomes root with sudo -i and no prompt. Use full paths in command rules; a bare command name lets a user shadow it with a malicious binary earlier in $PATH. Set up a narrow rule in the lab and test it:
echo 'alice ALL=(root) NOPASSWD: /usr/bin/systemctl restart cron' | sudo tee /etc/sudoers.d/alice
sudo -u alice sudo -l
Expected output:
User alice may run the following commands on lab:
(root) NOPASSWD: /usr/bin/systemctl restart cron
sudo -l is the single most useful audit command: it prints exactly what a user may do. Run it as (or against) any account to see its real reach.
GTFOBins: Escalation Through Allowed Commands
Here is the trap that catches even careful admins. A “single command” rule is only as safe as the command it names. Many ordinary programs can spawn a shell or run arbitrary code, so allowing one of them as root effectively allows a root shell. This catalog of shell-spawning behaviors is known as GTFOBins. Editors, pagers, interpreters, and find are classic offenders.
Suppose an admin, meaning to let alice search the system, writes an over-broad rule. Create the vulnerable state in the lab:
echo 'alice ALL=(root) NOPASSWD: /usr/bin/find' | sudo tee /etc/sudoers.d/alice
Now alice uses find’s -exec to launch a root shell, never touching a “restricted” command directly:
sudo -u alice bash -c 'sudo find . -maxdepth 0 -exec /bin/sh \;'
Expected output:
# id
uid=0(root) gid=0(root) groups=0(root)
The prompt changed to # and id reports uid=0: alice is now root. find was allowed, find can run a program, and that program was a shell. The same pattern works with vim (:!sh), less (!sh), awk, python3, tar --to-command, and dozens more. The lesson is that “allow one command” is meaningful only if that command cannot execute other things. When you must delegate a risky tool, restrict its arguments in the rule and confirm the tool honors that restriction, or wrap it in a script that constrains what it can do. Clean up with sudo rm /etc/sudoers.d/alice or roll back the snapshot.
Sudo is one delegation mechanism among several. Compare it with the identity-changing behavior of Setuid & Setgid and the sliced-up root powers in ACLs & Attributes; group membership from Users & Groups is what selects who a rule applies to.
Practical Guidance
- Always use
visudo(orvisudo -ffor drop-ins) so a syntax error cannot lock everyone out of sudo. - Write rules with full command paths and the narrowest command set that works; avoid
ALLin the command field. - Reserve
NOPASSWDfor automation that genuinely cannot prompt, and never combine it withALL. - Before trusting a single-command rule, check the command against GTFOBins-style shell escapes; if it can spawn a shell, it grants root.
- Audit every account with
sudo -land treat any unexpectedNOPASSWDor shell-capable command as a finding. - Keep custom rules in
/etc/sudoers.d/as small, named files so they are easy to review and remove.