Security Basics Lesson 12 – Linux Security Fundamentals | Dataplexa
Section II · Lesson 12

Linux Security Fundamentals

This lesson covers

SSH hardening that actually holds → sudo configuration and the risks of getting it wrong → Mandatory access control with SELinux and AppArmor → The Linux firewall stack — iptables and nftables → Log analysis with journald and syslog → Detecting compromise on a running system

Linux runs the majority of the world's servers, cloud infrastructure, containers, and embedded systems. It also runs most of the internet's attack targets. The OS gives you more security levers than almost any other platform — granular access controls, mandatory security frameworks, powerful audit tooling, a transparent file system you can inspect entirely from the command line. The question isn't whether the tools exist. It's whether anyone has configured them.

SSH hardening — the first thing to fix on any new server

SSH is the primary remote access method for Linux servers and consequently the most attacked service on the public internet. A server exposed on port 22 with password authentication enabled will be hit by automated brute-force scripts within minutes of going online. The default SSH configuration is not a secure one — it exists to be functional, not hardened.

# /etc/ssh/sshd_config — hardened configuration
# Edit this file, then: systemctl restart sshd

Port 2222                        # Move off default port 22 (reduces noise)
PermitRootLogin no               # Never allow direct root login over SSH
PasswordAuthentication no        # Keys only — no password brute-force possible
PubkeyAuthentication yes         # SSH key pairs required
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3                   # Disconnect after 3 failed attempts
LoginGraceTime 20                # 20 seconds to authenticate or disconnect
X11Forwarding no                 # No GUI forwarding (attack surface)
AllowTcpForwarding no            # Prevent SSH tunnelling unless needed
ClientAliveInterval 300          # Disconnect idle sessions after 5 minutes
ClientAliveCountMax 2
AllowUsers deploy analyst        # Whitelist — only these users can SSH in

What just happened

Disabling password authentication alone eliminates brute-force as an attack vector entirely — you can't guess a key. The AllowUsers directive is a whitelist: even if an attacker creates a new account on the system, it won't be on this list and therefore cannot log in remotely. MaxAuthTries 3 limits how quickly an attacker can iterate if they do find a way to try — pair this with fail2ban to auto-ban IPs that repeatedly hit the limit.

sudo — controlled privilege escalation

sudo lets a regular user run specific commands as root — or as any other user — without handing over the root password. Configured correctly it's a precise tool: this user can run this command as root, nothing else. Configured loosely it's a liability: a user with unrestricted sudo access is functionally root, just with an extra step.

# /etc/sudoers — edit ONLY with: visudo
# visudo validates syntax before saving. Direct edits can lock you out.

# ── BAD: unrestricted sudo — this user is effectively root ──
# deploy ALL=(ALL:ALL) ALL

# ── GOOD: specific commands only, no password prompt for automation ──
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx

# ── GOOD: analyst can read logs but cannot modify anything ──
analyst ALL=(root) NOPASSWD: /usr/bin/journalctl
analyst ALL=(root) NOPASSWD: /bin/cat /var/log/auth.log

# ── Audit who used sudo and when ──
grep "sudo:" /var/log/auth.log | tail -20

What just happened

The commented-out bad example grants unlimited root access — it's the lazy default that defeats the purpose of sudo entirely. The good examples pin each user to exactly the commands their role requires. The deploy account can restart and reload nginx — nothing else. The analyst account can read logs — nothing else. If either account is compromised, the blast radius is contained to those two commands. The final line audits every sudo invocation with user and timestamp — essential for detecting privilege abuse.

Mandatory access control — SELinux and AppArmor

Standard Linux permissions are discretionary — the file owner decides who can access what. Mandatory Access Control (MAC) goes further: the OS itself enforces policies that not even root can override without explicitly changing the policy. A process can only do what the MAC policy permits, regardless of file permissions or privilege level.

SELinux (Security-Enhanced Linux) is the MAC framework used on Red Hat, CentOS, and Fedora systems. It assigns labels to every process and file, and enforces a policy that defines which labels can interact with which. A compromised web server process labelled httpd_t cannot read files labelled shadow_t — even if it's running as root. The policy says no, and the kernel enforces it.

AppArmor is the MAC framework on Debian and Ubuntu systems. Instead of labels, it uses path-based profiles — per-application rules that define exactly which files a process can access, which network operations it can perform, and which capabilities it can use. Easier to configure than SELinux, slightly less granular.

# ── SELinux (RHEL/CentOS) ──

# Check current SELinux status
sestatus

# Never disable SELinux — set to permissive temporarily for debugging
setenforce 0    # Permissive: logs violations but doesn't block
setenforce 1    # Enforcing: blocks and logs violations

# See recent SELinux denials
ausearch -m avc -ts recent | grep denied

# ── AppArmor (Ubuntu/Debian) ──

# Check AppArmor status and loaded profiles
aa-status

# See AppArmor denial events in syslog
grep "apparmor" /var/log/syslog | grep "DENIED" | tail -20

# Put a specific profile into complain mode (log but don't block)
aa-complain /usr/sbin/nginx

