A Linux server can have an up-to-date kernel, hardened SSH, a firewall, EDR, and a sensible patching policy and still provide a straightforward path to privilege escalation. The problem may be something as ordinary as a forgotten SUID binary, a script writable by the wrong user, an inherited ACL, or access to the Docker socket. Permissions remain one of the attack surfaces administrators should audit regularly.
The key Linux permission risks in 20 seconds
- SUID, SGID, and Linux capabilities can grant privileges that are easy to miss during a superficial review.
- Cron, systemd, and scripts executed by root become dangerous when unprivileged users can modify them.
- ACLs may grant access that is not immediately visible through the traditional permission bits shown by
ls -l. /var/run/docker.sockand membership in certain groups should effectively be treated as administrative access.- Maintaining baselines makes it easier to detect permission changes after updates, software installations, or security incidents.
The difficult part is not understanding rwx. It is discovering permission chains. A seemingly irrelevant file may be writable by an ordinary user and then executed five minutes later by a service running with UID 0. A library may sit inside a writable directory. An ACL may provide access even though the standard chmod permissions look correct.
For a sysadmin, the useful question is therefore not simply what permissions a file has. It also matters who controls each component of the path and under which identity that resource will eventually be used or executed.
1. SUID and SGID: Start With the Obvious Privileges
SUID (Set User ID) and SGID (Set Group ID) allow certain binaries to execute with privileges associated with their owner or group.
A basic inventory should be part of any Linux security audit:
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f \
-exec ls -la {} \; 2>/dev/nullCode language: JavaScript (javascript)
The output alone does not identify vulnerabilities. What matters is comparing it with the server’s known-good state:
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f \
2>/dev/null | sort > suid-baseline.txtCode language: JavaScript (javascript)
After an update:
diff suid-baseline.txt \
<(find / -xdev \( -perm -4000 -o -perm -2000 \) \
-type f 2>/dev/null | sort)Code language: JavaScript (javascript)
A new SUID binary does not automatically indicate compromise, but administrators should be able to associate it with a package and explain why it requires elevated privileges.
2. World-Writable Files: Find Where Anyone Can Write
Permissions such as 777 are easy to spot. More interesting is determining whether an unprivileged account can modify something that will later be consumed or executed by root.
For files:
find / -xdev -type f -perm -002 2>/dev/nullCode language: JavaScript (javascript)
For world-writable directories without the sticky bit:
find / -xdev -type d -perm -002 ! -perm -1000 2>/dev/nullCode language: JavaScript (javascript)
A /tmp directory configured as 1777 is normal. /opt/company/scripts with equivalent permissions deserves much closer attention if administrative automation executes scripts from that location.
Administrators should not automatically fix every result with chmod. The service or application using each path should be understood first.
3. /etc/shadow, Credentials, and Application Secrets
Checking the permissions of /etc/passwd, /etc/shadow, /etc/group, and /etc/gshadow provides a useful basic control:
stat -c '%A %a %U:%G %n' \
/etc/passwd /etc/shadow /etc/group /etc/gshadowCode language: JavaScript (javascript)
The correct configuration varies between Linux distributions. /etc/shadow, for example, may legitimately use 640 root:shadow or another restrictive configuration.
The same audit should extend to less obvious secrets:
.env
config.yml
database.ini
credentials.json
*.pem
*.keyCode language: CSS (css)
A server may protect /etc/shadow perfectly while leaving a PostgreSQL password or API key world-readable under /opt/app.
4. SSH: Protecting id_ed25519 Is Not Enough
An SSH review should include the .ssh directory, private keys, and authorized_keys.
A typical configuration is:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
Ownership matters just as much as the mode:
stat -c '%A %a %U:%G %n' \
~/.ssh ~/.ssh/authorized_keys ~/.ssh/id_ed25519Code language: JavaScript (javascript)
An authorized_keys file writable by another account allows that account to alter which keys can authenticate as the user.
Correct permissions also do not solve another common issue: obsolete credentials. A key with perfect permissions remains a security problem if it belongs to a contractor, former employee, automation system, or machine that no longer requires access.
5. Sudoers: When Least Privilege Quietly Becomes Root
sudo rules deserve particular attention because an apparently restricted configuration can sometimes provide a route to a privileged shell.
The obvious example is:
user ALL=(ALL) NOPASSWD: ALL
But administrators should also examine rules that permit interpreters, editors, or commands with overly flexible arguments.
Start by validating the configuration:
sudo visudo -c
Then perform an initial search for potentially broad rules:
grep -rP '(NOPASSWD|ALL)' \
/etc/sudoers /etc/sudoers.d/ 2>/dev/nullCode language: JavaScript (javascript)
Not every match is insecure. The administrator needs to determine what the user can actually achieve with each permitted command, rather than judging the rule only by the command’s name.
6. Cron and systemd: Root Executing User-Controlled Code
This pattern deserves a dedicated review because it can create a direct privilege-escalation path.
For cron:
crontab -l -u root
cat /etc/crontab /etc/cron.d/* 2>/dev/null
For systemd timers:
systemctl list-timers --allCode language: PHP (php)
And to locate unit files writable by group or others:
find /etc/systemd/system /usr/lib/systemd/system \
-type f -name '*.service' -perm -022 2>/dev/nullCode language: JavaScript (javascript)
The audit should not stop at the .service file.
If it contains:
ExecStart=/opt/company/bin/backup.shCode language: JavaScript (javascript)
administrators should inspect backup.sh, its owner, and the permissions of its parent directories.
A protected systemd unit can still be dangerous if it eventually executes a script that an ordinary user can modify.
7. Linux Capabilities: Privileges That ls -l Does Not Show
SUID is no longer the only way to give an executable privileged capabilities.
Linux capabilities divide traditional root privileges into more specific units. An inventory can be generated with:
getcap -r / 2>/dev/nullCode language: JavaScript (javascript)
Capabilities that deserve particularly careful review include cap_setuid, cap_dac_override, cap_sys_ptrace, cap_net_admin, and the very broad cap_sys_admin.
A baseline is useful here too:
getcap -r / 2>/dev/null | sort > capabilities-baseline.txtCode language: JavaScript (javascript)
The question should not simply be whether a capability is powerful, but why that particular executable needs it.
8. Docker Socket and Indirectly Privileged Groups
In conventional Docker installations, granting access to:
/var/run/docker.sockCode language: JavaScript (javascript)
should be treated as a significant security decision.
Check the socket and group membership with:
ls -l /var/run/docker.sock
getent group dockerCode language: JavaScript (javascript)
The risk does not end with interactive users. Administrators should also determine which containers have the socket mounted.
An application compromised inside a container that can control the Docker daemon may be able to use the daemon’s privileges to create containers with access to sensitive host resources.
Membership in the docker group should therefore not be regarded as a minor operational permission. In many deployments, it provides a path to a level of control effectively equivalent to root.
9. ACLs: The Permission Hidden Behind the +
This is one of the controls that an audit based exclusively on ls -l can easily overlook.
Linux supports Access Control Lists (ACLs) that grant additional permissions to specific users and groups.
A file may appear as:
-rw-r-----+ 1 root root 2048 Sep 4 10:20 secrets.confCode language: CSS (css)
That + deserves attention.
The actual ACL can be inspected with:
getfacl /path/to/secrets.conf
For example:
user::rw-
user:developer:r--
group::---
mask::r--
other::---Code language: CSS (css)
Although the traditional permission bits look restrictive, developer has read access.
Depending on the tools available on the system, extended ACLs can be searched with:
getfacl -R -s /etc /opt /srv 2>/dev/nullCode language: JavaScript (javascript)
ACLs are useful for managing complex access requirements. Problems arise when they survive team changes, migrations, and old projects and nobody remembers they exist.
Default ACLs on directories also deserve attention because they can automatically propagate permissions to newly created files.
10. Ownership and Permissions Across the Entire Path
The tenth risk is less obvious and particularly useful during a security review.
A script may have:
-rwxr-xr-x root root /opt/company/bin/backup.sh
At first glance, that looks correct.
But the entire path should be examined:
namei -l /opt/company/bin/backup.sh
If /opt/company/bin or one of its parent directories can be modified by an unprivileged user, protecting the final file may not be enough.
This type of review is particularly important for:
- scripts executed through cron;
- systemd
ExecStartcommands; - internal binaries;
- tools permitted through
sudo; - directories included in privileged users’
$PATH.
It is also worth inspecting root’s $PATH:
sudo sh -c 'printf "%s\n" "$PATH"'Code language: JavaScript (javascript)
A directory writable by unprivileged users inside a search path used by root can create opportunities for command hijacking if a script or process invokes executables without absolute paths.
From a Permission Checklist to a Detection Policy
For a system administrator, checking these ten categories once has limited value. The real benefit comes when permission auditing becomes change detection.
SUID/SGID binaries, capabilities, privileged group membership, sensitive ACLs, and selected ownership information can all be stored as known-good references.
After an important update or software installation, administrators should be able to answer questions such as:
Which new SUID binaries appeared?
Which capabilities changed?
Who was added to the docker group?
Which file under /etc has a new owner?
Which new service is running as root?Code language: JavaScript (javascript)
It is also worth remembering that -xdev restricts find to the initial filesystem. This is useful for avoiding NFS shares, mounted volumes, pseudo-filesystems, or very large datasets, but it also means those filesystems are not being audited.
If /home, /var, /opt, or application data live on separate filesystems, they need to be checked explicitly.
SELinux and AppArmor do not replace these controls either. They can restrict what a compromised process is allowed to do, but Unix permissions, ACLs, capabilities, ownership, and privileged groups remain part of the system’s access-control decisions.
A Linux security audit should focus above all on unexpected relationships: an unprivileged user who can modify a resource and a more privileged process that later trusts, loads, or executes it.
That is where an apparently boring chmod, a forgotten ACL, or membership in the wrong group can stop being a routine administration issue and become a privilege-escalation path.
Frequently Asked Questions
Which Linux permissions should a system administrator audit first?
SUID/SGID binaries, world-writable files, sudoers rules, tasks executed as root, Linux capabilities, and indirectly privileged groups provide a good starting point. The audit should then expand to ACLs, ownership, and execution paths.
Why doesn’t ls -l show all effective Linux permissions?
Because Linux can use extended ACLs in addition to traditional owner, group, and other permission bits. getfacl can reveal additional access granted to specific users or groups.
Is membership in the docker group equivalent to root?
In a conventional Docker installation, it should be treated as root-equivalent or close to it. Control over the Docker daemon can allow operations that provide access to sensitive resources on the host.
How often should Linux server permissions be audited?
The appropriate frequency depends on the environment, but audits are particularly useful after updates, software installations, deployments, and administrative changes. Critical systems can also automate periodic comparisons against a trusted baseline.
