Linux Permissions Deep Dive: chmod, chown, umask, SUID/SGID/Sticky
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”Why Permissions Exist At All
Section titled “Why Permissions Exist At All”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.
The Three Identities: User, Group, Other
Section titled “The Three Identities: User, Group, Other”Every file and directory has exactly one owner (a user) and exactly one owning group. Permissions are then defined for three separate identities:
| Identity | Symbol | Meaning |
|---|---|---|
| User (owner) | u | The single user who owns the file |
| Group | g | Members of the file’s owning group |
| Other | o | Everyone else on the system |
| All | a | Shorthand for u+g+o combined |
The Three Permission Types
Section titled “The Three Permission Types”| Permission | Symbol | Effect on a FILE | Effect on a DIRECTORY |
|---|---|---|---|
| Read | r | View file contents | List directory contents (ls) |
| Write | w | Modify/overwrite/delete file contents | Create, delete, rename files inside it |
| Execute | x | Run the file as a program/script | Enter 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 lacksxfor 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.
Reading ls -l Output
Section titled “Reading ls -l Output”$ 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/passwdBreak down the 10-character string -rwsr-xr-x:
- rws r-x r-x^ ^ ^ ^type owner group other- Position 1 — file type:
-regular file,ddirectory,lsymlink,ccharacter device,bblock device,ssocket,pnamed pipe (FIFO). - Positions 2–4 — owner permissions (
rws= read, write, execute+SUID — more on thatsshortly). - 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"]
Octal (Numeric) Notation
Section titled “Octal (Numeric) Notation”Each permission triad maps to a 3-bit binary number, which collapses to a single octal digit:
| Binary | Octal | Meaning |
|---|---|---|
rwx | 111 | 7 |
rw- | 110 | 6 |
r-x | 101 | 5 |
r-- | 100 | 4 |
-wx | 011 | 3 |
-w- | 010 | 2 |
--x | 001 | 1 |
--- | 000 | 0 |
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,.envfiles)700— private directory, owner only (~/.ssh)777— everyone can do everything (a huge red flag almost everywhere you see it)
Part 2: chmod — Changing Permissions
Section titled “Part 2: chmod — Changing Permissions”chmod (“change mode”) sets permissions. It supports two syntaxes: octal (absolute) and symbolic (relative/additive).
Octal chmod
Section titled “Octal chmod”chmod 644 notes.txt # rw-r--r--chmod 755 script.sh # rwxr-xr-xchmod 600 id_rsa # rw-------chmod 700 ~/.ssh # rwx------chmod -R 755 /var/www/app # recursive: apply to directory and everything insideOctal 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 chmod
Section titled “Symbolic chmod”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
chmod u+x script.sh # add execute for owner onlychmod g-w file.txt # remove write from groupchmod o=r file.txt # set "other" to exactly read (no write, no exec)chmod a+r file.txt # add read for everyonechmod u+x,g+x script.sh # multiple targets, comma-separatedchmod +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.
Recursive chmod — A Common Pitfall
Section titled “Recursive chmod — A Common Pitfall”chmod -R 755 /var/www/appThis 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:
# Directories need x to be traversable; files usually don't need xfind /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:
chmod -R u=rwX,g=rX,o=rX /var/www/appThe 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.
chown praneeth notes.txt # change owner onlychown praneeth:staff notes.txt # change owner AND groupchown :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 changeCritical 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.
# Check what groups you're a member ofgroupsid# uid=1000(praneeth) gid=1000(praneeth) groups=1000(praneeth),27(sudo),999(docker)Real-world pitfall: the
dockergroup above is a landmine. Membership indockeris 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 needxto be usable at all.
umask# 0022umask 022 means: subtract write from group and other. Apply that to the bases:
File base: 666 Directory base: 777umask: - 022 umask: - 022 ----- -----Result: 644 755flowchart 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:
umask 077 # new files: 600, new directories: 700 — nothing shared with group/otherumask 002 # new files: 664, new directories: 775 — common for shared team directoriesTo 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.
ls -l /usr/bin/passwd# -rwsr-xr-x 1 root root 68208 Feb 10 2026 /usr/bin/passwdNotice 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:
chmod u+s program # symbolicchmod 4755 program # octal — the leading 4 is the SUID bitThe octal special-bit prefix works like this:
| Special bit | Octal value | Symbolic |
|---|---|---|
| SUID | 4000 | u+s |
| SGID | 2000 | g+s |
| Sticky | 1000 | +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.
chmod g+s /srv/shared_projectchown :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:
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.
chmod +t /shared_dropbox # symbolicchmod 1777 /shared_dropbox # octal — everyone can read/write/traverse, but only delete their own filesflowchart 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"]
Why SUID Is the #1 Linux Privesc Vector
Section titled “Why SUID Is the #1 Linux Privesc Vector”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.
# Find every SUID binary on the systemfind / -perm -4000 -type f 2>/dev/null
# Find every SGID binaryfind / -perm -2000 -type f 2>/dev/null
# Find both at oncefind / -perm -6000 -type f 2>/dev/nullOnce 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:
find . -exec /bin/sh -p \; -quitThe -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:
# vim with SUIDvim -c ':!/bin/sh'
# less/more with SUIDless /etc/profile!/bin/sh
# python with SUIDpython3 -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# cp with SUID — overwrite /etc/passwd or /etc/shadow directlycp /tmp/fake_passwd /etc/passwdPart 6: Step-by-Step Hands-On Lab
Section titled “Part 6: Step-by-Step Hands-On Lab”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.
# 1. Set up a sandboxmkdir ~/permlab && cd ~/permlabtouch secret.txt teamfile.txtmkdir shared
# 2. Inspect default permissions your umask producedls -l# -rw-r--r-- 1 you you 0 ... secret.txt (644, from umask 022)
# 3. Lock secret.txt down to owner-onlychmod 600 secret.txtls -l secret.txt# -rw------- 1 you you 0 ... secret.txt
# 4. Make a "script" and try running it before/after +xecho '#!/bin/bash' > run.shecho 'echo "hello from script"' >> run.sh./run.sh# bash: ./run.sh: Permission deniedchmod +x run.sh./run.sh# hello from script
# 5. Set up a shared-group directory with SGIDsudo groupadd permlabteam 2>/dev/nullsudo chown :permlabteam sharedchmod 2775 shared # SGID + rwxrwxr-xls -ld shared# drwxrwsr-x ... shared <- note the lowercase s
# 6. Confirm SGID inheritancetouch shared/newfile.txtls -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 bitchmod +t nostickyls -ld nosticky# drwxrwxrwt ... nosticky <- note the lowercase t
# 9. Hunt for SUID binaries like a pentester wouldfind / -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.cint main() { setuid(0); setgid(0); system("/bin/sh"); return 0; }EOFgcc vuln_suid.c -o vuln_suidsudo chown root:root vuln_suidsudo chmod 4755 vuln_suidls -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 nowLab 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.
# 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 modelsetfacl -m u:alice:rw notes.txt
# Grant a specific group read-onlysetfacl -m g:auditors:r notes.txt
# Remove a specific ACL entrysetfacl -x u:alice notes.txt
# Remove ALL ACL entries, reverting to classic permissionssetfacl -b notes.txtls -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”| Symptom | Root Cause | Fix |
|---|---|---|
Permission denied running a script you just wrote | Missing execute bit | chmod +x script.sh |
Can’t cd into a directory you can ls from outside | Directory missing x for you | chmod +x (not +r) on the directory |
| Web app can’t write to a file, even as root-owned Apache/Nginx worker | Wrong 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 change | You’re not the owner and not root | You need sudo, or you’re targeting the wrong path (symlink vs real file) |
| SUID bit silently disappears after copying a file | cp by default does not preserve special bits, and many filesystems (like ones mounted nosuid, e.g. /tmp on hardened systems) strip SUID entirely | Use 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 should | Filesystem mounted with nosuid, or the binary drops privileges internally, or you’re running a statically-linked shell that ignores -p | Check mount flags for nosuid; try the GTFOBins “Shell” technique that explicitly uses -p |
umask change doesn’t seem to apply | Set in the wrong shell init file, or the process was already running before the change | Confirm with umask in a fresh shell; some daemons set their own umask in their systemd unit (UMask= directive) |
Part 9: Real-World Application
Section titled “Part 9: Real-World Application”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
777file — 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, checksudo -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), andfind / -perm -o+w -type f 2>/dev/null(world-writable files, especially anything under/etc,/opt, or referenced by a cron job — checkcat /etc/crontabandls -la /etc/cron.*). Automate this withlinpeas.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/nullagainst 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 withnosuid,nodev,noexecwhere feasible, so even if an attacker drops a SUID binary there, the kernel refuses to honor the bit. - Least privilege by default: enforce
umask 027or stricter via/etc/profileand systemdUMask=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
777orchmod -R 777pattern is detected in a Dockerfile or provisioning script (a simplegrep -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-widefindorstatsyscalls from a freshly spawned low-privilege shell is a strong privesc-attempt signal, and a well-tuned SOC detection rule onexecveoffind,locate, or LinPEAS-style scripts against/is cheap and high-signal.
Final Revision / Summary
Section titled “Final Revision / Summary”- 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. chmodchanges permissions (octal for absolute, symbolicu/g/o/a++/-/=+r/w/xfor relative).chown/chgrpchange ownership; only root can change a file’s owner.umasksubtracts from the default base (666files,777directories) to determine actual permissions on creation. Default022→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 checkgetfacl, sincels -lalone can hide extra grants (watch for the trailing+). - Privilege escalation checklist:
sudo -l, SUID/SGID hunting withfind / -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.
Cheat Sheet / Quick Reference
Section titled “Cheat Sheet / Quick Reference”# Reading permissionsls -l file # rwxrwxrwx stylels -ld directory # same, for a directory itself (not its contents)stat file # verbose, includes octal mode
# Octal chmodchmod 644 file # rw-r--r--chmod 755 file_or_dir # rwxr-xr-xchmod 600 file # rw------- (private)chmod 700 dir # rwx------ (private dir)chmod -R 755 dir # recursive (careful: hits files AND dirs)
# Symbolic chmodchmod u+x file # add execute for ownerchmod g-w file # remove write for groupchmod o=r file # set other to exactly readchmod a+r file # add read for everyonechmod +X -R dir # execute only where already exec or is a dir
# Ownershipchown user filechown user:group filechown -R user:group dirchgrp group file
# umaskumask # show currentumask 022 # set (files 644, dirs 755)umask 077 # strict (files 600, dirs 700)
# Special bitschmod u+s file / chmod 4755 file # SUIDchmod g+s dir / chmod 2775 dir # SGIDchmod +t dir / chmod 1777 dir # Sticky bit
# Hunting (privesc / audit)find / -perm -4000 -type f 2>/dev/null # SUID binariesfind / -perm -2000 -type f 2>/dev/null # SGID binariesfind / -perm -6000 -type f 2>/dev/null # SUID + SGIDfind / -writable -type d 2>/dev/null | grep -v proc # world-writable dirsfind / -perm -o+w -type f 2>/dev/null # world-writable filessudo -l # what can you run as root?
# ACLsgetfacl filesetfacl -m u:alice:rw filesetfacl -x u:alice filesetfacl -b file # strip all ACLs| Octal | Symbolic | Common Use |
|---|---|---|
| 777 | rwxrwxrwx | Almost never correct — full open access |
| 755 | rwxr-xr-x | Executables, web-servable directories |
| 700 | rwx------ | Private directories (~/.ssh) |
| 644 | rw-r—r— | Normal files, config that others should read |
| 600 | rw------- | Private files (SSH keys, secrets, .env) |
| 4755 | rwsr-xr-x | SUID root binary (e.g., passwd) |
| 2775 | rwxrwsr-x | SGID shared team directory |
| 1777 | rwxrwxrwt | Sticky shared directory (/tmp) |
Practice Labs & Resources
Section titled “Practice Labs & Resources”- TryHackMe — “Linux Fundamentals Part 3” — dedicated room covering permissions, ownership, and
chmod/chownhands-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.