Delight Cybersecurity Student Workbook

Linux Security - File Permissions Series

Linux File
Permissions

Two hands-on sessions covering how to read, predict, build, audit, and harden file permissions on a Linux system - from the basics of rwxrwxrwx to real-world security misconfigurations.

Prerequisites: Basic Linux terminal familiarity - navigating directories, creating files, and running commands. No prior knowledge of permissions required.

Kali Linux recommended

This workbook is designed for Kali Linux. All tools - ls, chmod, chown, find, stat, umask, useradd, groupadd - are pre-installed. Ubuntu or Debian also work without modification. Every command runs directly in your terminal from ~/perm_labs.

kali@kali:~ - environment setup (run once before Session 1)

Open a terminal and run these commands before starting any exercise. They create the working directory, test users, and a shared group that all exercises depend on.

# 1. Create the working directory
mkdir -p ~/perm_labs && cd ~/perm_labs

# 2. Create three test users
sudo useradd -m -s /bin/bash alice
sudo useradd -m -s /bin/bash bob
sudo useradd -m -s /bin/bash charlie

# 3. Set passwords
echo "alice:alice123"     | sudo chpasswd
echo "bob:bob123"         | sudo chpasswd
echo "charlie:charlie123" | sudo chpasswd

# 4. Create shared group
sudo groupadd devteam

# 5. Add alice and bob - charlie is intentionally excluded
sudo usermod -aG devteam alice
sudo usermod -aG devteam bob

# 6. Confirm
getent group devteam
# Expected: devteam:x:NNNN:alice,bob

pwd && echo "Setup complete!"
Kali tip To switch to alice use su - alice (password: alice123). Return to your main user with exit. The - flag gives a full login shell so group memberships load correctly.

Session 1 · ~90 min

Read, Predict, Build

Learn to decode the permission string, predict access outcomes without running commands, and construct precise permission sets from scratch using both symbolic and octal notation.

01

Exercise 1 · ~20 min

Read the Room

Before you change a single permission, you need to be able to read them fluently. You will create a realistic filesystem snapshot and answer security-relevant questions purely by reading ls -la output - no changing anything, no guessing. By the end, reading a permission string will be as fast as reading a clock.
Commands:ls -lastatidchmod
Learning goal: Decode any permission string instantly - identify owner, group, and others; read/write/execute for files and directories; spot dangerous permissions at a glance.

Click any character in the permission string - example: -rwxr--r-- (octal 744)

Type
-
Owner (u)
r
w
x
Group (g)
r
-
-
Others (o)
r
-
-
File type bit
Position 1
The first character identifies the filesystem object type. - = regular file, d = directory, l = symbolic link, b = block device, c = character device, p = named pipe, s = socket. Set by the OS - you cannot change it with chmod.

String reads left-to-right: type · owner bits · group bits · others bits

Core concept - the DAC permission model

Linux uses Discretionary Access Control (DAC). Every file has one owner and one group. Permissions are three triplets - owner, group, others - each with read (r=4), write (w=2), execute (x=1). The OS checks in order: owner first, then group, then others. First match wins - no fallback.

  • Execute on a file = run as a program. Execute on a directory = traverse (cd into it)
  • To delete a file you need write + execute on the parent directory, not on the file itself
  • Root (UID 0) bypasses all DAC checks - permissions do not protect from root
kali@kali:~/perm_labs - creating the exercise snapshot

Run this script to create a realistic filesystem snapshot inside ~/perm_labs/ex1 with a mixture of correct and deliberately misconfigured permissions for you to audit.

STEP 01Create the exercise snapshot
# Create the directory structure
mkdir -p ~/perm_labs/ex1/{webroot,config,private,.ssh,logs}
cd ~/perm_labs/ex1

echo "<html>Hello world</html>" > webroot/index.html
echo "user=admin&password=Tr0ub4dor" > config/db.conf
echo "SECRET_KEY=abc123xyz" > config/.env
echo "-----BEGIN RSA PRIVATE KEY-----" > .ssh/id_rsa
echo "2024-01-15 12:00 GET /index.html 200" > logs/access.log
echo '#!/bin/bash
echo "Backup started"' > config/backup.sh

# Set a mix of correct and misconfigured permissions
chmod 644  webroot/index.html    # correct for web content
chmod 777  config/db.conf        # PROBLEM: world-writable credentials
chmod 644  config/.env           # PROBLEM: world-readable secrets
chmod 777  .ssh/id_rsa           # PROBLEM: private key world-accessible
chmod 640  logs/access.log       # acceptable
chmod 755  config/backup.sh      # world-executable script
chmod 700  private/              # correct for private dir

