← Recipes

阅读中文版本

Review sudoers for Privilege Escalation Paths

Dump effective sudo rules per user, hunt wildcard and NOPASSWD grants that escape their intent, and tighten them with command argument pinning.

~30 minsecuritylinuxhardening

Task

Enumerate effective sudo grants, identify rules that allow more than intended, and rewrite them as pinned commands.

When to use this

A deploy user was granted a wildcard restart command for convenience. It turns out the same rule permits running any binary in that directory, including a world-writable script.

Prerequisites

  • root or sudo rights to run sudo -l on behalf of users
  • Access to /etc/sudoers.d files for remediation
  • visudo for syntax-safe editing

Steps

1. List the effective rules for the account in question.

sudo -l -U deploy 2>/dev/null || sudo -l

Expected output: The exact command list deploy may run as root prints, including any NOPASSWD markers and wildcard arguments.

2. Audit each granted command for argument wildcards and directory scope.

grep -rn "deploy" /etc/sudoers /etc/sudoers.d/ | grep -vE "^\s*#" | head -10

Expected output: The source lines print; a grant like (root) NOPASSWD: /opt/app/bin/* stands out as executing anything in that directory as root.

3. Demonstrate the unintended escalation path concretely.

ls -la /opt/app/bin/ | head -5; find /opt/app/bin -perm -o+w -type f 2>/dev/null

Expected output: World-writable files inside the granted directory are listed; any one of them is arbitrary root code under the wildcard rule.

4. Rewrite the rule as a pinned command with fixed arguments.

# /etc/sudoers.d/deploy  (edit ONLY with visudo -f)

# before: any binary in the directory, as root, no password
# deploy ALL=(root) NOPASSWD: /opt/app/bin/*

# after: exact command, exact arguments, explicit env
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app.service, \
                          /usr/bin/systemctl status app.service, \
                          /opt/app/bin/rotate-logs /var/log/app/*.log

Expected output: sudo -l -U deploy now shows three pinned commands; attempts to run anything else are refused and logged.

5. Verify the tightened rule and check the audit trail works.

sudo -l -U deploy; sudo -u deploy sudo /usr/bin/systemctl restart app.service && journalctl -t sudo --since "5 min ago" | tail -3

Expected output: The restart succeeds through the pinned path, and journalctl shows the sudo log entry with user, command, and result, closing the loop.

Troubleshooting

Problem

Wildcard in sudoers does not behave like shell glob

Solution

sudoers wildcards match across spaces, so /opt/app/bin/* also matches /opt/app/bin/x argument1 argument2; avoid wildcards in command paths and pin arguments explicitly instead.

Problem

Editing sudoers directly breaks sudo entirely

Solution

Never edit without visudo: it parses before saving; if locked out, use the distribution recovery mode or a pre-authorized root shell to restore the file.

Problem

Scripts invoked via sudo are themselves writable

Solution

A pinned script is only as safe as its file permissions; ensure the script and its directory are root-owned and not group-writable, or call a compiled binary instead.

Limitations

  • sudo -l reflects policy at check time; group membership changes or LDAP-backed rules can alter it without file edits, so re-audit periodically.
  • NOPASSWD trades interactivity for exposure; keep it only where automation genuinely cannot supply credentials.

A convenient wildcard is a root shell with extra steps

sudoers rules fail quietly: they keep working while quietly granting far more than intended. The wildcard semantics in sudoers differ from shell globs, matching across spaces and arguments. Dumping effective rules per user and testing the escape path converts a vague hardening checklist into concrete fixes.

# What can deploy actually do as root?
sudo -l -U deploy
# (root) NOPASSWD: /opt/app/bin/*

# Wildcards match across spaces in sudoers:
# /opt/app/bin/*  ALSO matches
#   /opt/app/bin/rotate-logs /etc/shadow   <-- escalation

# ls the granted directory for writable files
find /opt/app/bin -perm -o+w -type f
# /opt/app/bin/dev-helper.sh   (world-writable = root shell)

# Pin it down (visudo -f /etc/sudoers.d/deploy)
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app.service

Read rules as an attacker with a manual

The attacker does not try to break sudo; they read the grant carefully and use it exactly as written. Review every wildcard for what else it matches, every NOPASSWD for what automation it unlocks, and every script target for who can edit that script. The rule is not the intent; the rule is the behavior.

Verify and log after tightening

After pinning, run sudo -l again as the user and exercise each allowed command. Then confirm journalctl records the sudo events. A tightened rule that silently broke the deploy pipeline gets reverted under pressure at 2 a.m., which is worse than never tightening it.