Home › The Stash

Linux for Defenders 07: Audit File Permissions — SUID, World-Writable, ACLs and AIDE

LabOctober 20, 2026

IntermediateTestedTime ~40 minTool find, setfacl, AIDENeeds a Linux VM you ownCost freeExam Linux+ · Security+ · CySA+

0 / 7 steps

The goal

By the end you will have audited the file permissions that matter most on a Linux VM: the handful of programs that run with extra rights, anything any user can write to, and folders shared through access control lists. You will plant one of the most common real misconfigurations, catch it and fix it, then take a baseline of the whole system with AIDE and watch it report exactly which files changed afterwards.

Your own VM, with a snapshot. You will create a deliberately unsafe file and change a system file, then put both right. Do it on a VM you own, and snapshot first. You need two ordinary test users; the sudo lab made alex and sam.

Why this lab. The last lab trusted the package manager’s memory of what each file should be. This one looks at the permissions on files instead, because a perfectly genuine file with the wrong permissions is just as dangerous as a tampered one. Then it gives you a record of the whole system that lives outside the package manager.

Steven’s fix for “permission denied” is chmod 777. Every time. Step 04 is about him. — Nyx

Before you start. You need an Ubuntu or Debian VM and a terminal. Install the two tools used at the end with sudo apt install acl aide.

Steps

01List the programs that run with extra rights

A few programs carry a special permission bit, SUID, that makes them run with their owner’s rights instead of yours. List them.

$ sudo find / -xdev -perm -4000 -type f 2>/dev/null | sort
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/su
/usr/bin/sudo
/usr/bin/umount
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign

Eleven on a stock Ubuntu 24.04 server. They are owned by root, so they run as root. That is on purpose: passwd has to write your new password into /etc/shadow, a file you cannot write to yourself. -xdev keeps find on the main disk, and changing -4000 to -2000 lists the similar SGID programs, which run with their group’s rights.

Why this matters. Every program on this list is trusted to act as root for an ordinary user, so the list should be short and should not change. Save it. A new name on it later, or one in a strange folder, is a finding.

02Check every one came from a package

Use what you learned in the last lab: ask the package manager who owns each SUID and SGID program, and print only the ones nobody owns.

$ for f in $(sudo find / -xdev -perm /6000 -type f 2>/dev/null); do dpkg -S "$f" >/dev/null 2>&1 || echo "NOT FROM A PACKAGE: $f"; done
$ 

No output is the result you want: every program with extra rights was installed by a package (-perm /6000 means SUID or SGID). Run dpkg -S on any one of them to see the owner — passwd, sudo, mount and so on.

The defender’s move. A program with extra rights that no package owns did not come through apt, and it is one of the first things incident responders look for. Combine this loop with dpkg --verify from the last lab and you know both that each program is expected and that it is unmodified.

03Find anything every user can write to

Look for files anyone can change, and folders anyone can write into without the protection /tmp has.

$ sudo find / -xdev -type f -perm -0002 2>/dev/null
$ sudo find / -xdev -type d -perm -0002 ! -perm -1000 2>/dev/null
$ ls -ld /tmp
drwxrwxrwt 1 root root 4096 Oct 20 19:22 /tmp

Both searches came back empty on a clean install. The first looks for world-writable files. The second looks for world-writable folders without the sticky bit — the t at the end of drwxrwxrwt on /tmp. The sticky bit lets anyone create files in /tmp but only lets you delete or rename your own, which is why it is safe as a shared space.

Why this matters. On a healthy system these lists are empty or very short. When they are not, it is almost always someone fixing a permissions problem the quick way — which the next step shows.

04Plant the classic mistake

Play the hurried admin. Create a nightly backup script that root runs from cron, and “fix” a permissions error with chmod 777.

$ printf '#!/bin/sh\ntar czf /var/backups/etc.tgz /etc 2>/dev/null\n' | sudo tee /usr/local/bin/nightly-backup.sh >/dev/null
$ sudo chmod 777 /usr/local/bin/nightly-backup.sh
$ echo '30 2 * * * root /usr/local/bin/nightly-backup.sh' | sudo tee /etc/cron.d/nightly-backup >/dev/null
$ ls -l /usr/local/bin/nightly-backup.sh
-rwxrwxrwx 1 root root 56 Oct 20 19:19 /usr/local/bin/nightly-backup.sh

