Safeguard
Vulnerability Analysis

sudo -e/sudoedit privilege escalation bypass (CVE-2019-14287)

CVE-2019-14287 let sudo users bypass "run as any user except root" rules via `sudo -u#-1`, gaining full root. Here's how it worked and how to fix it.

Safeguard Research Team
Research
8 min read

On October 14, 2019, sudo maintainer Todd C. Miller shipped version 1.8.28 to fix a privilege escalation flaw that let a restricted sudo user run commands as root even when the sudoers policy explicitly forbade it. Tracked as CVE-2019-14287 and reported by Joe Vennix of Apple Information Security, the bug lives in how sudo resolves user IDs supplied on the command line with the -u flag. Any sudoers configuration that grants a user the ability to run a command "as any user except root" — a pattern administrators reach for constantly when they want to hand off delegated access without handing over the keys to root — could be defeated with a single, trivially memorable command: sudo -u#-1 id -u. The output would read 0. Root, when the policy said "never root."

This is not an exotic memory-corruption bug requiring a fuzzer and a week of debugging. It is a logic flaw that turns a well-intentioned least-privilege control into a false sense of security, and it is exploitable by anyone who already has the specific kind of sudo access that many organizations assume is safe.

Affected Versions and Components

CVE-2019-14287 affects all sudo versions prior to 1.8.28. That is a long tail — the vulnerable code path around user-ID resolution had existed for years, and the flaw wasn't specific to a recent regression. The fix landed in sudo 1.8.28, released October 14, 2019, and every major Linux distribution followed within days with backported patches: Debian, Ubuntu, Red Hat/CentOS, SUSE, and Amazon Linux all issued advisories and updated packages in mid-to-late October 2019.

Exploitability hinges on a specific, but common, sudoers configuration pattern. The vulnerable setup uses the ALL keyword in the Runas specification combined with a negation to exclude root, for example:

alice ALL=(ALL, !root) /usr/bin/less

The intent is clear: alice can run less as any user on the system except root. That negation is exactly what breaks. By invoking sudo with a numeric user ID of -1 or its unsigned 32-bit equivalent, 4294967295, a user matching this pattern could bypass the !root restriction entirely.

The mechanism: when sudo resolves the requested target ID via -u#-1, there is no passwd entry for that ID, so sudo's policy check cannot match it against the literal !root (UID 0) exclusion in the Runas list — the negation was written as a name-based comparison, not a numeric one, and -1 doesn't resolve to a name. Having passed the policy check, sudo proceeds to set the process credentials. Because -1 is treated by setresuid()/setreuid() as a sentinel meaning "leave this ID unchanged," and sudo's process still holds an effective UID of 0 at that point in execution (sudo itself runs setuid-root before it drops privileges to the target user), the command executes with root's effective UID instead of being denied or executed as an unprivileged, unmapped ID. The net effect: a control designed to say "anyone but root" is silently rewritten to "root, no questions asked."

Any organization using Runas negation lists — a pattern common in shared administrative environments, CI runners with scoped sudo grants, and multi-tenant jump hosts — was exposed regardless of what specific commands were being delegated, because the bypass operates below the command layer, at credential resolution.

CVSS, EPSS, and KEV Context

NVD scores CVE-2019-14287 at 7.8 (High) under CVSS v3.x, with the vector AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — local attack vector, low complexity, low privileges required, no user interaction, and high impact across confidentiality, integrity, and availability. That combination is worth sitting with: the "low privileges required" is what makes this vulnerability distinctive. An attacker doesn't need to already be root or exploit a separate bug to get code execution — they need only the kind of scoped, "safe-looking" sudo grant that plenty of teams issue to junior engineers, service accounts, and support staff precisely because it isn't supposed to reach root.

