| |

LFCA 103 🐧 Principle of Least Privilege

The principle of least privilege (PoLP) is a foundational security concept stating that every user, program, and process should operate with the minimum set of permissions necessary to perform its intended function, and no more . It is a design principle rather than a specific tool, and it applies at every layer of a system: the kernel, the user space, applications, services, and network access. When properly applied, least privilege limits the blast radius of any compromise — a compromised process can only do what that process was already permitted to do, not what the attacker wishes it could do.

Least privilege is not a single control but a habit of design that runs through every decision about access. It determines whether an application runs as root or as a dedicated service account, whether a user can read every file on a system or only the files relevant to their role, whether a container has the full range of Linux capabilities or a restricted subset, and whether a database connection can drop tables or only insert rows. Each of these decisions is an application of the same principle.

This chapter covers the definition and rationale for least privilege, its application across users and groups, file permissions, process privileges, Linux capabilities, service accounts, sudo configuration, mandatory access control, and the operational practices that make least privilege sustainable rather than burdensome.

Key point: Least privilege means granting only the permissions required for a task, for only as long as required. It applies to users, processes, files, services, containers, and network access. The goal is to limit what a compromised account or process can do.


Why least privilege exists

The blast radius problem. When an account or process is compromised, the damage it can cause is bounded by its permissions. A web server running as root that is exploited through a remote code execution vulnerability gives the attacker full control of the host. The same vulnerability against a web server running as an unprivileged service account gives the attacker only the access that account already had . Least privilege does not prevent the compromise; it limits the consequences.

The privilege escalation problem. Attackers who gain initial access typically seek to escalate their privileges. They look for setuid binaries, misconfigured sudo rules, writable system files, or services running as root. A system designed with least privilege has fewer escalation paths because fewer privileged operations are available to an unprivileged attacker . Each unnecessary permission is a potential stepping stone.

The insider threat problem. Least privilege is not only about external attackers. An employee with broad access can accidentally or deliberately cause damage. A contractor with administrative rights on a production database can exfiltrate data or disrupt operations. Limiting permissions to what each role actually requires reduces the risk from both malicious and careless insiders .

The compliance problem. Least privilege is a requirement in multiple security frameworks. The Center for Internet Security (CIS) Controls and Benchmarks address access control as a fundamental practice. NIST SP 800-53 includes the principle of least privilege as a control (AC-6). PCI DSS, HIPAA, and ISO 27001 all include access control requirements that derive from least privilege . Compliance is not the reason to implement it, but it is a reason organizations cannot ignore it.

The operational simplicity problem. Broad permissions often feel simpler in the short term. Giving everyone sudo access avoids the friction of requesting elevated privileges. Running services as root avoids permission errors. But these shortcuts accumulate into a system where every account has more access than it needs, and the cost of that accumulated access is paid when something goes wrong. Least privilege requires more upfront design but reduces the operational cost of incidents.


a. Users, groups, and role-based access

The first layer of least privilege is user and group management. Every human who accesses a system should have a unique account, not a shared one, so that actions can be attributed and access can be revoked individually . Shared accounts — root used by multiple administrators, a generic admin account, or a service account used for interactive logins — violate least privilege because they obscure who did what and prevent fine-grained revocation.

Groups provide a mechanism for granting permissions to sets of users based on role. A developer who needs access to application logs but not to the database should be in a group that grants log access only. The principle is to define roles, create groups for those roles, and assign users to groups rather than granting permissions directly to individuals.

The usermod, groupadd, and gpasswd commands manage this structure. Regular auditing of group membership — who is in the sudo group, who has shell access, which accounts are inactive — keeps the access model aligned with current roles. Accounts for departed employees or contractors must be removed promptly; stale accounts are a common source of unauthorized access .

b. File permissions and ownership

Linux file permissions are the most granular form of access control at the filesystem level. Every file and directory has an owner, a group, and a set of permission bits for the owner, group, and others . The chmod command sets these bits, chown changes ownership, and chgrp changes group ownership.

