Skip to main content
See Security Labs

SEC401 - Containers, Linux and Mac Security

Lab 6.1 - Linux Permissions

Solo, Lab

Focus: Linux Security

Level: SEC401

Date: Apr 2026

Artifacts: Sanitized terminal screenshots from a Docker-based permissions lab container

TL;DR

  • Started a Docker lab container and connected as user annika
  • Observed default umask 0022 producing 644 files and 755 directories
  • Tightened umask to 0027 (group-readable, world-nothing) and confirmed 640/750 outputs
  • Demonstrated the sticky bit on /tmp (drwxrwxrwt) for shared-directory safety

Skills demonstrated

Linux file mode bits (owner/group/other)umask math and default permission inheritanceSticky bit and shared-directory safetyContainer-based lab workflows (docker compose)ls -l / ls -ld interpretation

Note: Course-provided PCAPs and lab instructions are not shared. Only my own captures and sanitized notes are published.

Why this matters

Most Linux privilege escalation findings in CTFs and real audits come down to permissions that should have been tighter. Understanding umask, group-vs-other bits, and the sticky bit is the difference between writing a hardening baseline and copy-pasting one you don't understand. It's also what lets you explain to a dev why their world-readable config file is a finding.

Context

This lab demonstrates the core Linux discretionary access control primitives — file mode bits, umask, and the sticky bit — inside a disposable Docker container. The goal is to understand why default permissions are what they are, how to tighten them for a hardening baseline, and how the sticky bit protects world-writable directories like /tmp from cross-user tampering.

Tools used

LinuxbashDockerumasklschmod

Steps taken

1Start the Docker lab container

Ran start_6.1.sh to bring up the lab-61-permissions-1 container on the lab-61_default network. The 'docker stop/rm requires at least 1 argument' lines are the script safely reporting that no prior container existed to clean up. End state: container Running 2/2.

$ cd /sec401/labs/6.1
$ ./start_6.1.sh

2Connect into the container as annika

Ran connect.sh to drop into a shell inside the container as user annika. Every subsequent command runs inside this disposable container, so nothing touches the host.

$ ./connect.sh

3Create a file with the default umask

Wrote a line to test_perms.txt with echo, cat-ed it back to confirm content, then ls -l to read the mode. Output: -rw-r--r-- 1 annika annika 7. Owner rw, group r, other r — the canonical 644 you get with umask 0022.

$ echo annika > test_perms.txt
$ cat test_perms.txt
$ ls -l test_perms.txt

4Read the current umask

umask prints 0022. The mask works by subtracting bits from the base (666 for files, 777 for dirs): 666 - 022 = 644 for files, 777 - 022 = 755 for dirs. That's why the file above landed on 644 without any chmod.

$ umask

5Tighten umask to 0027 and retest

Set umask to 0027 (group read only, world nothing), created a new file and directory, and listed them. Output: -rw-r----- for secure.txt and drwxr-x--- for secure_dir. That's 640/750 — the hardening baseline used by most CIS benchmarks because it cuts world access entirely while keeping same-group collaboration working.

$ umask 0027
$ echo annika > secure.txt
$ mkdir secure_dir
$ ls -ld secure*
umask 0027mask bits = user 0, group 2, other 7
Effectfiles default to 640, dirs to 750

6Sticky bit on /tmp

Listed /tmp with ls -ld: drwxrwxrwt. The trailing t is the sticky bit — directory is world-writable, but only the file owner (or root) can rename or delete a file inside it. Created /tmp/sticky_bit_test.txt to demonstrate: any user can write to /tmp, but annika's file is protected from deletion by other users in the same container.

$ ls -ld /tmp
$ echo "only annika may rename or delete this file" > /tmp/sticky_bit_test.txt
$ ls -l /tmp/sticky_bit_test.txt
drwxrwxrwtd=dir, rwx (user), rwx (group), rwt (other with sticky)
t without x would display as T

Key findings

Default umask in the lab container is 0022, producing 644 files and 755 dirs
umask 0027 produces 640 files and 750 dirs — world access eliminated
/tmp has drwxrwxrwt — world-writable but protected by the sticky bit
Group-readable mode (640) preserves same-group collaboration

Outcome / Lessons learned

Walked through the full Linux permission model from first principles: default umask → file mode bits → tightened hardening umask → sticky-bit semantics on a shared directory. End state is a working mental model for why 644/755 is the default, why 640/750 is the hardened baseline, and why /tmp is drwxrwxrwt specifically.

Set umask 0027 (or 0077 for single-tenant hosts) in /etc/login.defs and /etc/profile so it applies to every interactive session. For service accounts, set the umask in the systemd unit's UMask= directive so spawned processes inherit it. Audit existing sensitive paths (/etc, /var/log, /home) for world-readable files that shouldn't be, and confirm every world-writable directory on the filesystem has the sticky bit — find / -perm -0002 -type d ! -perm -1000 is the one-liner to enforce that.

Security controls relevant

  • CIS Linux benchmark: default umask 027
  • File permission auditing (find -perm)
  • Sticky bit on world-writable directories
  • Service-account systemd UMask= hardening
  • Group-based collaboration without world access

What I took away from this

umask is the one Linux setting that silently shapes every file created on the system. Most hardening guides start with 027 without explaining why — the why is that 027 is the tightest mask that still permits same-group collaboration, and most systems have a legitimate reason to preserve group access (shared dev team, service account + admin group, etc.). 077 is tighter but breaks those workflows.

The sticky bit is a good reminder that Unix permissions aren't just user/group/other — they're a small set of orthogonal tools, and the sticky bit is the one that makes shared directories safe. /tmp, /var/tmp, /dev/shm all rely on it. Any world-writable directory without the sticky bit is a finding worth chasing, because it means any user can delete or rename another user's files inside it.

The docker-compose lab pattern here is understated but useful. You get a clean, throwaway environment for every permission exercise, no risk of clobbering your host, and the setup script handles the network and container lifecycle. Same pattern scales to reproducing customer bugs or running hostile code — it's the defensive version of the sandbox approach.

Evidence gallery