# Return to enforce mode
aa-enforce /usr/sbin/nginx

What just happened

Both frameworks follow the same operational pattern: enforcing mode blocks and logs, permissive/complain mode logs only. You switch to complain mode when configuring a new application to see what it needs without breaking it, then switch back to enforce once the profile is right. ausearch -m avc pulls access vector cache denials from the audit log — this is how you find out what SELinux stopped, which is also how you find out what an attacker was trying to do after compromising a process.

Never disable SELinux — fix the policy instead

The most common SELinux advice on the internet is "just disable it if it's causing problems." This is the security equivalent of removing a smoke detector because it keeps going off. SELinux denials mean your application is trying to do something outside its defined policy — which is exactly what an attacker would also be trying to do. The correct response is to understand the denial and update the policy to permit only what's legitimate. setenforce 0 in production is an open door.

Log analysis — finding the signal

Linux systems generate logs from the kernel, services, authentication stack, and audit framework simultaneously. The challenge isn't generating logs — it's finding the relevant entries before the attacker covers their tracks or the window for response closes. Knowing which log files contain what, and how to query them quickly, is a core operational skill.

# ── Key log locations ──
# /var/log/auth.log       — authentication, sudo, SSH (Debian/Ubuntu)
# /var/log/secure         — same, on RHEL/CentOS
# /var/log/syslog         — general system messages
# /var/log/kern.log       — kernel messages
# /var/log/audit/audit.log — auditd output (if enabled)

# ── journald — query structured logs fast ──

# All authentication events in the last hour
journalctl -u ssh --since "1 hour ago"

# Follow logs in real time (like tail -f but smarter)
journalctl -f

# Filter by priority — only errors and above
journalctl -p err --since today

# Show logs for a specific process
journalctl _PID=1337

# ── Triage: signs of compromise ──

# New user accounts created recently
grep "useradd\|adduser" /var/log/auth.log

# Cron jobs added by non-root users
grep "CRON" /var/log/syslog | grep -v "root"

# Commands run via sudo today
journalctl -u sudo --since today

What just happened

The triage block at the bottom is the incident response fast-check list. New user accounts created outside normal provisioning windows are a persistence technique — attackers create accounts to maintain access after a password change. Non-root cron jobs are another persistence method: a malicious cron entry that calls back to an attacker's server every five minutes survives reboots and most IR responses that don't specifically check cron. These three checks take under thirty seconds and cover the most common post-compromise persistence methods.

Detecting compromise on a live system

When you suspect a Linux system has been compromised, the first instinct is often to start changing things. Resist it. Changes destroy forensic evidence. The first pass should be purely observational — get a picture of what the system is doing right now before anything is touched.

# ── Live triage — observe before you touch ──

# Active network connections — who is the system talking to?
ss -antp

# Processes with open network connections
lsof -i -n -P

# Recently modified files (last 24 hours) — attacker tooling, backdoors
find / -mtime -1 -type f 2>/dev/null | grep -v /proc | grep -v /sys

# Check for rootkit indicators (requires rkhunter installed)
rkhunter --check --skip-keypress

# Loaded kernel modules — unusual modules can indicate rootkits
lsmod | sort

# Check /etc/passwd and /etc/crontab modification time
stat /etc/passwd /etc/crontab /etc/sudoers

# Bash history — may have been cleared, but check anyway
cat ~/.bash_history
cat /home/*/.bash_history 2>/dev/null

What just happened

ss -antp and lsof -i show every active network connection with the owning process — a reverse shell or C2 beacon will appear here. The recently modified files search is how you find attacker-dropped tools and backdoors — filter out /proc and /sys because those virtual filesystems change constantly. The stat check on critical files shows their last modification timestamp — if /etc/passwd was modified at 3am when no admin was working, that's your lead.

Instructor's Note

Linux security isn't about memorising commands — it's about building a mental model of what normal looks like on a system, so that abnormal is immediately obvious. A process you don't recognise. A network connection to an IP that shouldn't be there. A file modified at 3am. A cron job that wasn't there yesterday. The commands in this lesson give you the tools to ask the right questions. Running them regularly on systems you're responsible for — not just during incidents — is how you develop the intuition to spot the answer.


Practice Questions

The sudoers file must never be edited directly with a standard text editor. Which command must always be used to edit it safely, and why?




SELinux has two active operational modes. One logs violations but does not block them. The other both logs and blocks policy violations. What is the mode that actively blocks called?




The single sshd_config directive that completely eliminates brute-force password attacks as an attack vector against SSH. What is it?



Quiz

An attacker exploits a web server running as root on a system with SELinux enforcing. Despite having root-level code execution, they cannot read /etc/shadow or write to /var/spool/cron. What explains this?



During live triage of a suspected compromise, why is ss -antp one of the first commands to run?



After compromising a Linux server, an attacker runs useradd -m -s /bin/bash support and sets a password. What persistence technique does this represent and why is it effective?


Up Next · Lesson 13

Windows Security Fundamentals

Active Directory, Group Policy, Windows Defender, and the event log — the security stack that runs most corporate environments and the attack paths that target it.