The permissions model follows the principle of least privilege when files are owned by the appropriate user and group, and when the permission bits are the minimum required. A configuration file containing database credentials should be readable only by the service account that uses it — mode 600 (owner read/write) or 640 (owner read/write, group read). A web-accessible directory should be readable and executable by the web server, not writable unless the application requires it.

Common violations include world-writable files (777), directories writable by anyone, and files owned by root that are writable by non-root users. Each of these is a potential path for an attacker to modify system behavior. Auditing for these violations — using find to locate world-writable files, setuid binaries, and unowned files — is a standard hardening step .

c. Process privileges and service accounts

Processes inherit the privileges of the user who starts them. A process started by root runs with root’s full privileges. A process started by an unprivileged user runs with that user’s limited privileges. The decision of which user starts a service determines the upper bound of what that service can do if compromised.

Services should run as dedicated, unprivileged service accounts created specifically for that service. The www-data user on Debian and Ubuntu systems is a standard example: the web server runs as www-data, owns the files it needs to write, and has no ability to modify system binaries or other users’ files. Database servers, application servers, and other long-running processes should follow the same pattern — a dedicated account for each service, with ownership of only the files that service needs .

The useradd command with the --system flag creates a system account without a home directory or login shell, which is appropriate for services. The --shell /usr/sbin/nologin option prevents interactive login for accounts that should only run services .

d. Linux capabilities

Traditional Unix privilege is binary: a process is either root with all privileges or unprivileged with none. Linux capabilities break this binary into distinct units that can be granted individually . A process can be given only the ability to bind to a privileged port (less than 1024) without being given the ability to modify system files, load kernel modules, or change file ownership.

Capabilities relevant to services include CAP_NET_BIND_SERVICE (bind to ports below 1024), CAP_NET_RAW (use raw sockets, needed for ping and some network tools), CAP_SYS_ADMIN (a broad capability that should be avoided), and CAP_SETUID/CAP_SETGID (change process user/group IDs). The getcap and setcap commands inspect and set file capabilities. For example, giving a binary the ability to bind to a low port without running as root:

sudo setcap cap_net_bind_service=+ep /usr/bin/myservice

Container runtimes like Docker apply capabilities by default, dropping most of the root capability set and granting a limited subset. The --cap-drop and --cap-add flags control this per container, and --privileged disables all capability restrictions — a flag that should almost never be used .

e. sudo and privilege elevation

The sudo command allows a user to execute specific commands with elevated privileges without sharing the root password . The /etc/sudoers file defines who can run what. Proper configuration follows least privilege by granting only the commands each user needs, not full root access.

A common anti-pattern is user ALL=(ALL) ALL, which grants the user the ability to run any command as any user, including a shell as root. A more restrictive rule might allow a specific operator to restart a specific service:

operator ALL=(root) /usr/bin/systemctl restart nginx

This grants exactly the permission required — restarting nginx — and nothing else. If the operator’s account is compromised, the attacker can restart nginx but cannot read /etc/shadow, install a rootkit, or create new users.

The /etc/sudoers.d/ directory allows per-user or per-role sudo rules in separate files, which is cleaner than editing the monolithic sudoers file. Always use visudo to edit sudo rules, because it validates syntax before saving; a malformed sudoers file can lock out all administrative access .

f. Mandatory access control with SELinux and AppArmor

Standard Linux permissions are discretionary: the owner of a file decides who can access it. Mandatory access control (MAC) systems like SELinux and AppArmor add a layer that even the root user cannot bypass . SELinux labels every process and file with a security context, and policy defines which contexts can interact. A web server running under SELinux cannot read files labeled for the database, even if the Unix permissions would allow it .

SELinux has three modes: enforcing (policy violations are blocked and logged), permissive (violations are logged but allowed), and disabled. Production systems should run in enforcing mode. The getenforce command shows the current mode, and setenforce changes it temporarily. Permanent configuration is in /etc/selinux/config .

AppArmor is an alternative MAC system used by Ubuntu and SUSE. It profiles individual applications, defining what files and capabilities each program can access. Profiles can be in enforce or complain mode. The aa-status command shows loaded profiles and their modes .