The script is harmless and it works. The problem is rwxrwxrwx: the last three letters mean every user on the machine can rewrite it, and root runs whatever it says at 2:30 every morning.

The attacker’s view. A file that root runs but anyone can edit hands root to whoever edits it next. It needs no exploit and no password, which is why it is among the first things a penetration tester checks. It maps to MITRE ATT&CK T1053.003, the scheduled-job technique from the cron lab.

05Catch it and fix it

Run the step 03 search again, check who runs the file, prove an ordinary user can change it, then fix the permissions.

$ sudo find / -xdev -type f -perm -0002 2>/dev/null
/usr/local/bin/nightly-backup.sh
$ sudo grep -rl nightly-backup /etc/cron.d/
/etc/cron.d/nightly-backup
$ sudo -u sam test -w /usr/local/bin/nightly-backup.sh && echo "sam can edit it"
sam can edit it
$ sudo chown root:root /usr/local/bin/nightly-backup.sh
$ sudo chmod 755 /usr/local/bin/nightly-backup.sh
$ ls -l /usr/local/bin/nightly-backup.sh
-rwxr-xr-x 1 root root 56 Oct 20 19:19 /usr/local/bin/nightly-backup.sh
$ sudo -u sam test -w /usr/local/bin/nightly-backup.sh || echo "sam can no longer edit it"
sam can no longer edit it

The search finds it, the cron grep shows root runs it, and test -w run as sam proves the risk without changing anything. 755 means root can edit it, everyone can run it, and nobody else can change it. The step 03 search comes back empty again.

Why this matters. The rule for anything root runs — cron jobs, service files, scripts they call — is that only root can write to it, and every folder above it too. When you find a permission problem, fix the one user or group that needs access (next step) instead of opening it to everyone.

06Share a folder the right way: ACLs

Give one user read access to a private folder without changing its owner or opening it to everyone. Then learn to find and remove that kind of grant.

$ sudo mkdir -p /srv/reports; echo 'Q3 numbers' | sudo tee /srv/reports/q3.txt >/dev/null
$ sudo chmod 750 /srv/reports; sudo chmod 640 /srv/reports/q3.txt
$ sudo -u sam cat /srv/reports/q3.txt
cat: /srv/reports/q3.txt: Permission denied
$ sudo setfacl -m u:sam:rx /srv/reports
$ sudo setfacl -m u:sam:r /srv/reports/q3.txt
$ ls -ld /srv/reports
drwxr-x---+ 2 root root 4096 Oct 20 19:20 /srv/reports
$ getfacl -p /srv/reports
# file: /srv/reports
# owner: root
# group: root
user::rwx
user:sam:r-x
group::r-x
mask::r-x
other::---
$ sudo -u sam cat /srv/reports/q3.txt
Q3 numbers
$ sudo -u alex cat /srv/reports/q3.txt
cat: /srv/reports/q3.txt: Permission denied

The + at the end of drwxr-x---+ is the only sign in ls that an ACL exists, and getfacl shows it: a named entry for sam on top of the normal owner, group and other. Sam can read the report; alex still cannot; other is still ---. Now audit and clean up:

$ sudo getfacl -R -s -p /srv /etc /usr/local 2>/dev/null | grep '^# file'
# file: /srv/reports
# file: /srv/reports/q3.txt
$ sudo setfacl -b /srv/reports /srv/reports/q3.txt
$ ls -ld /srv/reports
drwxr-x--- 2 root root 4096 Oct 20 19:20 /srv/reports

getfacl -R -s lists only files that carry an ACL — the way to audit a whole tree. setfacl -b strips every extra entry and the + goes away. (Removing only sam’s entry with setfacl -x u:sam leaves the mask line, so the + stays; -b is the clean reset.)

Why this matters. ACLs are the right tool for “this one person needs access”, and the wrong thing to forget about: access granted by an ACL does not show up in a normal ls -l review. Include getfacl -R -s in every permissions audit.

07Take a baseline with AIDE and catch changes

AIDE records the size, owner, permissions and fingerprints of every important file, then tells you later exactly what changed. Build the baseline, make two changes, and check.

