File Permissions in Depth: Octal, SUID, SGID & the Sticky Bit
The last lesson introduced rwx. That's the surface. This lesson is the whole iceberg, the notation shortcuts, ownership, what the bits really mean on directories, and the three special bits that are the difference between a locked-down box and a root shell. If you plan to do privilege escalation later, this is the lesson it all rests on.
Recap: the three triads
- rwx r-x r--
type owner group others
Every file has three sets of read / write / execute bits: one for the owner, one for the file's group, one for everyone else (others). The leading character is the type (- file, d directory, l symlink).
Two ways to write the same thing: symbolic vs octal
Each triad is really three bits, and three bits make a number from 0 to 7. That's why permissions are so often written as three octal digits:
| Symbolic | Bits | Octal | Meaning |
|---|---|---|---|
--- | 000 | 0 | nothing |
--x | 001 | 1 | execute |
-w- | 010 | 2 | write |
-wx | 011 | 3 | write + execute |
r-- | 100 | 4 | read |
r-x | 101 | 5 | read + execute |
rw- | 110 | 6 | read + write |
rwx | 111 | 7 | everything |
The trick: r = 4, w = 2, x = 1, add them up per triad.
rwx r-x r-x -> 7 5 5 -> chmod 755
rw- r-- --- -> 6 4 0 -> chmod 640
rwx --- --- -> 7 0 0 -> chmod 700
Setting permissions with chmod
Octal sets all three triads at once:
chmod 644 notes.md # rw-r--r-- (a typical file)
chmod 600 id_rsa # rw------- (a private key: owner only)
chmod 755 script.sh # rwxr-xr-x (a program everyone may run)
Symbolic changes bits relative to what's there, which is often clearer:
chmod u+x script.sh # add execute for the user (owner)
chmod go-w file # remove write from group and others
chmod a=r file # set everyone to read-only (a = all)
u = user/owner, g = group, o = others, a = all; + adds, - removes, = sets exactly.
Ownership: chown and chgrp
Permissions are meaningless without knowing who owns the file.
chown analyst report.txt # change the owner
chown analyst:staff report.txt # change owner AND group at once
chgrp staff report.txt # change only the group
Changing ownership needs root. This matters for security: if you can change the owner of a file, the permission bits no longer protect it from you.
The bit everyone gets wrong: x on a directory
On a file, x means "run it as a program". On a directory it means something completely different: the right to enter it (cd in) and reach the files inside by name.
- r on a directory: you can list its names (
ls). - x on a directory: you can traverse into it and access a file if you know its name.
- w on a directory: you can create and delete files in it (note: deleting a file is a write to the directory, not the file, which is exactly what the sticky bit below reins in).
So a directory with r but no x lets you see the names but touch nothing; x but no r lets you open a known path but not browse. Beginners lose hours to this.
The special bits
Beyond the nine rwx bits there is a fourth, leading octal digit holding three special bits. They are where permissions stop being bookkeeping and start being a security frontier.
SUID , Set User ID (octal 4000)
A file with SUID set runs with the privileges of its owner, not the user who launched it. You see it as an s in the owner's execute position:
-rwsr-xr-x 1 root root /usr/bin/passwd
^ here
/usr/bin/passwd is owned by root and SUID, so when you run it, it briefly acts as root, just enough to write your new password into /etc/shadow (a file you can't touch directly). That's the legitimate use.
The dangerous use: any root-owned SUID binary that can run other commands, read files, or spawn a shell hands a normal user root. Enumerating them is one of the first things an attacker does:
find / -perm -4000 -type f 2>/dev/null
If an unusual binary (say find, vim, nmap, or a custom script) shows up in that list owned by root, it's very likely a privilege-escalation path. We come back to this in the Privilege Escalation and GTFOBins lessons.
A capital
S(instead ofs) means the SUID bit is set but the underlying execute bit is not, usually a misconfiguration.
SGID , Set Group ID (octal 2000)
SGID is the same idea for the group, and it does two things depending on where it sits:
- On a file: the program runs with the file's group privileges. Shows as
sin the group's execute position (-rwxr-sr-x). - On a directory: new files created inside inherit the directory's group instead of the creator's. This is how shared project folders keep everything under one group.
find / -perm -2000 -type f 2>/dev/null # enumerate SGID binaries
The sticky bit (octal 1000)
The sticky bit only matters on directories. In a world-writable shared directory it restricts deletion: only the owner of a file (or root) may delete or rename it, even though everyone can write to the directory. You see it as a t in the others' execute position:
drwxrwxrwt 10 root root /tmp
^ here
/tmp is the classic example: everyone needs to create temp files there, but without the sticky bit any user could delete any other user's files. A capital T means the sticky bit is set without the underlying x.
Setting the special bits
Prepend the fourth octal digit (SUID = 4, SGID = 2, sticky = 1), or use symbolic form:
chmod 4755 prog # SUID + rwxr-xr-x (chmod u+s prog)
chmod 2755 dir # SGID + rwxr-xr-x (chmod g+s dir)
chmod 1777 shared # sticky + rwxrwxrwx (chmod +t shared)
umask: the default that decides new files
You never run chmod when you create a file, yet it still comes out with some permissions. Those defaults are decided by the umask, a mask that removes permission bits from a starting point.
The system starts from a base:
- 0666 for new files (
rw-rw-rw-, files never get execute by default) - 0777 for new directories (
rwxrwxrwx)
...then strips out whatever bits the umask has set. The exact rule is a bitwise AND with the inverse of the mask:
final = base & ~umask
Try it yourself, type a umask and watch the new-file and new-directory permissions update:
The mask removes bits from the base (0666 for files, 0777 for directories). Try 000 to see a world-writable result, or 077 to lock new files down to the owner only.
Worked example, a mask of 0000. First check the current value (two ways to read it):
$ umask
0000
$ umask -S
u=rwx,g=rwx,o=rwx
A mask of 0000 removes nothing, so for a new file:
0666 & ~0000
= 0666
So the new file becomes:
-rw-rw-rw-
...world-writable, exactly as observed. That's a dangerous default and a real finding on a hardened box.
Worked example, a mask of 0022 (the common, safer default):
0666 & ~0022 = 0644 -> -rw-r--r-- (files)
0777 & ~0022 = 0755 -> rwxr-xr-x (directories)
The 022 strips the write bit from group and others, so new files stay readable by all but writable only by you. Set it with umask 022.
Why this matters
Permissions are the most basic access-control mechanism on the box, and they are where a huge share of real findings live: a world-writable script that root runs on a timer, a private key left 644, a forgotten SUID binary, a config readable by others. Reading these bits fluently, and knowing which ones are dangerous, is the difference between walking past a root shell and noticing it.
Try it (terminal)
ls -l /usr/bin/passwd, spot the SUIDs.find / -perm -4000 -type f 2>/dev/null, enumerate every SUID binary.ls -ld /tmp, find the sticky bitt.chmod 640 notes.mdthenls -l notes.md, watch the mode change torw-r-----.chmod u+s,g+s script.sh, set SUID and SGID symbolically, then read the result.