Both systems provide defense in depth. Even if an attacker gains root through a vulnerability, MAC policy limits what that root access can do. Disabling SELinux or AppArmor to make an application work is a violation of least privilege; the correct fix is to adjust the policy to grant only the access the application needs .

g. Auditing and maintaining least privilege

Least privilege is not a one-time configuration but an ongoing practice. User roles change, services are added and removed, and permissions accumulate. Regular auditing identifies drift.

Useful audit commands include:

# Find world-writable files
find / -type f -perm -0002 -not -path "/proc/*" 2>/dev/null

# Find setuid and setgid binaries
find / -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null

# List users with shell access
grep -E "/(bash|sh|zsh)$" /etc/passwd

# List sudo-enabled users
getent group sudo

# Check for accounts with no password
sudo awk -F: '($2 == "") {print $1}' /etc/shadow

# List file capabilities
getcap -r / 2>/dev/null

Each command reveals a category of potential least-privilege violation. World-writable files can be modified by any user. Setuid binaries run with the privileges of their owner, typically root. Shell-accessible accounts can log in interactively. Sudo group members can elevate. Passwordless accounts can be logged into without credentials. File capabilities grant specific privileges to binaries. Reviewing these regularly keeps the system aligned with the principle.


Complete Example Session

# ============================================
# PART 1: CREATE A DEDICATED SERVICE ACCOUNT
# ============================================
# System account with no login shell.

sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp
# ============================================
# PART 2: SET FILE OWNERSHIP
# ============================================
# Service owns only the files it needs.

sudo chown -R myapp:myapp /opt/myapp
sudo chmod 750 /opt/myapp
# ============================================
# PART 3: CONFIGURE SUDO WITH LIMITED RULES
# ============================================
# Grant only specific commands.

sudo visudo -f /etc/sudoers.d/operator
# operator ALL=(root) /usr/bin/systemctl restart myapp
# ============================================
# PART 4: CHECK FOR WORLD-WRITABLE FILES
# ============================================
# These are potential privilege escalation paths.

find / -type f -perm -0002 -not -path "/proc/*" 2>/dev/null
# ============================================
# PART 5: FIND SETUID BINARIES
# ============================================
# Setuid binaries run with owner's privileges.

find / -type f -perm -4000 2>/dev/null
# Review each; remove setuid from binaries that don't need it
# ============================================
# PART 6: SET FILE CAPABILITIES
# ============================================
# Grant only the ability to bind a low port.

sudo setcap cap_net_bind_service=+ep /usr/bin/myapp
getcap /usr/bin/myapp
# ============================================
# PART 7: VERIFY SELINUX STATUS
# ============================================
# Check enforcement mode.

getenforce
sestatus
# ============================================
# PART 8: CHECK APPARMOR PROFILES
# ============================================
# List loaded profiles and modes.

sudo aa-status
# ============================================
# PART 9: AUDIT ACCOUNTS WITH SHELL ACCESS
# ============================================
# Identify accounts that can log in interactively.

grep -E "/(bash|sh|zsh)$" /etc/passwd
sudo getent group sudo
# ============================================
# PART 10: CONFIGURE DOCKER CAPABILITIES
# ============================================
# Drop all capabilities, add only what is needed.

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myimage
# Never use --privileged

These ten parts cover the practical application of least privilege: creating service accounts, setting ownership, configuring sudo, auditing for violations, applying capabilities, checking MAC systems, and configuring containers. Each step reduces the surface area available to a compromised account or process.


Quick Reference

Least Privilege by Layer

LayerApplication
UsersUnique accounts; no shared logins
GroupsRole-based; no direct individual grants
FilesMinimum permissions; correct ownership
ProcessesUnprivileged service accounts
CapabilitiesGrant specific privileges only
SudoSpecific commands, not full root
MACSELinux or AppArmor in enforcing mode
ContainersDrop all capabilities, add only needed

Key Commands

CommandPurpose
useradd --systemCreate system service account
chmod 750Owner rwx, group rx, others none
chown user:groupSet ownership
visudo -f /etc/sudoers.d/Edit sudo rules safely
setcap cap_x=+epGrant file capability
getcap -r /List all file capabilities
getenforceCheck SELinux mode
aa-statusCheck AppArmor profiles