ls -laR ~/perm_labs/ex1
STEP 02Answer questions using only ls and stat

Using only ls -la and stat, answer each question before revealing the answers. Build the habit of reading - not testing.

# These are your only allowed commands for this step
ls -la ~/perm_labs/ex1/
ls -la ~/perm_labs/ex1/config/
ls -la ~/perm_labs/ex1/.ssh/
stat ~/perm_labs/ex1/config/db.conf
QuestionFileYour answer
Can a non-owner user read db.conf?config/db.confwrite here
Can any user modify db.conf?config/db.confwrite here
Which permission on id_rsa is a security problem?.ssh/id_rsawrite here
Can a random user read .env?config/.envwrite here
Can a random user enter private/?private/write here
What octal value is logs/access.log?logs/access.logwrite here
STEP 03Use stat for detailed metadata
# stat shows the octal form directly
stat ~/perm_labs/ex1/config/db.conf
# Look for: Access: (0777/-rwxrwxrwx)

# Print only octal + filename (useful in scripts)
stat -c "%a %n" ~/perm_labs/ex1/config/*

# Recursive audit - octal + owner + group + filename
find ~/perm_labs/ex1 -exec stat -c "%a %U %G %n" {} \;
Tipstat -c "%a" gives octal, %U owner name, %G group name, %n filename. Combine them to build audit output without parsing ls.
STEP 04Fix all three misconfigurations

Apply the minimum necessary permissions - do not over-restrict either.

# db.conf - credentials: owner read/write only
chmod 600 ~/perm_labs/ex1/config/db.conf

# .env - credentials: owner read only
chmod 600 ~/perm_labs/ex1/config/.env

# id_rsa - SSH refuses to use keys readable by others
chmod 600 ~/perm_labs/ex1/.ssh/id_rsa

# Verify all three
stat -c "%a %n" ~/perm_labs/ex1/config/db.conf \
                ~/perm_labs/ex1/config/.env \
                ~/perm_labs/ex1/.ssh/id_rsa
# Expected: 600 for all three
SSH ruleSSH refuses to use a private key readable by anyone other than the owner. Test: set chmod 644 .ssh/id_rsa then try to connect - you will see UNPROTECTED PRIVATE KEY FILE and the connection is refused. This is one of the few tools that enforces permission security automatically.
?Checkpoint - Exercise 1

1. A file has permissions -rw-r--r--. Which statement is true?

AEveryone can write to it
BThe owner can write; group and others can only read
COnly the owner can read it
DThe file is executable by all

2. You want to delete notes.txt inside /home/alice/docs/. The file has permissions 400. Can you delete it? What permission actually controls deletion?

Deletion is controlled by write + execute on the parent directory (/home/alice/docs/), not the file itself. Even though the file is 400, if you have write+execute on the parent you can delete it. Conversely, write permission on the file does not let you delete it without write+execute on the parent. This surprises most students because it is counter-intuitive.

3. What is the octal value of -rwxr-xr--?

A755
B754
C744
D764

02

Exercise 2 · ~25 min

Build Permissions from Scratch

Reading permissions is half the skill. The other half is setting them precisely. You will build a directory structure with exact permission requirements using both symbolic (chmod u+x) and octal (chmod 755) notation, then peer-review a partner's setup to catch mistakes.
Commands:chmodchownls -latouch / mkdir
Learning goal: Set permissions precisely using both symbolic and octal notation; understand when each is appropriate; build the habit of applying least-privilege from the start.

Octal permission builder - toggle bits to build any permission

Owner (u)

Read4
Write2
Execute1

Group (g)

Read4
Write2
Execute1

Others (o)

Read4
Write2
Execute1
755 rwxr-xr-x Owner: read, write, execute · Group: read, execute · Others: read, execute

Core concept - symbolic vs octal notation

Both notations set the same underlying bits. Choose the one that fits the task:

  • Octal (chmod 644) - sets permissions absolutely. Best when you know the exact final state. Faster for scripting.
  • Symbolic (chmod u+x) - modifies relative to the current state. Best for surgical one-bit changes. Uses u (owner), g (group), o (others), a (all), with operators + (add), - (remove), = (set exactly).
STEP 01Create the directory structure
# Create the exercise directory
mkdir -p ~/perm_labs/ex2/{public,private,scripts,shared}
cd ~/perm_labs/ex2

touch public/index.html public/style.css
touch private/secret.key private/config.yaml
touch scripts/deploy.sh scripts/cleanup.sh
touch shared/report.txt shared/data.csv
STEP 02Apply the specification using octal notation

Derive each octal value yourself before typing the command - use the builder above.

File / DirRequirementOctal
public/index.htmlOwner: rw, Group: r, Others: r644
private/secret.keyOwner: rw only - nobody else600
private/config.yamlOwner: rw, Group: r, Others: none640
scripts/deploy.shOwner: rwx, Group: rx, Others: none750
scripts/cleanup.shOwner: rwx only700
shared/report.txtOwner: rw, Group: rw, Others: r664
shared/ (directory)Owner: rwx, Group: rwx, Others: rx775
# Apply them
chmod 644 public/index.html
chmod 600 private/secret.key
chmod 640 private/config.yaml
chmod 750 scripts/deploy.sh
chmod 700 scripts/cleanup.sh
chmod 664 shared/report.txt
chmod 775 shared/

# Verify all at once
find ~/perm_labs/ex2 -exec stat -c "%a %n" {} \; | sort
STEP 03Use symbolic notation for targeted changes
# Add group write to shared/data.csv only
chmod g+w shared/data.csv

# Set config.yaml to exactly owner-read, nothing else
chmod u=r,g=,o= private/config.yaml
stat -c "%a %n" private/config.yaml   # Expected: 400

# Add write back for owner
chmod u+w private/config.yaml
stat -c "%a %n" private/config.yaml   # Expected: 600
Key differencechmod 600 file always sets exactly those permissions. chmod u-x file only removes owner-execute, leaving all other bits unchanged. Use octal for a known final state; use symbolic for surgical one-bit changes.
STEP 04Peer review

If working in pairs, have your partner introduce two intentional errors in their ex2 directory. Then audit each other's work and write one sentence per problem found, describing the security implication.

# Quick audit - shows all files with octal permissions
find ~/perm_labs/ex2 -exec stat -c "%a %-30n" {} \;

# Flag world-writable files
find ~/perm_labs/ex2 -perm -o+w -type f

# Flag files executable by others that should not be
find ~/perm_labs/ex2 -perm -o+x -type f
?Checkpoint - Exercise 2

1. Which command adds execute permission for the owner only, leaving group and others unchanged?

Achmod +x file
Bchmod u+x file
Cchmod 100 file
Dchmod a+x file

2. backup.sh has permissions 755. You want to prevent anyone except the owner from executing it. What is the correct command, and why?

Use chmod 700 backup.sh or chmod go-x backup.sh. The first sets absolute permissions (owner: rwx, group/others: none). The second removes execute from group and others while leaving owner unchanged. The octal form is generally preferred in scripts because it expresses the final state unambiguously regardless of what the permissions were before.

03

Exercise 3 · ~30 min

The Ownership Triangle

This exercise introduces the three-way relationship between owner, group, and others - and proves that Linux permission checks stop at the first match. You will predict access outcomes before testing, switch between user accounts to verify, and discover the one rule that surprises nearly every student.
Commands:su -idchownchgrpgroups
Learning goal: Internalise the owner→group→others check order; understand first-match-wins with no fallback; discover the counter-intuitive case where an owner has fewer permissions than group members.

Select a scenario - click Read/Write cells to see the access decision

report.txt · Owner: alice · Group: devteam · -rw-r----- (640)
User
Read?
Write?
Identity used
alice
✓ Yes
✓ Yes
owner
bob
✓ Yes
✗ No
group
charlie
✗ No
✗ No
others
root
✓ Yes
✓ Yes
superuser
How Linux decides access
Linux checks identities in order: owner first → group second → others last. The first matching category is used and the check stops - no fallback. Click any ✓/✗ cell to see the exact reasoning for that user.

Scenario B is the most important - it reveals the counter-intuitive first-match rule

STEP 01Create files with different owners
# Create exercise directory
mkdir -p ~/perm_labs/ex3 && cd ~/perm_labs/ex3

sudo touch alice_file.txt bob_file.txt shared_file.txt team_file.txt

sudo chown alice:alice    alice_file.txt
sudo chown bob:bob        bob_file.txt
sudo chown alice:devteam  shared_file.txt
sudo chown alice:devteam  team_file.txt

sudo chmod 640 alice_file.txt    # owner rw, group r, others none
sudo chmod 600 bob_file.txt      # owner rw only
sudo chmod 664 shared_file.txt   # owner rw, group rw, others r
sudo chmod 060 team_file.txt     # THE TRICK: group rw, owner has nothing!

ls -la ~/perm_labs/ex3/
STEP 02Predict access before switching users

Fill in this table in your notes before running any commands. Work through each row by reading the permission string only.

UserFile (perms)Can read?Can write?Identity used
alicealice_file.txt (640)???
bobalice_file.txt (640)???
charliealice_file.txt (640)???
aliceteam_file.txt (060)???
bobteam_file.txt (060)???
charlieteam_file.txt (060)???
STEP 03Verify by switching users
# Quick test using sudo -u (no need to fully switch accounts)
sudo -u alice   cat ~/perm_labs/ex3/alice_file.txt 2>&1
sudo -u bob     cat ~/perm_labs/ex3/alice_file.txt 2>&1
sudo -u charlie cat ~/perm_labs/ex3/alice_file.txt 2>&1

# The trick case - predict alice's result before running!
sudo -u alice   cat ~/perm_labs/ex3/team_file.txt  2>&1
# Expected: PERMISSION DENIED - alice is owner but owner bits are 0!

sudo -u bob     cat ~/perm_labs/ex3/team_file.txt  2>&1
# Expected: success - bob is in devteam, group bits are rw

sudo -u charlie cat ~/perm_labs/ex3/team_file.txt  2>&1
# Expected: DENIED - charlie is others, others bits are 0
The insightteam_file.txt has permissions 060. Alice is the owner but owner bits are 0 - so Alice is denied. Bob is in devteam, group bits are 6 - Bob can read and write. Linux never falls through: first match wins, always. The owner can be locked out of their own file.
?Checkpoint - Exercise 3

1. A file has permissions 070 and is owned by alice:devteam. Alice tries to read the file. What happens?

Alice is denied. She is the owner, so Linux applies owner permissions first - which are 0 (none). Linux stops there and does not check whether Alice is also in devteam. Even though group bits are 7 (rwx), Alice never reaches that check because she matched as owner first. This is the counter-intuitive first-match-wins rule in action.

2. Why does root bypass permission checks? Is there any way to protect sensitive data from root?

Root bypasses DAC because the Linux kernel grants UID 0 special treatment - it is exempt from permission checks for most filesystem operations. You cannot prevent root from reading a file using standard permissions alone. To protect data from root, you need encryption (ciphertext root cannot decrypt without the key) or mandatory access control systems like SELinux/AppArmor that enforce policy even on root. Permissions protect from other users, not from the superuser.

Session 2 · ~100 min

Real Scenarios, Find Issues, umask

Apply what you learned to real-world scenarios: diagnose a broken web server using only permission fixes, configure a shared team directory with setgid and sticky bit, hunt for misconfigurations with find, and control default permissions with umask.

04

Exercise 4 · ~25 min

Broken Web Server

A web server is failing in three different ways - pages won't serve, the config won't load, and uploaded files are inaccessible. Every problem is a permission issue. Diagnose each failure by reading permissions, fix them with the minimum necessary change, and document what each misconfiguration would mean for an attacker.
Commands:python3 -m http.serverchmodchownls -lacurl
Learning goal: Connect permission errors to real service failures; diagnose from error messages rather than trial-and-error; apply least-privilege fixes rather than just adding permissions.

Core concept - web server permissions

A web server process runs as a low-privilege user (www-data on Debian/Kali). For it to serve a file it must:

  • Traverse (execute bit) every directory on the path from root to the file
  • Read the file itself and its config at startup
  • Write to its log directory - but not to the web root (that would allow defacement)
STEP 01Create the broken environment
# Create directory structure
mkdir -p ~/perm_labs/ex4/{webroot/{html,uploads},config,logs}
cd ~/perm_labs/ex4

echo "<html><body><h1>Welcome</h1></body></html>" > webroot/html/index.html
echo "DB_HOST=localhost
DB_PASS=secretpass123" > config/app.conf
echo "upload content" > webroot/uploads/photo.jpg
touch logs/access.log logs/error.log

# Deliberately break four things
chmod 700  webroot/html/      # Problem 1: not traversable by server process
chmod 644  config/app.conf    # Problem 2: world-readable credentials
chmod 555  webroot/uploads/   # Problem 3: uploads dir not writable
chmod 444  logs/access.log    # Problem 4: log not writable

ls -la ~/perm_labs/ex4/webroot/
ls -la ~/perm_labs/ex4/config/
ls -la ~/perm_labs/ex4/logs/
STEP 02Diagnose each failure
# Simulate the server process as a less-privileged user (bob)
sudo -u bob cat ~/perm_labs/ex4/webroot/html/index.html
# Expected: Permission denied - html/ is 700 (owner only)

sudo -u bob cat ~/perm_labs/ex4/config/app.conf
# This SUCCEEDS - but should it? World-readable credentials is the problem!

sudo -u bob sh -c "echo 'upload' > ~/perm_labs/ex4/webroot/uploads/new.txt"
# Expected: Permission denied - uploads/ is 555 (no write)

sudo -u bob sh -c "echo 'log entry' >> ~/perm_labs/ex4/logs/access.log"
# Expected: Permission denied - log is 444 (read-only)
ProblemCurrentCorrect permSecurity risk if too open
html/ not traversable700write herewrite here
app.conf world-readable644write herewrite here
uploads/ not writable555write herewrite here
access.log not writable444write herewrite here
STEP 03Apply minimum necessary fixes
# Fix 1: html/ needs to be traversable and readable by the server process
chmod 755 ~/perm_labs/ex4/webroot/html/
# 755: owner rwx, group rx, others rx - server can enter and read
# Not 777: the server never needs to write to the web root

# Fix 2: app.conf should NOT be world-readable
chmod 640 ~/perm_labs/ex4/config/app.conf
# Owner rw, group r (server group can read), others denied

# Fix 3: uploads/ needs to be writable by the server
chmod 755 ~/perm_labs/ex4/webroot/uploads/

# Fix 4: log file needs to be writable by the server process
chmod 644 ~/perm_labs/ex4/logs/access.log

# Re-test
sudo -u bob cat ~/perm_labs/ex4/webroot/html/index.html   # now works
sudo -u bob cat ~/perm_labs/ex4/config/app.conf           # still denied - correct!
echo "All fixes verified"
PrincipleLeast privilege: grant only the permissions required for the service to function. Every extra permission is an attack surface. A world-readable config file leaks credentials to any local user. A world-writable web root lets any user deface the site or plant a web shell.
?Checkpoint - Exercise 4

1. A file has correct content but the web server returns 403 Forbidden. The file itself is 644. What else should you check?

Check the execute (traverse) bit on every directory in the path from the web root to the file. A 403 on a readable file almost always means a parent directory is missing the execute bit for the server's user. Use ls -la on each parent and look for the x bit in the others or group position.

2. Why is a world-writable uploads directory dangerous even if the web server only needs to write there?

World-writable means any local user can create, modify, or delete files in that directory - not just the server process. An attacker with any shell on the system could plant a web shell in the uploads directory, which the server would serve to visitors. The correct fix: set the directory to be writable only by the server's specific user/group using chown + 700 or 750.

05

Exercise 5 · ~30 min

The Shared Project Directory

A team needs a shared directory where all members can read and write files, new files automatically inherit the group, and members cannot delete each other's work. Solving this correctly requires three specific features: group permissions, the setgid bit, and the sticky bit. You will configure each one, see the problem it solves, then verify with real user switching.
Commands:chmod 2770chmod +tchown :devteamnewgrpls -la
Learning goal: Configure a production-grade shared directory using setgid and sticky bit; understand what each special bit does and why both are needed; verify the setup by testing with real user accounts.

Click a special bit to learn what it does and when to use it

👤
setuid (SUID)
chmod 4xxx · u+s
👥
setgid (SGID)
chmod 2xxx · g+s
📌
Sticky bit
chmod 1xxx · +t
setgid (SGID) on a directory - group inheritance
DirectoriesThis exercise
When setgid is set on a directory, any new file or subdirectory created inside automatically inherits the directory's group - not the creator's primary group. Essential for shared team directories: without setgid, files created by alice would be owned by alice's primary group, and bob might not be in that group. Shows as s in the group-execute position: drwxrws---. Octal: add 2 to the leading digit - e.g. 2770.

setgid on a directory and on a file do very different things - click to compare

STEP 01Create the directory without special bits - observe the problem
# Create the shared project directory
sudo mkdir -p /opt/project
sudo chown root:devteam /opt/project
sudo chmod 770 /opt/project

# Alice creates a file
sudo -u alice sh -c "echo 'Alice data' > /opt/project/alice_notes.txt"

# Check the group on the new file
ls -la /opt/project/alice_notes.txt
# Group = alice (her primary group) - NOT devteam!
# Bob cannot read it because he is not in alice's primary group

sudo -u bob cat /opt/project/alice_notes.txt
# Likely: Permission denied
STEP 02Add setgid - fix group inheritance
# Add setgid - new files will inherit devteam group
sudo chmod g+s /opt/project
# Equivalent: sudo chmod 2770 /opt/project

ls -la /opt/
# Look for 's' in group-execute position: drwxrws---

# Alice creates another file
sudo -u alice sh -c "echo 'Alice v2' > /opt/project/alice_v2.txt"

ls -la /opt/project/alice_v2.txt
# Group should now be devteam automatically

sudo -u bob cat /opt/project/alice_v2.txt
# Expected: success - group is devteam, bob is in devteam
STEP 03Demonstrate the deletion problem - fix with sticky bit
# WITHOUT sticky bit: bob can delete alice's files
sudo -u bob rm /opt/project/alice_v2.txt
# SUCCEEDS - bob has write+execute on the directory, so he can delete. Bad!

# Recreate alice's file
sudo -u alice sh -c "echo 'Recreated' > /opt/project/alice_v2.txt"

# Add the sticky bit
sudo chmod +t /opt/project
# Or: sudo chmod 3770 /opt/project (2=setgid + 1=sticky = 3)

ls -la /opt/
# Look for 'T' or 't' in the others-execute position
# Capital T = sticky set, others have no execute
# Lowercase t = sticky set and others have execute

# Now bob tries to delete alice's file
sudo -u bob rm /opt/project/alice_v2.txt
# Expected: Operation not permitted - sticky bit protects it

# But bob CAN still delete his own files
sudo -u bob sh -c "echo 'Bob data' > /opt/project/bob_notes.txt"
sudo -u bob rm /opt/project/bob_notes.txt
# Expected: success - sticky only prevents deleting OTHER people's files
Final stateProduction-ready shared directory: chown root:devteam /opt/project + chmod 3770 /opt/project. The 3 combines setgid (2) + sticky (1). New files inherit devteam group automatically. Members cannot delete each other's work.
?Checkpoint - Exercise 5

1. What does the s in the group-execute position mean in drwxrws---?

AThe directory has a symbolic link inside it
Bsetgid is set - new files inherit this directory's group
COnly the superuser can enter this directory
DThe directory is shared over the network

2. The sticky bit is set on /opt/project. Bob is in devteam and has write+execute on the directory. Can Bob delete Alice's file? Can Bob delete his own file?

Bob cannot delete Alice's file - the sticky bit restricts deletion to the file's owner, the directory's owner, or root, regardless of directory write permission. Bob can delete his own files because he is their owner. This is exactly how /tmp works on any Linux system: world-writable so anyone can create temp files, but the sticky bit prevents users from deleting each other's files.

06

Exercise 6 · ~25 min

Finding Misconfigurations with find

A real Linux system has thousands of files. Manual inspection is impossible - you need find to hunt for dangerous permissions automatically. You will plant deliberate misconfigurations, then build and run the same find commands that penetration testers and security auditors use in real engagements.
Commands:find -permfind -nouserstatls -la
Learning goal: Build and understand the find commands used in real security audits; classify findings by severity; explain why each misconfiguration is dangerous.

Click any find flag to learn what it does and why it matters

-perm -0002
World-writable files
-perm -4000
setuid binaries
-perm -2000
setgid binaries
-nouser
Orphaned files
-xdev
Stay on filesystem
-perm -0002 - world-writable files
Critical severity
The leading - in -perm -0002 means "at least these bits set" - so it matches any file where the others-write bit is on, regardless of other bits. 0002 is octal for the others-write bit. World-writable scripts called by cron as root are a direct privilege escalation path. World-writable config files let any local user modify server behaviour.

These five flags form the core of any Linux permission audit

STEP 01Plant the misconfigurations
# Create a directory tree with deliberate problems to find
mkdir -p ~/perm_labs/ex6/{system,home/alice,scripts,tmp_share}

# World-writable files - critical
echo "server config" > ~/perm_labs/ex6/system/server.conf
chmod 777 ~/perm_labs/ex6/system/server.conf

echo "cron task" > ~/perm_labs/ex6/scripts/nightly.sh
chmod 777 ~/perm_labs/ex6/scripts/nightly.sh

# Private key with bad permissions - high
echo "-----BEGIN RSA PRIVATE KEY-----" > ~/perm_labs/ex6/home/alice/.ssh_key
chmod 644 ~/perm_labs/ex6/home/alice/.ssh_key

# Setuid binary simulation
cp /bin/ls ~/perm_labs/ex6/scripts/myls
chmod 4755 ~/perm_labs/ex6/scripts/myls

# Setgid binary simulation
cp /bin/ls ~/perm_labs/ex6/scripts/myls2
chmod 2755 ~/perm_labs/ex6/scripts/myls2

# World-writable directory
chmod 777 ~/perm_labs/ex6/tmp_share/

echo "Misconfigurations planted. Now find them."
STEP 02Hunt for world-writable files and directories
# Find world-writable FILES in the exercise directory
find ~/perm_labs/ex6 -type f -perm -0002

# Find world-writable DIRECTORIES
find ~/perm_labs/ex6 -type d -perm -0002

# Show with full details
find ~/perm_labs/ex6 -perm -0002 -exec ls -la {} \;

# Real-world version (whole system, staying on one filesystem)
find / -xdev -type f -perm -0002 2>/dev/null
Why it mattersA world-writable script called by a cron job running as root is a direct path to privilege escalation. An attacker who can write to that script can append: chmod 4777 /bin/bash - next time cron runs, any user can get a root shell with bash -p.
STEP 03Hunt for setuid and setgid binaries
# Find setuid binaries
find ~/perm_labs/ex6 -type f -perm -4000

# Find setgid binaries
find ~/perm_labs/ex6 -type f -perm -2000

# Find BOTH in one command
find ~/perm_labs/ex6 -type f \( -perm -4000 -o -perm -2000 \)

# Show with owner, group, octal permissions
find ~/perm_labs/ex6 \( -perm -4000 -o -perm -2000 \) \
  -exec stat -c "%a %U %G %n" {} \;

# Real-world: all setuid binaries on the system
find / -xdev -type f -perm -4000 2>/dev/null
Pen test noteLegitimate setuid binaries on a standard Kali system include passwd, su, sudo, mount, ping. Any binary not on the expected list is suspicious. Run strings on it to see what commands it calls - PATH hijacking is the first thing to check.
STEP 04Hunt for orphaned files
# Create a file owned by a UID with no account
sudo touch ~/perm_labs/ex6/orphaned_file.txt
sudo chown 9998:9998 ~/perm_labs/ex6/orphaned_file.txt 2>/dev/null || true
# ls will show numeric UID 9998 instead of a username

# Find files with no matching user
find ~/perm_labs/ex6 -nouser 2>/dev/null

# Find files with no matching group
find ~/perm_labs/ex6 -nogroup 2>/dev/null

# Real world: search entire filesystem
find / -xdev \( -nouser -o -nogroup \) 2>/dev/null
STEP 05Build a reusable audit script

Combine all checks into a single script. Save it as ~/perm_labs/audit.sh - this is the skeleton of a real permission audit tool.

# Save as ~/perm_labs/audit.sh then run: bash ~/perm_labs/audit.sh ~/perm_labs/ex6
cat > ~/perm_labs/audit.sh << 'SCRIPT'
#!/bin/bash
TARGET="${1:-$HOME/perm_labs/ex6}"
echo "=== Permission Audit: $TARGET ==="

echo -e "\n[CRITICAL] World-writable files:"
find "$TARGET" -xdev -type f -perm -0002 2>/dev/null | \
  while read f; do echo "  $(stat -c '%a %U %G %n' "$f")"; done

echo -e "\n[CRITICAL] World-writable directories:"
find "$TARGET" -xdev -type d -perm -0002 2>/dev/null | \
  while read f; do echo "  $(stat -c '%a %U %G %n' "$f")"; done

echo -e "\n[HIGH] Setuid binaries:"
find "$TARGET" -xdev -type f -perm -4000 2>/dev/null | \
  while read f; do echo "  $(stat -c '%a %U %G %n' "$f")"; done

echo -e "\n[MEDIUM] Setgid binaries:"
find "$TARGET" -xdev -type f -perm -2000 2>/dev/null | \
  while read f; do echo "  $(stat -c '%a %U %G %n' "$f")"; done

echo -e "\n[INFO] Orphaned files:"
find "$TARGET" -xdev \( -nouser -o -nogroup \) 2>/dev/null | \
  while read f; do echo "  $f"; done

echo -e "\n=== Audit complete ==="
SCRIPT

chmod +x ~/perm_labs/audit.sh
bash ~/perm_labs/audit.sh ~/perm_labs/ex6
?Checkpoint - Exercise 6

1. What does find / -perm -4000 -type f search for?

AFiles readable by all users
BFiles with the setuid bit set
CFiles owned by UID 4000
DFiles with exactly octal 4000 permissions

2. You find a world-writable shell script at /etc/cron.daily/cleanup.sh owned by root. Rate the severity and explain the exact attack path.

Critical severity. Cron runs scripts in /etc/cron.daily/ as root. Because cleanup.sh is world-writable, any user can append a command - for example: echo 'chmod 4777 /bin/bash' >> /etc/cron.daily/cleanup.sh. When cron next runs the script as root, /bin/bash gets the setuid bit. The attacker then runs bash -p to get a root shell. This is a textbook local privilege escalation via cron + world-writable script.

07

Exercise 7 · ~20 min

umask - Controlling Default Permissions

Every file you create starts with a default permission set. That default is not random - it is controlled by umask, a value that masks out (removes) bits from the theoretical maximum. Understanding umask is essential: it explains why files created on a developer's workstation might have different defaults than those created by a system service - and why that difference can become a security problem.
Commands:umaskumask 027umask -Stouchmkdir
Learning goal: Understand how umask works as a bitmask; calculate the resulting file and directory permissions from any umask value; choose and set the right umask for different environments; make it persistent.

umask calculator - type a value or choose a preset

umask value:
Max for files:666 (rw-rw-rw-)files never get execute by default
Max for dirs:777 (rwxrwxrwx)dirs need execute to be traversable
Mask removes:027 (----w-rwx)
New file gets:640 (rw-r-----)
New dir gets:750 (rwxr-x---)
umask 027 - used on secure servers. Owner gets full access. Group gets read-only. Others get nothing. New files are never world-readable by default - recommended for systems handling sensitive data.

Core concept - how umask works

umask is a bitmask subtracted from the default maximum. Files default to 666 (no execute - you should never create executable files by default), directories default to 777. The umask removes specific bits: umask 022 removes group-write and others-write, giving files 644 and directories 755.

  • Formula: result = default AND NOT(mask) - or simply subtract the umask from the default
  • umask is per-process - it does not affect existing files, only newly created ones
  • Set temporarily: umask 027 - lasts until the shell exits
  • Set permanently: add umask 027 to ~/.bashrc (per-user) or /etc/profile (system-wide)
STEP 01Observe the current umask and its effect
# View current umask in octal
umask
# On Kali, default is usually 0022

# View in symbolic form (shows what IS allowed, not what is removed)
umask -S
# Example: u=rwx,g=rx,o=rx

# Create a file and directory with the current umask
mkdir -p ~/perm_labs/ex7 && cd ~/perm_labs/ex7
touch test_default.txt
mkdir test_default_dir

ls -la
# With umask 022: file = 644, dir = 755
STEP 02Change umask and observe the difference
# Switch to more restrictive umask for this session only
umask 027

# Create new files
touch test_027.txt
mkdir test_027_dir

# Compare side by side
stat -c "%a %n" test_default.txt test_027.txt
# test_default.txt = 644 ; test_027.txt = 640

stat -c "%a %n" test_default_dir test_027_dir
# test_default_dir = 755 ; test_027_dir = 750
STEP 03Try maximum privacy - umask 077
# Maximum privacy - nothing shared with anyone
umask 077

touch test_077.txt
mkdir test_077_dir

stat -c "%a %n" test_077.txt    # Expected: 600
stat -c "%a %n" test_077_dir    # Expected: 700

# This is appropriate for: private keys, personal secrets
# Too restrictive for: shared servers, team directories
STEP 04Make a umask persistent across logins
# Add to ~/.bashrc for this user permanently
echo "umask 027" >> ~/.bashrc

# Test in a new subshell
bash -c "umask"
# Expected: 0027

# Check system-wide default
grep -i umask /etc/login.defs
# UMASK line controls the system-wide default for new user accounts

grep umask /etc/profile 2>/dev/null || echo "Not set in /etc/profile"

# Reset back to 022 for this session to avoid affecting other exercises
umask 022
RecommendationFor a multi-user server: set umask 027 in /etc/profile - all users and services default to group-read, others-denied. For a service account (web server, database): set umask 027 or 077 in the service's startup script so its config files are never world-readable by default.
?Checkpoint - Exercise 7

1. What permissions will a new file have if the current umask is 027?

A777
B640
C750
D644

2. A developer sets umask 000 on a shared server to "avoid permission issues". What is the security problem?

umask 000 means no bits are masked out. Files are created with 666 (world-readable and writable) and directories with 777. Every file the developer creates - config files, scripts, data - is immediately readable and writable by all users on the system. An attacker with any compromised account can read credentials, modify configs, or overwrite scripts. The correct fix for "permission issues" is to identify the actual requirement and set it precisely - not to disable all protection.

3. You set umask 027 in your terminal but the files created by a cron job still come out as 644. Why?

umask is per-process and inherited from the parent shell. The cron daemon starts its own processes with its own umask - typically 022 from /etc/profile or the cron daemon's own startup config. Your terminal's umask does not affect cron. To change the umask for cron jobs, add umask 027 at the top of the crontab file or inside the script itself.

Both sessions complete

What you have covered

Ready for Session 3? Next topics include setuid binary exploitation, credential file exposure and remediation, Linux ACLs for per-user fine-grained control, and building a full automated permission audit and reporting tool.