CVE-2019-14287 is not currently listed in CISA's Known Exploited Vulnerabilities (KEV) catalog. That absence shouldn't be read as "low risk" — KEV listing reflects confirmed in-the-wild exploitation evidence, not exploit availability or triviality. Given that working proof-of-concept commands were public within days of disclosure and the exploit is a single CLI invocation with no tooling required, the practical barrier to exploitation by an insider or an attacker who has already gained a foothold with limited sudo rights is effectively zero. EPSS scores for CVEs from 2019 in this vulnerability class have generally settled at low-to-moderate percentile values over time as exploitation activity failed to spike broadly — but EPSS is a probability-of-mass-exploitation signal, not a statement about risk in your specific environment. For a local privilege escalation bug gating access to root on systems you already run sudo on, the relevant question isn't population-level exploitation probability; it's whether your own sudoers files contain the vulnerable pattern. Teams should pull current EPSS and KEV status from live feeds before finalizing a risk rating, but should not let a low EPSS score talk them out of patching a straightforward root escalation.

Timeline

  • Pre-2019: The flawed user-ID resolution logic exists in sudo across multiple release lines, unnoticed.
  • September 2019: Joe Vennix of Apple Information Security discovers and reports the issue to sudo maintainer Todd C. Miller.
  • October 14, 2019: Sudo 1.8.28 is released, fixing the vulnerability by rejecting sudo invocations where the target user does not exist in the password database instead of silently proceeding.
  • October 15, 2019: CVE-2019-14287 is publicly disclosed and assigned.
  • October 15–21, 2019: Debian, Ubuntu, Red Hat, SUSE, and other major distributions publish advisories and ship patched packages.
  • Ongoing: Sudo remains one of the most widely deployed privilege-management tools on Linux and Unix-like systems, meaning unpatched instances of pre-1.8.28 sudo persist for years afterward on systems that lag on OS updates — legacy build agents, embedded Linux images, and long-lived container base images being the usual suspects.

Remediation

  1. Patch sudo to 1.8.28 or later. This is the definitive fix. On most distributions, this means updating the sudo package through your standard package manager (apt, yum/dnf, zypper) and confirming the installed version with sudo --version. Distribution-maintained backports (e.g., Debian's 1.8.27-1+deb10u1 or later) also resolve the issue without requiring an upstream version bump.

  2. Audit every sudoers file for Runas negation patterns. Grep for ALL, !root and similar constructs across /etc/sudoers and /etc/sudoers.d/* on every host, not just the ones you remember configuring this way:

    grep -rE '\(ALL,\s*!' /etc/sudoers /etc/sudoers.d/
    

    Even after patching, treat any negation-based Runas rule as a design smell.

  3. Replace negation lists with explicit allow-lists. Instead of "any user except root," enumerate exactly which target users are permitted:

    alice ALL=(appuser, www-data) /usr/bin/less
    

    Positive enumeration fails closed; negation-based exclusion fails open the moment an edge case like this one appears.

  4. Validate configuration changes with visudo -c and review effective grants with sudo -l for every account holding a Runas specification, both before and after remediation.

  5. Inventory sudo versions fleet-wide. Pre-1.8.28 sudo binaries often linger in golden images, container base layers, and infrequently patched build infrastructure long after the primary fleet is updated. A one-time patch cycle doesn't guarantee the fix persists across every image rebuild.

  6. Add sudoers negation patterns to your configuration-drift and IaC scanning rules so a reintroduced !root clause in a future pull request gets flagged before merge rather than discovered during an incident.

How Safeguard Helps

Safeguard's SBOM generation and ingest pipeline flags every host and container image still shipping a pre-1.8.28 sudo package, giving you a fleet-wide view of exposure instead of a host-by-host guessing game. Reachability analysis then narrows that list to the systems where the vulnerable Runas negation pattern actually exists in a live sudoers configuration, so remediation effort goes to genuinely exploitable misconfigurations rather than every machine that happens to have sudo installed. Griffin AI correlates the sudo package version, the sudoers policy content, and each host's exposure context to separate "patch this today" from "low-priority backlog" with the nuance a raw CVSS score can't provide. Where the fix is a straightforward package bump, Safeguard's auto-fix PRs open the version update directly against your base image or configuration-management repo, cutting the time between disclosure-driven backlog and merged remediation from weeks to hours. Together, this turns a vulnerability that hides in configuration intent — not just software version — into something your team can find, prioritize, and close without manually auditing every sudoers file by hand.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.