Audit Commands

CommandFinds
find / -perm -0002World-writable files
find / -perm -4000Setuid binaries
grep -E "/(bash|sh)$" /etc/passwdShell-accessible accounts
getent group sudoSudo-enabled users
awk -F: '($2=="")' /etc/shadowPasswordless accounts

Best Practices

✅ Do This:

sudo useradd --system --shell /usr/sbin/nologin myapp    # Dedicated service account
sudo chmod 640 /etc/myapp/config                          # Minimum file permissions
# /etc/sudoers.d/operator
# operator ALL=(root) /usr/bin/systemctl restart myapp    # Specific sudo rule
sudo setcap cap_net_bind_service=+ep /usr/bin/myapp      # Minimal capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE      # Restricted container

❌ Don’t Do This:

chmod 777 /var/www                                        # ❌ World-writable
useradd shared_admin                                      # ❌ Shared account
# user ALL=(ALL) ALL                                      # ❌ Full root sudo
docker run --privileged                                   # ❌ All capabilities
setenforce 0                                              # ❌ Disables SELinux

Common Pitfalls

PitfallWhy It HappensFix
Service running as rootDefault configurationCreate dedicated service account
World-writable filesQuick fix for permission errorsFix ownership and group instead
Full sudo accessConvenienceGrant specific commands only
SELinux disabledApplication errorsAdjust policy, not enforcement mode
Shared admin accountsSimplicityUnique accounts; revoke individually
Stale accountsDeparted employeesRegular access reviews
--privileged containersContainer won’t start otherwiseDiagnose missing capability; add only that

Real-World Examples

1. Web Server as www-data

sudo chown -R www-data:www-data /var/www/html
sudo chmod 755 /var/www/html

2. Restart-Only Sudo Rule

operator ALL=(root) /usr/bin/systemctl restart nginx

3. Bind Low Port Without Root

sudo setcap cap_net_bind_service=+ep /usr/bin/myapp

4. System Account for Database

sudo useradd --system --shell /usr/sbin/nologin postgres

5. SELinux Enforcing

sudo setenforce 1
sudo sed -i 's/SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config

6. AppArmor Complain Mode for Debugging

sudo aa-complain /etc/apparmor.d/usr.bin.myapp

7. Container Capability Drop

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE --user 1000:1000 myimage

8. Audit Setuid Binaries

find / -perm -4000 -type f 2>/dev/null | while read f; do ls -l "$f"; done

9. Check Sudo Rules

sudo -l -U username

10. Remove Stale User

sudo userdel -r olduser

Visual

Least Privilege Layers

┌──────────────────────────────────────────────────────────────┐
│  LEAST PRIVILEGE AT EVERY LAYER                              │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  USER LAYER                                            │  │
│  │  Unique accounts, role-based groups, no shared logins  │  │
│  └────────────────────────────────────────────────────────┘  │
│                          │                                   │
│                          ▼                                   │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  FILE LAYER                                            │  │
│  │  Minimum permissions, correct ownership                │  │
│  └────────────────────────────────────────────────────────┘  │
│                          │                                   │
│                          ▼                                   │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  PROCESS LAYER                                         │  │
│  │  Dedicated service accounts, no root                   │  │
│  └────────────────────────────────────────────────────────┘  │
│                          │                                   │
│                          ▼                                   │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  CAPABILITY LAYER                                      │  │
│  │  Specific capabilities, not full root                  │  │
│  └────────────────────────────────────────────────────────┘  │
│                          │                                   │
│                          ▼                                   │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  MAC LAYER (SELinux/AppArmor)                          │  │
│  │  Policy even root cannot bypass                        │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

Blast Radius Comparison

