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
| Layer | Application |
|---|---|
| Users | Unique accounts; no shared logins |
| Groups | Role-based; no direct individual grants |
| Files | Minimum permissions; correct ownership |
| Processes | Unprivileged service accounts |
| Capabilities | Grant specific privileges only |
| Sudo | Specific commands, not full root |
| MAC | SELinux or AppArmor in enforcing mode |
| Containers | Drop all capabilities, add only needed |
Key Commands
| Command | Purpose |
|---|---|
useradd --system | Create system service account |
chmod 750 | Owner rwx, group rx, others none |
chown user:group | Set ownership |
visudo -f /etc/sudoers.d/ | Edit sudo rules safely |
setcap cap_x=+ep | Grant file capability |
getcap -r / | List all file capabilities |
getenforce | Check SELinux mode |
aa-status | Check AppArmor profiles |
Audit Commands
| Command | Finds |
|---|---|
find / -perm -0002 | World-writable files |
find / -perm -4000 | Setuid binaries |
grep -E "/(bash|sh)$" /etc/passwd | Shell-accessible accounts |
getent group sudo | Sudo-enabled users |
awk -F: '($2=="")' /etc/shadow | Passwordless 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Service running as root | Default configuration | Create dedicated service account |
| World-writable files | Quick fix for permission errors | Fix ownership and group instead |
| Full sudo access | Convenience | Grant specific commands only |
| SELinux disabled | Application errors | Adjust policy, not enforcement mode |
| Shared admin accounts | Simplicity | Unique accounts; revoke individually |
| Stale accounts | Departed employees | Regular access reviews |
--privileged containers | Container won’t start otherwise | Diagnose 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
| Item | Value |
|---|---|
| Least privilege definition | Minimum permissions required for a task |
| Applies to | Users, groups, files, processes, capabilities, containers |
| User accounts | Unique, no shared logins |
| Service accounts | Dedicated, unprivileged, no login shell |
| File permissions | Minimum bits; correct owner and group |
| Sudo | Specific commands, not full root |
| Linux capabilities | Fine-grained privileges instead of root |
| MAC | SELinux or AppArmor in enforcing mode |
| Containers | Drop all capabilities, add only needed |
| Auditing | Regular 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-datapattern applies to every long-running service . - Sudo rules should be specific. Granting
ALL=(ALL) ALLis 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!