Detection

How to detect privilege drift on Linux servers

Privilege escalation is an attack. Privilege drift is what makes it easy. They are different problems and only one of them has a CVE.

The short answer

Privilege drift is the gradual accumulation of permissions on a system: accounts that keep access after a project ends, users added to sudo or docker for one task and never removed, SSH keys that outlive the contractor who owned them, and setuid binaries dropped in by an install script. You detect it by baselining who has privilege today and then watching for changes: audit /etc/sudoers and /etc/sudoers.d, the members of root-equivalent groups, every setuid and setgid binary and file capability, and every authorized_keys file. The part that matters is the watching. A one-off audit tells you the state on one day, and drift is defined by what happens on the other days.

Drift is not escalation, and conflating them costs you

Privilege escalation is an attacker turning limited access into root, usually through a vulnerability, a misconfiguration or a stolen credential. It is an event, it has a moment, and it is what most tooling is built to catch.

Privilege drift has no moment. It is the sum of a hundred reasonable decisions: a developer added to the docker group to debug something in March, a contractor key added for a migration, a service account created with a login shell because that was faster, a setuid helper dropped in by a vendor installer. Every one made sense. Together they mean that on any given Tuesday, more people and more processes can become root than anyone believes.

The connection is that drift is what makes escalation cheap. An attacker who lands as a low-privilege user does not need a kernel exploit if a forgotten group membership already grants them what they want. Drift is the reason so many real intrusions never involve an exploit at all.

The four places privilege actually lives

On a Linux server, effective privilege is spread across four surfaces, and drift happens on all of them independently. Auditing one and calling it done is the common mistake.

  • sudoers: /etc/sudoers and every file in /etc/sudoers.d. Look for NOPASSWD, ALL=(ALL) grants, and entries granting a single command that happens to be a shell escape.
  • Group membership: root-equivalent groups are not just wheel and sudo. docker, lxd, adm, disk and shadow are all effectively root on most systems, because a member can reach root through the group's own capabilities.
  • setuid, setgid and file capabilities: any binary that runs with elevated privilege regardless of who invokes it. Vendor installers add these quietly.
  • SSH authorized_keys: every key in every user's authorized_keys, including root. Keys have no expiry and no owner recorded anywhere the system enforces.

The commands to audit each one

Run these on a server you have not looked at in a while and the result is usually uncomfortable.

  • sudo -l -U <user> shows what a specific user can actually run. Do it per account rather than reading sudoers and hoping you parsed the precedence correctly.
  • grep -rE "NOPASSWD|ALL=\(ALL" /etc/sudoers /etc/sudoers.d/ finds the grants worth questioning first.
  • for g in sudo wheel docker lxd adm disk shadow; do getent group $g; done lists membership of the groups that are root in practice.
  • find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -printf "%m %u %p\n" 2>/dev/null enumerates setuid and setgid binaries. Compare against a known-good list rather than eyeballing it.
  • getcap -r / 2>/dev/null finds file capabilities, which are the setuid bits people forget exist.
  • for f in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys; do echo "== $f"; ssh-keygen -lf "$f" 2>/dev/null; done fingerprints every key that can log in.
  • awk -F: "$3>=1000 && $7!~/nologin|false/" /etc/passwd lists human-usable accounts, which is usually longer than expected.

Why the audit alone does not fix it

You run those commands, you find four things, you clean them up. Three months later the server has drifted again, because the same reasonable pressures are still operating and nothing changed about how permissions get granted.

The audit answers "what is true now". Drift is a question about change: who gained privilege since the last time this was correct, and when. Answering that from a point-in-time audit means diffing it against a previous audit you hopefully kept, on a schedule you hopefully maintained.

That is also why drift survives compliance programmes that require an annual access review. An annual review catches drift a year late, and it catches it by asking humans to remember why a grant exists, which is precisely the information nobody has.

Detecting the change instead of the state

The alternative is to treat the privilege surface as a baseline and alert on the delta. Establish what exists once, then report every addition: a new account, a new member of a privileged group, a new setuid binary, a new SSH key, a change to sudoers.

This inverts the work. Instead of periodically reconstructing the whole picture and comparing it to memory, you get one line at the moment something changes, while the reason is still fresh and the person who did it is still reachable. Most of those lines will be legitimate, and that is fine: confirming a known change costs seconds, whereas reconstructing an unknown one costs an afternoon.

It also changes what an unexplained change means. If every legitimate grant produces an alert someone acknowledges, then an unacknowledged one is a genuine signal rather than something lost in a list of two hundred.

How SecAI handles this

SecAI baselines the privilege surface on each server and reports changes to it: new local user accounts, new members of privileged groups, new setuid or setgid binaries and file capabilities, new SSH keys, and modifications to sudoers, passwd, group and shadow.

Two details matter for whether this is usable. SSH keys are reported by SHA256 fingerprint, never by transmitting key material. And a setuid binary is only treated as expected when its current content is verified against its package record, because a file merely being owned by a package proves nothing if an attacker overwrote it.

The honest limit: SecAI can tell you a privilege grant appeared, on which server, and when. It cannot tell you whether it was authorised, because approval lives in your change process and not on the machine. That judgement stays yours, which is the right split.

Related

Questions people ask

See it on your own server

Install the SecAI agent with one command and watch it protect a Linux server in about 60 seconds. 14-day free trial, no credit card.