skip to content

You are reviewing a sudoers policy. Explain why a rule that grants NOPASSWD access to a single named command can still amount to full root, and what makes a rule genuinely narrow.

level: seniorimportance: should knowfreq 52%

answer

  1. you grant a program, not an outcome
  2. shell escape, file write, or config it reads
  3. no arguments listed means any arguments
  4. subtracting from ALL does not hold
  5. validate before install, or lock yourself out

basics

~20 s

A sudoers rule grants a program, not an outcome. If the program can spawn a shell, run an editor or pager, write to an arbitrary path, or read a file the user controls, the grant becomes full root. Narrow rules pin the absolute path, spell out the arguments, and avoid wildcards.

solid answer

~50 s

Sudo enforces exactly what you wrote: this user may execute this binary as that target. It cannot enforce what the binary then does. So the review question is never "is this one command dangerous?" but "what can this binary be made to do as root?". Three classic escapes: programs with a shell escape or a pager — an editor's `:!sh`, `find … -exec`, anything that pipes through `less`; programs that write arbitrary files as root — `tee`, `cp`, `dd`, `chown` — because writing `/etc/sudoers` or root's `authorized_keys` is game over; and programs that execute user-controlled configuration. Two sudoers details make it worse: a rule listing a command with **no** arguments permits **any** arguments, and a trailing wildcard usually permits far more than the author imagined. A narrow rule uses the absolute path, states the exact arguments (`""` to forbid all), avoids wildcards, and is installed as a validated drop-in under `/etc/sudoers.d`.

code

bash · 11 lines
bash
# Author the drop-in somewhere harmless first
printf '%%deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app.service\n' > /tmp/deploy

# Refuse to install anything that does not parse
visudo -cf /tmp/deploy

# Install with the mode and ownership sudo insists on (no dot in the name)
install -m 0440 -o root -g root /tmp/deploy /etc/sudoers.d/deploy

# Re-check the whole policy, includes and all
visudo -c

go deeper

for a junior

Know that sudoers rules should name absolute paths, that visudo is how you edit the policy, and that NOPASSWD removes the password prompt entirely.

for a middle

Explain that a rule listing no arguments permits any arguments, what a shell escape is, and why sudoers drop-in files must be validated before they are installed.

for a senior

Review a rule the way an attacker reads it — shell escapes, arbitrary file writes, user-supplied configuration — and propose the narrow rewrite, including sudoedit for file edits.

for a principal

Own the policy as code: allow-lists only, grants to groups with membership review, NOPASSWD confined to dedicated automation accounts, and syntax validation gating every change.