$ sudo aideinit
Running aide --init...
$ sudo ls -l /var/lib/aide/
-rw------- 1 root root 4985633 Oct 20 19:20 aide.db
-rw------- 1 root root 4985633 Oct 20 19:20 aide.db.new
$ echo '10.9.9.9 updates.example.test' | sudo tee -a /etc/hosts >/dev/null
$ printf '#!/bin/sh\necho hi\n' | sudo tee /usr/local/bin/helper >/dev/null; sudo chmod 755 /usr/local/bin/helper
$ sudo aide -c /etc/aide/aide.conf --check | sudo tee /root/aidecheck.txt
AIDE found differences between database and filesystem!!

Summary:
  Total number of entries:	18817
  Added entries:		2
  Removed entries:		0
  Changed entries:		4

Added entries:
f+++++++++++++++++: /root/aidecheck.txt
f+++++++++++++++++: /usr/local/bin/helper

Changed entries:
f >.... mc..H.. . : /etc/hosts
d =.... mc.. .. . : /root
d =.... mc.. .. . : /usr/local/bin
f >b... mc..H.. . : /var/log/aide/aideinit.log

File: /etc/hosts
 Size      : 67                               | 97
 Mtime     : 2026-10-20 19:20:45 +0000        | 2026-10-20 19:25:24 +0000
 MD5       : cXca/J7N2tZIFou35jHWxg==         | F4adyp5meyO6O6kKTIeT5g==

Both changes are named: the new helper is an added entry, and /etc/hosts is changed, with its old and new size, time and fingerprint side by side. The letters in >.... mc..H.. say the size grew (>), the modify and change times moved (m, c), and the hashes differ (H). The baseline took about 20 seconds here and the check about 45; on a full desktop expect minutes.

The other four lines are noise, and learning to read past them is the skill: the report file you just saved, AIDE’s own log, and the two folders whose contents changed (/root and /usr/local/bin). On a machine in daily use, expect more of this — logs, caches, temporary files — until you tune /etc/aide/aide.conf to skip paths that change by design. The detail section is trimmed above to the hash line that matters. When a change is one you made on purpose, fold it into the baseline:

$ sudo aide -c /etc/aide/aide.conf --update
$ sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

The next check no longer mentions /etc/hosts or helper. Both commands report “differences found” with a non-zero exit code; that is how AIDE says it saw changes, not an error.

The defender’s move. An unexpected line in /etc/hosts can quietly send a name like updates.example.test to the wrong address, and a new program in /usr/local/bin did not come from a package. AIDE catches both without knowing anything about packages. For the baseline to be trusted, keep a copy of aide.db somewhere the machine cannot write to, such as offline storage, because root on a compromised machine could rewrite it.

What you learned

  • find / -xdev -perm -4000 — the short list of programs that run with their owner’s rights; save it and watch for changes
  • dpkg -S on each one — every program with extra rights should belong to a package
  • -perm -0002 and the sticky bit — world-writable files and folders, and why /tmp is the safe exception
  • root runs it, so only root writes it — the chmod 777 cron script and its 755 fix
  • setfacl, getfacl -R -s, setfacl -b — grant one user access, audit every ACL, reset cleanly
  • AIDE — aideinit, --check, --update; keep the baseline off the machine

On Red Hat (RHEL, Rocky, Fedora). Checked on Rocky Linux 9: the same find commands work (Rocky 9 had 12 SUID programs) and ACLs are identical. Ownership is rpm -qf instead of dpkg -S. AIDE is in the base repositories (sudo dnf install aide, version 0.19 there); there is no aideinit, so run sudo aide --init, then move /var/lib/aide/aide.db.new.gz to aide.db.gz, then sudo aide --check.

Put it on your resume. Audited Linux file permissions (SUID/SGID inventory checked against package ownership, world-writable files, ACLs), remediated a world-writable root cron script, and deployed AIDE file-integrity monitoring with a verified baseline. Change it to match what you actually did.

Tried it? Tell us

Nova built this lab from Steven’s ideas, and every command above was run on a real machine before it went up. If a step behaved differently for you, or you know a better way, tell us. Ideas for the next lab are welcome too — no promises on timing.

NextLinux for Defenders 08 — Capstone: a daily health-check script, kept in Git→

Go deeper

Original walkthrough. Every command and output block above was run on a fresh Ubuntu 24.04 (AIDE 0.18.6, acl 2.3.2) and the Red Hat note checked on Rocky Linux 9.8 (AIDE 0.19.2). The AIDE detail section is trimmed to one hash line; entry counts and times will differ on yours. The test file already had one extra hosts line from an earlier run, which is why its size starts at 67 bytes. If something does not match your machine, tell me and I’ll fix it.