The sudo
utility is an essential tool for protecting privileged system access for most linux systems. But managing elevated access also makes it a prime target for local privilege escalation. Hardening the sudo configuration is necessary to prevent accidental internal attack surface.
Enforcing password authentication
Most default installation of sudo prompt the user to type their password before running a command with elevated permissions. The password check is then disabled for a specified timeout period (typically 5-15 minutes).
The reason for requiring password confirmation before commands is fairly simple: it prevents unintentional execution of privileged commands, for example if a malicious user is left alone on a logged in linux machine or ssh session. But it also helps reduce the risk of accidentally executing privileged commands hidden in unprivileged scripts. Passwordless sudo access could easily hide a sudo command in this file, and executing it would not provide any hints to the administrator running it. By requiring the user's password, this silent compromise isn't possible anymore.
While these security mechanisms are valid, it is equally true that constantly re-typing a password quickly becomes a nuisance for operators, tempting them to increase the timeout or enable complete passwordless access. Consider enabling passwordless sudo only for specific (the most common) commands instead to strike a balance between usability and security:
myadmin ALL=(ALL) ALL
myadmin ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
This allows only the execution of the specific systemctl restart nginx
command, but excluding it from the password prompting requirements. This kind of exception should only be given to specific commands (including parameters) - enabling tit for the entire systemctl
utility without password prompts would pose significant risk.
Administrators can reduce friction in their daily maintenance tasks without reducing the overall system security by using this kind of password prompt exception.
Locking down commands
If untrusted users must be given access to some privileged commands, these commands should be locked down as narrowly as possible, even if they seem harmless at first glance. Many programs like env
or find
can be used to execute arbitrary commands, and should not be allowed freely. Most of these can use the NOEXEC:
option to prevent them from invoking commands:
myadmin ALL=(ALL) NOEXEC: /usr/bin/find
This option is great for systems that support it (most modern linux versions do), but may not be enough for some edge cases.
Suppose you have a root-owned git repository and want to give a user access to making commits in it. The repository also contains some pre-commit hooks to run code formatters and linting software to ensure only quality code is committed to the history. Internally this means the git
command has to run commands now, so NOEXEC:
is out of the question. But giving users unfiltered access to git
is extremely easy to exploit by setting their editor config value to a shell or interactive interpreter:
sudo git config core.editor 'python3 -i'
sudo git commit
The core.editor
config key is used to find the preferred text editing program, which git commit
invokes if no commit message is given at the terminal. The user now has a privileged python3
interpreter, allowing them to run any code they like with root permissions.
The only viable defense in this case is to lock down the specific commands the user may run, including arguments:
somedev ALL=(ALL) /usr/bin/git commit
somedev ALL=(ALL) /usr/bin/git push
While more time-consuming to configure for the administrator, this is the only reasonable option in our sample scenario. You should still prefer NOEXEC:
where ever possible, or even combine them for an added layer of security.
Controlling environment variables
Some environment can be used to alter the way programs work. Most notable are the LD_PRELOAD
variable that can be used to link a custom compiled library against an arbitrary executable at runtime, arbitrarily altering its behavior by overriding the standard library C functions. Even harmless executables like ping
can be turned into a root shell with this technique. Other problematic variables include the EDITOR
, used to hold the preferred text editing program of a user - except nothing validates if this is really a text editor. Setting something like EDITOR='python3 -i'
easily weaponizes programs like git or kubectl
into potential root shells if run through sudo
.
The sudo
command strips most environment variables before executing a privileged command by default, which is a core security feature. This means the attack vector isn't viable without modification, but what if a developer insists on having their preferred editor choice respected for sudo
commands? The only safe solution is to write a wrapper script that checks the value of the editor and allow only that known safe values:
# /usr/local/bin/secure-kubectl
#!/bin/bash
# Only allow specific EDITOR values
case "$EDITOR" in
nano|vi|vim)
;;
*)
echo "Invalid EDITOR. Must be nano, vi, or vim."
exit 1
;;
esac
# Run the actual command
exec /usr/bin/kubectl "$@"
Then only permit the user to use the wrapper:
somedev ALL=(ALL) /usr/local/bin/secure-kubectl
The command may look unwieldy at first, but the developer can hide this by creating a bash alias
or shortcut.
Configuring audit logs
Especially if untrusted users must have access to some privileged commands through sudo
, these commands should be logged in case later auditing is required, and to verify if attempts were made to circumvent security measures.
Sudo can be configured to log not only the commands a user types, but also their input and output:
Defaults:some_user env_reset
Defaults:some_user log_input
Defaults:some_user log_output
Defaults:some_user log_year
The logged information is stored in /var/log/sudo-io/
and can be accessed via sudoreplay
. List all recorded commands with the -l flag first:
sudo sudoreplay -l
The output will list recorded commands one per line:
Apr 25 17:02:20 2025 : restricted : HOST=bookworm ; TTY=pts/1 ; CWD=/home/restricted ; USER=root ; TSID=000001 ; COMMAND=/usr/bin/echo hi
Apr 25 17:02:24 2025 : restricted : HOST=bookworm ; TTY=pts/1 ; CWD=/home/restricted ; USER=root ; TSID=000002 ; COMMAND=/usr/bin/touch somefiles
Apr 25 17:02:27 2025 : restricted : HOST=bookworm ; TTY=pts/1 ; CWD=/home/restricted ; USER=root ; TSID=000003 ; COMMAND=/usr/bin/less somefiles
Then take note of the TSID
value for the command you are interested in. You can then replay it and see exactly what the user saw:
sudo sudoreplay 000003
Be aware that logging command, while great for auditing, can quickly fill up the disk on busy systems or if users seek to exploit this feature. Consider setting up log rotation and disk quotas if possible