## What a sudoers rule actually says ``` deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app.service ``` Read as: user `deploy`, on host set `ALL`, may run *that command with those arguments* as `root`, without re-authenticating. Sudo's job ends the instant `execve` succeeds. Everything the program then does — read files, write files, start other programs — happens as root with no further policy in the loop. So reviewing a rule means answering one question: **what can this binary be talked into doing as root?** ## The three escape families **1. It can give you a shell.** Editors and pagers are the notorious ones — an editor's `:!sh`, `less`'s `!sh`, `find . -exec /bin/sh \;`, an interpreter that takes `-c`. Anything that pages its own output through `$PAGER` inherits the pager's escape. The community catalogue of these (GTFOBins) exists precisely because "it's just one command" is such a common misjudgement. Once a root shell exists, the rule's narrowness is irrelevant. **2. It can write a file of your choosing.** `tee`, `cp`, `dd`, `mv`, `chown`, `chmod`, a compiler with an output path, an archiver that extracts wherever the archive says. Root-writable file plus attacker-chosen path equals a new sudoers line, a new `/root/.ssh/authorized_keys`, a modified systemd unit, or a replaced binary. No shell escape needed. **3. It reads configuration you control.** A daemon started as root with `-c /path/you/wrote`, a tool honouring an environment variable naming a plugin, `systemctl link` pointing at a unit file in your home. The program is behaving correctly; you supplied its instructions. ## The two sudoers rules people get wrong **No arguments in the rule means any arguments are allowed.** `deploy ALL=(root) /usr/bin/systemctl` is not "they can restart things"; it is every subcommand systemctl has. If a command must take no arguments, say so explicitly with `""`: ``` deploy ALL=(root) NOPASSWD: /usr/local/bin/reload-app "" ``` **Wildcards match more than you think.** A pattern ending in `*` will happily match extra arguments, and once an attacker can append arguments, most programs can be steered. Wildcards in the middle of a path are worse. Prefer no wildcard at all; where the argument genuinely varies, wrap it in a small root-owned script that validates its input, and grant sudo on the script. **`!` subtraction is not a security control.** Writing `ALL, !/bin/su` reads well and does not hold: the sudoers documentation is explicit that subtracting commands from `ALL` is generally ineffective, because a user granted `ALL` can copy or rename a binary and run the copy. Grant an allow-list; never an all-minus-a-few. ## PATH, relative paths and the environment A rule must name an **absolute** path. A relative name would be resolved against a `PATH` the caller may influence. The corresponding server-side controls are `Defaults env_reset`, which rebuilds the environment from a small allowed set, and `Defaults secure_path="…"`, which pins the elevated `PATH` to trusted directories. Both are shipped enabled on mainstream distributions; a policy that disables them, or that adds a broad `env_keep`, has widened every rule in the file at once. ## NOPASSWD NOPASSWD is not the same defect as a wide command, but it removes the last speed bump: anyone who reaches that user's session — a stolen SSH key, a web shell running as that account, a stray CI job — gets the grant instantly with no secret to steal. Reserve it for non-interactive automation running as a dedicated account with a single narrow rule. Humans should re-authenticate; `timestamp_timeout` already caches it so the friction is small. ## Editing safely Always `visudo`. It takes a lock, opens a copy in a restricted editor, and refuses to install a file that does not parse — a syntax error in `/etc/sudoers` makes sudo refuse to run at all, and on a host where root has no password that is a lockout requiring console access. Put changes in `/etc/sudoers.d/` drop-ins (`visudo -f /etc/sudoers.d/deploy`), which the main file pulls in with an include directive; sudo 1.9.1 introduced `@includedir` and still accepts the older `#includedir` spelling. Files whose names contain a `.` or end in `~` are ignored, which silently defeats naming a file `deploy.conf`. Mode `0440`, owned by root. In CI, gate the change with `visudo -cf <file>` before it is ever shipped, and keep a second root-capable session open while you test. ## What a good rule looks like Absolute path; exact arguments or `""`; no wildcards; target user narrowed with `(root)` or better, a service account; granted to a **group**, not a username, so membership review is the control; passworded unless it is machine-to-machine; installed as a validated drop-in from configuration management. And for the common case of "they need to edit a root-owned file", use `sudoedit` (`sudo -e`) — it copies the file out, runs your editor as **you**, and copies it back as root, so the editor never runs privileged.

  • A team needs to edit a root-owned config file. What do you grant them?
    `sudoedit` (`sudo -e`) on that specific path. It copies the file to a temporary location, runs the user's editor as the **unprivileged** user, and writes the result back as root — so no editor process ever runs privileged and there is no `:!sh` to escape into. Granting sudo on the editor binary itself hands over full root, which is exactly what sudoedit exists to avoid.
  • Why is `deploy ALL=(ALL) ALL, !/bin/su` not a real restriction?
    Because subtracting from `ALL` is not enforceable. The user can copy `/bin/su` to another path and run the copy, or reach the same privilege through any of the thousands of other binaries `ALL` already permits. The sudoers documentation warns against this pattern explicitly. Security has to come from an allow-list of specific commands, never from an all-minus-exceptions rule.
  • How would you let a user restart one service without granting the whole systemctl binary?
    Pin the command and its arguments exactly — `/usr/bin/systemctl restart app.service` with no wildcard — and grant it to a group rather than a person. Confirm the invocation does not page its output through a pager the user can escape from. Where the argument must vary, put the validation in a small root-owned wrapper script and grant sudo on the wrapper instead.

saying these in an interview costs you the question

  • Assuming one allowed command cannot become full root
  • Writing a rule with no arguments and calling it narrow
  • Trusting wildcards to constrain the argument list
  • Blocking dangerous commands by subtracting from ALL
  • Editing /etc/sudoers directly instead of using visudo

context