┌──────────────────────────────────────────────────────────────┐
│  COMPROMISE IMPACT: ROOT vs SERVICE ACCOUNT                  │
│                                                              │
│  WEB SERVER AS ROOT:                                         │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  Attacker gains:                                       │  │
│  │  - Read/write any file                                 │  │
│  │  - Create/delete users                                 │  │
│  │  - Modify system binaries                              │  │
│  │  - Install rootkit                                     │  │
│  │  - Access all network resources                        │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  WEB SERVER AS www-data:                                     │
│  ┌────────────────────────────────────────────────────────┐  │
│  │  Attacker gains:                                       │  │
│  │  - Read/write web files                                │  │
│  │  - Cannot modify system binaries                       │  │
│  │  - Cannot create users                                 │  │
│  │  - Cannot read /etc/shadow                             │  │
│  │  - Limited to www-data's permissions                   │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

Sudo Configuration Spectrum

┌──────────────────────────────────────────────────────────────┐
│  SUDO RULES: FROM BROAD TO MINIMAL                           │
│                                                              │
│  MOST PERMISSIVE (avoid):                                    │
│  user ALL=(ALL) ALL                                          │
│  └── Full root, any command                                  │
│                                                              │
│  LESS PERMISSIVE:                                            │
│  user ALL=(root) /usr/bin/systemctl                          │
│  └── Full systemctl control                                  │
│                                                              │
│  MINIMAL (preferred):                                        │
│  user ALL=(root) /usr/bin/systemctl restart nginx            │
│  └── Only restart nginx                                      │
│                                                              │
│  MOST RESTRICTIVE:                                           │
│  user ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx  │
│  └── Only restart nginx, no password prompt                  │
└──────────────────────────────────────────────────────────────┘

Container Capability Defaults

┌──────────────────────────────────────────────────────────────┐
│  DOCKER CAPABILITY BEHAVIOR                                  │
│                                                              │
│  DEFAULT (docker run):                                       │
│  └── Drops most capabilities, keeps a limited set            │
│      including CHOWN, DAC_OVERRIDE, SETUID, SETGID,          │
│      NET_BIND_SERVICE, and others                            │
│                                                              │
│  --cap-drop=ALL --cap-add=X:                                 │
│  └── Drops everything, adds only X                           │
│      Most restrictive; recommended for production            │
│                                                              │
│  --privileged:                                               │
│  └── Grants ALL capabilities                                 │
│      Disables all restrictions                               │
│      Almost never appropriate                                │
│                                                              │
│  Best practice:                                              │
│  docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE \      │
│    --user 1000:1000 myimage                                  │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Least privilege definitionMinimum permissions required for a task
Applies toUsers, groups, files, processes, capabilities, containers
User accountsUnique, no shared logins
Service accountsDedicated, unprivileged, no login shell
File permissionsMinimum bits; correct owner and group
SudoSpecific commands, not full root
Linux capabilitiesFine-grained privileges instead of root
MACSELinux or AppArmor in enforcing mode
ContainersDrop all capabilities, add only needed
AuditingRegular review of permissions and accounts

Key takeaways:

  • Least privilege limits blast radius. A compromised account or process can only do what it was already permitted to do; unnecessary permissions are unnecessary risk .
  • Every account should be unique. Shared accounts prevent accountability and fine-grained revocation.
  • Services should run as dedicated unprivileged accounts. The www-data pattern applies to every long-running service .
  • Sudo rules should be specific. Granting ALL=(ALL) ALL is equivalent to giving root; grant only the commands required .
  • Capabilities break the root binary. A process can bind a low port without having full root privileges .
  • MAC systems add defense in depth. SELinux and AppArmor enforce policy that even root cannot bypass; disabling them to fix application errors is the wrong solution .
  • Auditing is ongoing. World-writable files, setuid binaries, stale accounts, and passwordless accounts accumulate over time; regular review keeps the system aligned with the principle .
  • Containers should drop all capabilities by default. Add back only what the container requires; never use --privileged .

Remember: The principle of least privilege is not a single tool but a design habit that applies at every layer of a system. It requires asking, for every access decision, whether this permission is actually needed for this task. The answer is often no, and the discipline of denying unnecessary access reduces the impact of every compromise. Start with user accounts and file permissions, extend to service accounts and capabilities, enforce with sudo rules and MAC policy, and audit regularly to catch drift. The goal is not to make systems unusable but to make them resilient — a system where no single compromised account can bring down the whole.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!