sudo Security
Root and sudo already covered how sudo works, how it differs from su, and how to read and edit a sudoers policy safely with visudo. This article assumes that foundation and goes one level further: how to keep a sudo policy from decaying into "everyone has ALL" over time, how to make sure sudo usage is actually recorded somewhere an incident responder can find it, and how to think about sudo as an attack surface in its own right — because the program that grants root is itself a piece of software with a version number and, occasionally, a CVE.
What You Will Learn
By the end of this article you will be able to:
- scope sudoers rules with
Cmnd_Aliasand explain why restricting only the binary name is not enough; - enable and read sudo's I/O logging to reconstruct what an elevated command actually did;
- tune the credential timestamp and authentication requirements deliberately, not by accident;
- explain why keeping
sudoitself patched is part of sudo security, with a concrete historical example; - distribute a consistent sudo policy across many servers instead of hand-editing each one.
Scoping Rules with Cmnd_Alias
A sudoers rule restricted to a single command is safer than ALL, but a collection of related commands repeated across many rules becomes hard to review and easy to get subtly wrong. Cmnd_Alias groups them under one name:
Cmnd_Alias APP_CTL = /usr/bin/systemctl restart myapp, /usr/bin/systemctl status myapp
deploy ALL=(root) NOPASSWD: APP_CTL
This is a readability and auditability improvement, not a new security boundary by itself — but it matters for security in a very concrete way: a policy that's easy to read gets reviewed correctly, and a policy nobody can parse at a glance gets rubber-stamped instead.
An allowed command can still be a wide-open door
Restricting a rule to /usr/bin/systemctl restart myapp blocks running arbitrary commands, but if myapp's own configuration lets it execute other programs — a systemd unit with an ExecStartPre= pointing at a script the deploy account can edit, for instance — the sudo rule's narrowness didn't actually narrow what the account can ultimately do. Scoping the sudo rule and checking what the allowed command can reach are two separate steps; neither substitutes for the other. Tools like GTFOBins catalog exactly this pattern for common binaries — checking a command's entry there before granting NOPASSWD access to it is a reasonable habit for anything beyond a small, fixed set of well-understood administrative commands.
Recording What Actually Happened: I/O Logging
By default, sudo logs that a command ran, and as whom, but not what the command actually printed or received as input. For accounts with broad or sensitive access, that gap matters during an incident — knowing deploy ran sudo bash tells you almost nothing about what happened inside that shell. Defaults log_input,log_output closes it:
Matching Defaults entries for <username> on prod-01:
log_input, log_output, iolog_dir=/var/log/sudo-io/%{user}
Once enabled, a session's full input and output can be replayed:
sudoreplay plays the recorded session back exactly as it happened, keystroke for keystroke — the closest thing to standing behind the operator's shoulder after the fact.
Cost and scope
I/O logging generates a meaningful amount of log data for interactive sessions, and it doesn't apply retroactively — it only captures sessions started after it's enabled. Turning it on for every account by default is reasonable on a small server; on a large fleet, scoping it to accounts and commands with genuinely elevated risk (an interactive root shell grant, for instance, rather than a single fixed systemctl restart) keeps the log volume proportional to the actual risk.
Tuning the Credential Timestamp
By default, once a user successfully authenticates to sudo, that authentication is cached for a few minutes (timestamp_timeout, default 15 on most distributions) so repeated sudo commands in the same terminal don't each demand a password. This is a convenience/security trade-off worth setting deliberately rather than leaving at the default:
A shorter window reduces the time an unattended, already-authenticated terminal remains a live root shell if the operator steps away; 0 demands a password on every single invocation. use_pty is a related setting worth enabling alongside I/O logging:
use_pty forces the command to run inside a pseudo-terminal that sudo itself controls, which is a prerequisite for log_input/log_output to reliably capture interactive sessions rather than only the command's final output.
Keeping sudo Itself Patched
sudo is a setuid-root binary parsing untrusted input, which makes it a target in its own right, not just a policy file. CVE-2021-3156 ("Baron Samedit"), a heap-based buffer overflow in sudo's command-line parsing, allowed any local user — including one with no sudoers entry at all — to escalate to root, and it went undetected in the codebase for roughly ten years before being found. No sudoers policy, however carefully scoped, protects against a flaw in sudo itself.
Checking the installed sudo version against the distribution's security advisories, and making sure sudo updates aren't excluded from automatic security patching (covered in Production Updates), is as much a part of "sudo security" as the policy file is.
Centralizing Policy Across Many Servers
Hand-editing /etc/sudoers.d/ on each server individually is the point where policy drifts — one host gets a fix the others don't, and nobody remembers which is authoritative. Two common approaches:
- Configuration management (Ansible, Puppet, Salt) — the sudoers drop-in files under
/etc/sudoers.d/are pushed from a version-controlled source, sogit logshows exactly who changed which grant and when. sudowith LDAP (sudo-ldap) — sudo rules are stored centrally in an LDAP directory instead of local files, and every host reads the same policy at evaluation time; a role change for one user updates every server at once, with no per-host drift possible.
Either approach turns "did we remember to update every server" into a solved problem rather than a manual checklist — and, combined with sudoreplay and centralized log shipping (covered in Security Log Analysis), gives a fleet-wide answer to "what has this account done with elevated privileges" rather than a per-host one.
Practical Scenario: Auditing an Overgrown Policy
Problem. A server has accumulated sudoers entries over several years, and no one is certain which are still needed.
List everything currently granted, without changing anything:
/etc/sudoers.d/deploy-app:deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp
/etc/sudoers.d/old-contractor:contractor ALL=(ALL) NOPASSWD: ALL
/etc/sudoers.d/backup-job:backup-svc ALL=(root) NOPASSWD: /usr/local/bin/run-backup.sh
Check whether each account is still active:
The contractor account's shell is already nologin — the sudoers entry is dead weight left behind after offboarding, and worse, an ALL=(ALL) NOPASSWD: ALL entry for an account that no longer needs it at all.
Remove the stale entry and validate the remaining policy:
Conclusion. visudo -c confirms the remaining policy is syntactically valid after the removal, but syntax validity says nothing about whether a grant is still needed — that judgment call requires cross-checking each entry against the accounts and jobs that are actually still in use, which is precisely the kind of review that should happen on a schedule rather than only when someone happens to notice.
Common Mistakes
- Equating "restricted to one binary" with "safe." As covered above, a scoped command that can itself execute other programs reopens the same door the restriction was meant to close.
- Enabling I/O logging and never reviewing it. Logs that accumulate without a review process provide forensic value after an incident but do nothing to prevent one; pairing I/O logging with a periodic review, or at minimum an alert on
sudoreplay-worthy sessions, closes that gap. - Assuming a sudoers file with no
NOPASSWDentries is inherently safe. Requiring a password slows down casual misuse but does nothing against a session where the account's own password or SSH key is already compromised — sudo security and account security are related, not identical, concerns. - Never checking the installed
sudoversion. As with any setuid binary, an unpatchedsudois itself a vulnerability independent of how carefully the sudoers policy is written.
Exercises
- On a lab system, create a sudoers rule scoped to a single, harmless command using
Cmnd_Alias, and verify withsudo -lthat only that command is permitted. - Enable
Defaults log_input,log_outputfor your own account, run a short interactivesudo bashsession, and replay it withsudoreplay. - Check the installed
sudoversion on a system you have access to and determine, from the distribution's changelog or advisory feed, whether it includes fixes for any publicly known CVEs. - Explain, for a fleet of 50 servers, why editing
/etc/sudoers.d/by hand on each one is a security liability even if every individual edit is correct.
Summary
Sudo security goes beyond a correctly written sudoers file: rules need to be scoped with tools like Cmnd_Alias and checked against what the allowed command can itself do, sensitive sessions benefit from I/O logging and sudoreplay, credential caching should be tuned deliberately, sudo itself needs to stay patched, and policy across many servers needs a single source of truth rather than per-host edits. The next article, PAM Basics, covers the authentication framework that sudo — along with su, login, and sshd — delegates the actual identity check to.