The previous lesson left Nimbus's code defending itself. But that code runs on something: servers with packages, services, users and permissions, and around forty laptops spread between the Valencia office and the homes of half the staff. That is where the python -m http.server nobody switched off has been alive since 01-04; that is where the unencrypted machines from 04-03's KPI are (92.3 % against a target of 98 %); and that is where the accounts with administrator privileges nobody needs are. This lesson applies 01-04's surface reduction to the operating system and turns "our servers are properly configured" into a baseline that is written down, applied by a machine and verified every week.
Contents
- What hardening is and what a baseline is
- Reference standards: CIS Benchmarks and STIGs
- Assess before you touch: lynis and OpenSCAP
- Hardening the Linux server, step by step
- Hardened SSH, directive by directive
auditd,fail2banand automatic updates- Patch management as a process
- The employee's endpoint
- Antivirus versus EDR
- Device management, BYOD and
osquery - Automating and verifying the baseline
- The legacy system that cannot be hardened
- What hardening is and what a baseline is
Hardening is reducing a system's attack surface: removing what is not used, closing what should not be open, limiting what each account can do and leaving a record of what happens. It is the application to the operating system of the principles from 01-03 — least privilege, secure defaults, defence in depth — and of the surface reduction from 01-04.
A freshly installed system is optimised to work in any scenario, not in yours. It comes with active services you will never use, generous permissions and convenient configurations. Hardening is the difference between the generic and yours.
The idea that holds everything else up is the baseline: a set of security configurations, written down, versioned and automatically applicable, that defines how every Nimbus server and every Nimbus laptop must be. Without a baseline three things happen: every machine ends up different, nobody knows which one is correct, and configuration drift is invisible.
| Without a baseline | With a baseline |
|---|---|
| "I think that server is fine" | "It complies with baseline v2.3, verified on 12 May" |
| Every machine is a special case | All identical; the differences are documented exceptions |
| A new server is configured by hand and no two are alike | It is created by applying the baseline in minutes |
| Drift is invisible | It is detected and corrected (§11) |
| There is no evidence for 04-03 or for an audit | The conformity report is the evidence |
flowchart LR
E["1. ASSESS\nlynis / OpenSCAP\non the real system"] --> D["2. DEFINE\nWritten and versioned\nbaseline (CIS L1)"]
D --> A["3. APPLY\nAnsible or golden image,\nNEVER by hand"]
A --> V["4. VERIFY\nweekly lynis +\nansible --check --diff"]
V -->|"difference = 0"| OK["Compliant:\ndated evidence"]
V -->|"difference != 0"| DR["DRIFT\nWho and why.\nMay be compromise (05-02)"]
DR --> A
- Reference standards: CIS Benchmarks and STIGs
There is no need to invent the baseline: public, detailed catalogues already exist.
| CIS Benchmarks | DISA STIG | |
|---|---|---|
| Who publishes them | The Center for Internet Security | The US Department of Defense |
| Style | Recommendations with rationale and impact | Mandatory requirements |
| Levels | Level 1 (secure without breaking anything) and Level 2 (high-security environments) | Categories I, II and III |
| Cost | Free as a PDF; the CIS-CAT Lite tool is free | Free |
| For Nimbus | Yes: CIS Level 1 for Ubuntu Server and for the laptops | Not applicable |
How they are really used, which is not how it is usually done. An Ubuntu benchmark has more than 300 controls; applying them all blindly produces, almost certainly, a server that will not boot or an application that stops working. The correct procedure has four steps:
- Assess first on a real system and see the current distance (§3).
- Filter by level and by role: all of Level 1; from Level 2, only what adds value in your context. A control about printing services does not apply to an API server.
- Test in preproduction, in small batches and including a reboot, because many controls only show their effect on restart.
- Document the exceptions with reason, owner and expiry date, in the register from 04-02. A control not applied and not documented is a silent deviation; documented, it is a decision.
The benchmark is a starting point, not a goal. 100 % conformity with unencrypted personal data and no verified backups is a worse posture than 80 % with the priorities properly chosen.
- Assess before you touch: lynis and OpenSCAP
# lynis: a quick audit, no dependencies, ideal for the first snapshot
sudo lynis audit system --quick --report-file /var/log/lynis-api-prod-1.dat
# OpenSCAP: a formal evaluation against the CIS Level 1 profile, with an HTML report
sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis_level1_server \
--results /var/log/oscap-results.xml \
--report /var/log/oscap-report.html \
/usr/share/xml/scap/ssg/content/ssg-ubuntu2204-ds.xmllynis --quickdoes not wait for confirmation between sections: that is what makes it runnable fromcron. It gives an indicative score and a prioritised list of suggestions.oscapevaluates against a formal profile (here, CIS Level 1 for a server) and produces a machine-readable XML plus a human-readable report. It is the tool that generates auditable evidence;lynisis the one used day to day.
[+] Boot and services
- Service Manager [ systemd ]
- Running services [ 41 ] <-- too many
[+] Software: services
! Found service listening on 0.0.0.0:8000 [ python3 ] <-- 01-04, still alive
! Found service listening on 0.0.0.0:6379 [ redis ]
[+] SSH Support
! PermitRootLogin is set to 'yes' [ SUGGESTED: no ]
! PasswordAuthentication is set to 'yes' [ SUGGESTED: no ]
[+] File systems
! /tmp is not a separated partition
! /home mounted without nosuid,nodev
[+] Hardening
Hardening index : 58 [############ ]
Suggestions (23):
- Install a file integrity tool (AIDE, Wazuh FIM) [FINT-4350]
- Enable process accounting / auditd [ACCT-9622]
- Configure automatic security updates [PKGS-7420]How to read this output without wasting time. The hardening index (58) is not the metric that matters: it is there to measure progress, not to show off. What matters are the three lines marked with ! in the services and SSH sections, because they describe real exposure: the port-8000 http.server is still there 94 days on, root can log in over SSH and passwords are accepted. Those three get fixed today; the 23 suggestions are spread over weeks.
- Hardening the Linux server, step by step
Minimise packages and services. Every active service is a surface and a stream of patches.
# What is really listening, and who started it
sudo ss -tulpn | grep LISTEN
# This is where the http.server from 01-04 finally dies: it is not "stopped", the
# cause is removed. If somebody started it by hand, it is killed and documented; if
# a forgotten systemd unit starts it, that unit is disabled so it does not come
# back on reboot.
sudo systemctl disable --now temporary-server.service
sudo rm /etc/systemd/system/temporary-server.service
sudo apt purge -y telnetd rpcbind avahi-daemon cups # unused services
sudo apt autoremove --purgeThe golden rule: if you do not know why it is there, switch it off in preproduction and watch for a week. It is faster and more honest than investigating the origin of every service.
Users and sudo with least privilege. No shared accounts — one named account per person — no passwords for services (system accounts with nologin), and sudo scoped per command where possible instead of ALL. Every use of sudo is logged and that log feeds the detections from 05-02.
Permissions and mounts. Mount options are a cheap and very effective control:
# /etc/fstab (extract)
/dev/vg0/tmp /tmp ext4 defaults,nodev,nosuid,noexec 0 2
/dev/vg0/home /home ext4 defaults,nodev,nosuid 0 2
/dev/vg0/var /var ext4 defaults,nodev 0 2noexec prevents binaries from being run off that partition, nosuid cancels the privilege-escalation bit and nodev blocks device files. noexec on /tmp cuts off at the root the most common pattern after an intrusion: downloading a tool to /tmp and running it.
Kernel parameters. sysctl hardens the network stack and the kernel's behaviour: disabling packet forwarding on machines that are not routers, ignoring ICMP redirects, enabling syncookies against SYN floods, restricting access to dmesg and to kernel traces, and enabling kernel.randomize_va_space=2 for address space randomisation. They are declared in /etc/sysctl.d/60-nimbus.conf and applied with sysctl --system.
AppArmor and SELinux are mandatory access controls: they limit what a process can do even when it runs as root. If the Nginx process is only allowed to read its configuration and write its logs, a compromise of Nginx cannot read /etc/shadow. On Ubuntu, AppArmor comes enabled and with profiles for the usual services; the recommendation for Nimbus is never to disable it — the first thing many people do when something does not work — and to learn to read its denials in the log.
- Hardened SSH, directive by directive
SSH is the administration door and therefore the preferred target.
# /etc/ssh/sshd_config - Nimbus servers
Port 22
# Changing the port is NOT a security control: it reduces the noise from automated
# scans, but it stops nobody who runs a targeted scan. The real control here is
# that 22 is only reachable from the bastion host (05-04).
PermitRootLogin no
# Nobody logs in as root. You log in with a named account and escalate with sudo,
# which leaves a trace of WHO did what. With direct root, the log says nothing.
PasswordAuthentication no
KbdInteractiveAuthentication no
# No passwords: this wipes out brute force, password spraying and credential
# stuffing (02-02) against this service in one move.
PubkeyAuthentication yes
AuthenticationMethods publickey
# Ed25519 public key only (03-06). For the consultancy's account (A-19) a second
# factor is also required: publickey,keyboard-interactive.
AllowUsers lucia ivan svc-deploy
# An explicit allowlist: a new account CANNOT log in over SSH until somebody adds
# it here. Deny by default applies to access too.
PermitEmptyPasswords no
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding yes # needed for ProxyJump from the bastion host
ClientAliveInterval 300
ClientAliveCountMax 2
# Closes idle sessions after 10 minutes: it shrinks the window of a session left
# open and forgotten on an unlocked laptop.
MaxAuthTries 3
MaxSessions 4
LoginGraceTime 30
# They limit attempts and half-open connections, and make probing expensive.
LogLevel VERBOSE
# Logs the FINGERPRINT of the key used on each access: without this you know that
# "lucia" logged in, but not with which of her keys. It is vital in an investigation.It is validated with sshd -t before reloading the service and, very importantly, a second session is tested before closing the first: an error in this file leaves the server unreachable.
auditd, fail2ban and automatic updates
auditd, fail2ban and automatic updatesauditd records system calls and file accesses. It is configured with a few well-chosen rules, because logging everything produces gigabytes of nothing useful:
# /etc/audit/rules.d/nimbus.rules
-w /etc/passwd -p wa -k identity # user changes
-w /etc/shadow -p wa -k identity
-w /etc/sudoers.d/ -p wa -k privileges # who widens permissions
-w /etc/ssh/sshd_config -p wa -k remote_access
-w /var/log/audit/ -p wa -k log_tampering # touching the logs = S1 alert
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_execution
-e 2 # configuration IMMUTABLE until reboot-w watches a path (-p wa = writes and attribute changes), -k tags the event so it can be searched for, and -e 2 freezes the rules: not even root can modify them without a reboot, which stops an attacker quietly disabling the auditing. These events feed detections D-07 and D-08 from 05-02 directly.
fail2ban reads the logs and temporarily blocks IPs that fail repeatedly. It is useful, but it is worth placing it correctly: with SSH reachable only from the bastion host and with no password authentication, fail2ban is the second line, not the first. Where it does add real value is in front of the API, complementing the rate limiting from 05-05, and it is one of the automatic responses 05-02 classified as safe because they are reversible.
Automatic security updates. On Nimbus's servers unattended-upgrades is enabled for the security repository only, with a night-time window, automatic reboot disabled and e-mail notification. The reasoning: the probability that a security patch breaks something is low; the probability that a server left unpatched for six weeks gets exploited is not. Packages critical to the service (the database) are excluded and patched in a planned window.
- Patch management as a process
Patching is not running apt upgrade: it is a process with an inventory, deadlines and verification, and it rests on the P0-P4 priority policy from 05-01.
| Step | What it involves at Nimbus |
|---|---|
| Inventory | Which machines exist and which versions they run. Without this there is no coverage (§10, osquery) |
| Intake | Distribution advisories, a CVE feed, CISA's KEV. Automated, not by chance |
| Prioritisation | P0 in 24 h, P1 in 7 days (control C-16), P2 in 30, P3 in 90 |
| Testing | Preproduction first; for exposed P0s, the risk is accepted and you patch now |
| Window | Tuesdays at 22:00, with notice to customers if there is an outage |
| Reboot | The step that gets postponed most: a patched kernel that has not been rebooted is still vulnerable |
| Verification | A rescan (05-01) confirming that the version changed |
The real trade-off is between availability and risk, and it is worth stating honestly: patching may break the service for a few minutes; not patching may cost you the service for days. The way to resolve it is not to pick a side, but to reduce the cost of patching: reproducible environments, zero-downtime deployment, and an automatic smoke test confirming that the application is still alive after the window. When patching costs twenty minutes instead of a whole night, the dilemma disappears.
And the special case: the pending reboot. Nimbus treats it as a finding with an owner and a deadline, not as a nuisance. needrestart shows which services are still using old libraries, and that list is reviewed in every window.
- The employee's endpoint
Forty laptops, half of them outside the office. It is asset A-14 and the starting point of most real intrusions.
| Control | Why | Status at Nimbus |
|---|---|---|
| Disk encryption (BitLocker, FileVault, LUKS) | A stolen laptop that is not encrypted is a notifiable breach (06-03); encrypted, it is an equipment issue | 92.3 %, target 98 % (C-11) |
| Custody of recovery keys | Without it, an encrypted disk that fails means permanent data loss | In the secrets manager, with two people having access |
| Automatic screen lock (5 min, with a password) | The simplest attack: an open laptop in a coworking space | By MDM policy |
| Account without administrator privileges | The control that stops the most attacks: without admin, malware cannot install itself system-wide or persist | Pending, it is the priority |
| Managed updates | An unpatched browser is the most common way in | Automatic, verified by osquery |
| Local firewall enabled | It protects you on hotel wifi, where there is no perimeter | Enabled by the baseline |
| Workstation backup | Ransomware on a laptop, or simply a spilled coffee | Sync + versioned backup |
| USB device control | The baiting from 02-03: a USB stick in the office car park | Mass storage blocked, named exceptions |
The account without administrator privileges deserves a paragraph of its own, because it is the control with the best cost/effectiveness ratio in the whole lesson and the one that generates the most resistance. Without privileges, most malware cannot install itself, cannot persist across reboots, cannot disable the antivirus and cannot read the credentials of other accounts on the machine. The usual objection — "developers need to install things" — is almost always solved with user-space package managers, containers and a one-off elevation path with logging. The real objection that remains is cultural, and it is handled by explaining it, not by imposing it.
- Antivirus versus EDR
| Traditional antivirus | EDR (Endpoint Detection and Response) | |
|---|---|---|
| How it detects | Signatures of known files, heuristics | Behaviour: what the process does, what it calls, who it talks to |
| Faced with new malware | Blind until there is a signature | Can detect it by what it does |
| Visibility | "Blocked" or nothing | A full process tree, with a timeline to investigate |
| Response | Quarantining the file | Isolate the machine, kill processes, collect evidence remotely |
| Cost (40 machines) | Included in the operating system | 1,500-4,000 €/year commercial; Wazuh, free, covers a good part of it |
Why EDR matters, stated without the marketing: the antivirus answers "is this file bad?", and the EDR answers "what has happened on this machine?". When Sara's laptop turns out to be compromised, the antivirus says it quarantined something; the EDR shows that an attachment opened PowerShell, that PowerShell downloaded a file, that the file was copied into the start-up folder and that it opened a connection to a domain registered three days ago. The first closes a ticket; the second lets you respond according to 04-05 and answer the only question that matters: how far did it get?
Recommendation for Nimbus with 18,000 €/year: Microsoft Defender (included) as the antivirus + the Wazuh agent as the visibility and response layer. It covers 80 % of what a commercial EDR gives at zero licence cost, in exchange for hours of Lucía's time. When the budget grows, a managed EDR is the first purchase from this lesson.
- Device management, BYOD and
osquery
osqueryMDM (device management) is what makes everything above applicable to forty machines without visiting each one. It applies the baseline, enforces encryption and locking, distributes updates, installs agents and — most importantly — allows remote wiping of a lost machine and continuously checks that the policy is still applied. For Nimbus, the realistic option is the MDM included with its identity/office provider, plus the Wazuh agent.
BYOD (personal devices) is where the technical meets the legal and the employment side. Nimbus cannot wipe Sara's personal phone or inspect her photos. The correct approach, aligned with POL-04 (Acceptable Use), is: access to e-mail and corporate tools only from the device's managed work container, with verifiable minimum requirements (PIN lock, encryption, up-to-date system, no root/jailbreak), remote wiping limited to corporate data, and prior written information about what the company can and cannot see and do. (Legal validation note: in Spain, controls over staff devices require prior notice and proportionality; the BYOD policy is best reviewed legally before it is applied. This is developed in 06-03.)
osquery turns the estate into an SQL database you can ask questions of. It is the tool that answers 04-03's metrics with data rather than estimates:
-- Which laptops do NOT have disk encryption? (KPI C-11, target 98 %)
SELECT h.hostname, h.hardware_serial, d.path, d.encryption_status
FROM disk_encryption d JOIN system_info h
WHERE d.encryption_status != 'encrypted';
-- Which users have local administrator privileges?
SELECT u.username, g.groupname FROM users u
JOIN user_groups ug ON u.uid = ug.uid JOIN groups g ON ug.gid = g.gid
WHERE g.groupname IN ('sudo', 'admin', 'wheel');
-- Out-of-date software and processes listening on the network (the real surface)
SELECT DISTINCT p.name, p.pid, l.address, l.port
FROM listening_ports l JOIN processes p ON l.pid = p.pid
WHERE l.address NOT IN ('127.0.0.1', '::1');The third query, run against the servers, is the one that would have found the http.server on day one. And all of them, scheduled and shipped to Wazuh, turn "we think 98 % is encrypted" into an exact, dated number, which is precisely what 04-03 requires as evidence.
- Automating and verifying the baseline
Why it is not applied by hand. Configuring thirty directives on a server takes two hours, it is done differently the second time, there is no record of what was changed and it cannot be repeated when the server is recreated. The baseline is applied with Ansible (or an equivalent) or baked into a golden image.
# roles/baseline/tasks/main.yml (commented extract)
- name: Services forbidden by the baseline
ansible.builtin.systemd:
name: "{{ item }}"
state: stopped
enabled: false # 'enabled: false' is what stops them coming back on reboot
loop: [rpcbind, avahi-daemon, cups]
- name: sshd configuration in line with the baseline
ansible.builtin.template:
src: sshd_config.j2
dest: /etc/ssh/sshd_config
validate: "/usr/sbin/sshd -t -f %s" # does NOT install the file if it is invalid:
notify: reload sshd # it avoids leaving the server unreachable
- name: Kernel parameters
ansible.posix.sysctl:
name: "{{ item.k }}"
value: "{{ item.v }}"
sysctl_file: /etc/sysctl.d/60-nimbus.conf
reload: true
loop:
- { k: net.ipv4.conf.all.accept_redirects, v: "0" }
- { k: net.ipv4.tcp_syncookies, v: "1" }
- { k: kernel.randomize_va_space, v: "2" }
- name: auditd rules
ansible.builtin.copy: { src: nimbus.rules, dest: /etc/audit/rules.d/nimbus.rules }
notify: reload auditdThree properties make this file valuable. It is idempotent: it can be run a thousand times and the result is the same, so it serves both to apply the baseline and to correct drift. It is the documentation, because it describes exactly how the fleet is configured, with no way of going out of date. And it is versioned, so every change to the baseline goes through review like any other code.
The natural evolution is immutable infrastructure: instead of modifying servers, you build an already hardened image and the servers are replaced on every deployment. It eliminates drift by definition — no server lives long enough to deviate — and, as an enormous security benefit, it destroys any attacker persistence on every deployment. For Nimbus's containerised API this is already the case, and it is developed in 05-07.
Verifying. The baseline is not applied and forgotten: it is checked.
# Every week, on every server, from the infrastructure CI
sudo lynis audit system --quick --cronjob | tee /var/log/lynis-$(date +%F).log
ansible-playbook baseline.yml --check --diff # what WOULD have changed = drift--check --diff is the gem: it runs the playbook without applying anything and shows what would deviate from the baseline. If the result is not empty, somebody touched something by hand and you need to find out who and why. It is completed with file integrity monitoring (Wazuh FIM) over /etc, /usr/bin and the SSH keys, whose alerts go to the 05-02 channel as one more detection: an unauthorised change to a server's configuration is a sign of compromise, not a curiosity.
- The legacy system that cannot be hardened
There is always one: a server with an application that only works with an old version, a supplier's machine you are not allowed to touch, a device controlling something physical. Denying it does not help; the correct answer is to isolate and compensate:
- Isolate it in its own network zone (05-04), with minimal ingress and egress rules: if it cannot browse, it cannot receive orders or exfiltrate.
- Compensate with controls around it: a reverse proxy with a WAF in front, authentication at the previous layer, exhaustive logging of everything that goes in and out.
- Monitor more, not less: being the most fragile system, it is the one that needs the most sensitive detections.
- A specific backup and recovery plan, because it will be the hardest to rebuild.
- Document it as an accepted risk with an expiry date in the register from 04-01, with a review date and, if possible, a replacement budget.
Nimbus has its own case: A-22, the preproduction environment. It is not that it cannot be hardened; it is that nobody has done it. The correct decision is to apply the same baseline as production — it costs the same, because it is automated — and to settle the inventory's "to be reviewed" classification once and for all.
Common Mistakes and Tips
- Applying a whole benchmark blindly. You break the service, lose the team's trust and the hardening project dies in the first week.
- Changing the SSH port and believing it is a control. It reduces noise; it stops nobody. The control is that it is only reachable from the bastion host.
- Reloading
sshdwithout validating and without a second session open. It is the classic way to lock yourself out of a production server. - Disabling AppArmor or SELinux "because something does not work". You adjust the profile, you do not switch off the control.
- Patching without rebooting. An updated kernel that has not been rebooted is still vulnerable, and the patching metric lies.
- Leaving everybody with local administrator privileges. It is the control that stops the most attacks and the one most often postponed.
- Applying the baseline by hand. It is not repeatable, it leaves no record and it guarantees no machine is like any other.
- Tip: start by measuring. Run
lynison the three servers today: in twenty minutes you will have the prioritised list of what needs doing, for free. - Tip: a weekly
--check --diffin CI. It is the cheapest way to detect configuration drift and unauthorised change. - Tip: the first
osqueryquery you should run is the disk encryption one. It turns an estimated KPI into an exact number, and it usually holds a surprise.
Exercises
Exercise 1 — Prioritise from an audit
Lucía runs lynis against api-prod-1 and gets, in addition to the index of 58, these findings:
! Service listening on 0.0.0.0:8000 [python3]
! PermitRootLogin yes / PasswordAuthentication yes
! No file integrity tool installed
! /tmp not a separate partition
! auditd not running
! 14 security updates available (2 kernel)
! Firewall (nftables) not configured
! Default umask 022- Rank the eight findings by priority, justifying the criterion.
- State which are fixed today and which require a maintenance window, and why.
- Which of them is not resolved on this server, but at another layer?
Exercise 2 — Design the laptop baseline
Marta approves a budget to get the 40 laptops in order. Define the workstation baseline:
- Ten controls with their rationale, ordered by impact.
- For each one, how it is automatically verified that it is still applied.
- What you would do with the developer who demands permanent administrator privileges.
Exercise 3 — Interpret a configuration drift
The weekly ansible-playbook --check --diff returns this for api-prod-2:
TASK [baseline : sshd configuration] ****************
--- before: /etc/ssh/sshd_config
+++ after: /etc/ssh/sshd_config
-PasswordAuthentication yes
+PasswordAuthentication no
-AllowUsers lucia ivan svc-deploy support-tmp
+AllowUsers lucia ivan svc-deploy
TASK [baseline : auditd rules] **********************
--- before: (file absent)
+++ after: /etc/audit/rules.d/nimbus.rules
changed: [api-prod-2]Interpret each difference, state which is most serious and what actions you would take.
Solutions
Exercise 1
(1) Ranking by priority, applying the criterion from 05-01 — exposure and exploitability first, not nominal severity:
| Rank | Finding | Justification |
|---|---|---|
| 1 | python3 listening on 0.0.0.0:8000 |
An unauthorised service, exposed, serving an unknown directory, active for months. It is current exposure, not hypothetical |
| 2 | PasswordAuthentication yes + PermitRootLogin yes |
It turns SSH into a brute-force target against root. Two lines of configuration |
| 3 | 14 security updates, 2 of them kernel | Known vulnerabilities with patches available. They are cross-referenced against KEV: if any is in the catalogue, it moves to first place |
| 4 | Local firewall not configured | Defence in depth behind the security group from 05-04. Important, but there is already a layer in front |
| 5 | auditd stopped |
It is not exposure: it is blindness. Without it there is no D-07/D-08 detection and no forensic evidence |
| 6 | No file integrity tool | Same category: visibility, not exposure |
| 7 | /tmp without a separate partition |
It prevents noexec, which cuts a common post-intrusion pattern. It requires disk work |
| 8 | umask 022 |
Low risk (new files readable by everyone). It is fixed along with the rest of the baseline |
(2) Today versus a window. Fixed today, with no outage: the http.server (kill the process and remove the unit), the SSH directives (reloading sshd does not cut existing sessions, always validating first with sshd -t and with a second session open), auditd, the local firewall — being careful to apply the rule allowing SSH before the default-deny policy — and the umask. Requiring a maintenance window: the 2 kernel updates, because they need a reboot and without it the vulnerability is still alive; and the separate /tmp partition, which means repartitioning the disk. The remaining 12 updates can be applied in working hours if they do not affect active services, although it is best to group them into the Tuesday window.
(3) The finding not resolved here is, strictly speaking, the exposure of port 8000: removing the process is the immediate fix, but the underlying question — why was an unauthorised service reachable from the Internet? — is answered in 05-04, with the security group that should only allow 443 from the load balancer. The complete answer combines two layers: hardening removes the service, and the network stops a future service being reachable. That is exactly the point of the defence in depth from 01-03.
Exercise 2
(1) and (2) Workstation baseline:
| # | Control | Rationale | Automatic verification |
|---|---|---|---|
| 1 | No local administrator privileges | The one that stops the most attacks: without it there is no installation and no persistence | Weekly osquery query for members of admin/sudo |
| 2 | Disk encryption with the key in custody | A theft goes from notifiable breach to an equipment issue | disk_encryption in osquery; KPI C-11 |
| 3 | Automatic system and browser updates | An unpatched browser is the most common way in | Version and date of the last patch via MDM/osquery |
| 4 | Screen lock after 5 minutes with a password | A trivial physical attack outside the office | MDM policy with a compliance report |
| 5 | Antivirus + Wazuh agent running | Detection and, above all, the visibility to respond | The agent's heartbeat; an alert if a machine stops reporting |
| 6 | Local firewall enabled | Outside the office there is no perimeter | osquery on the firewall's status |
| 7 | Always-on VPN outside the office (05-04) | Filtered DNS and controlled access from any network | Connections logged per user on the VPN server |
| 8 | Workstation backup | Local ransomware or physical loss | Date of last backup per machine, with an alert at 7 days |
| 9 | USB storage control | Baiting from 02-03 | MDM policy + auditd/osquery events |
| 10 | Inventory and documented offboarding | An uninventoried machine is not protected and cannot be wiped | Monthly reconciliation between MDM, HR and the inventory from 01-04 |
Verification is what separates this list from a decorative document: every control has a query that produces a dated number, and that number is the evidence 04-03 asks for and that a customer or an auditor will request in 06-04.
(3) The developer who asks for permanent administrator rights. You do not simply say no, nor simply say yes. First you find out the specific use case — usually installing dependencies, using containers or debugging — because almost all of them are covered without permanent privileges: package managers in user space, Docker with the right group, and pre-approved tools installed by the MDM. For whatever is left, you offer one-off elevation with logging: a route that grants privileges for a limited time, leaves a trace of what was done and does not survive a reboot. It is the same just-in-time model as A-19 applied to the workstation. If an irreducible case still remains, it is documented as an exception with an owner and an expiry date (04-02), compensated with reinforced monitoring on that machine, and reviewed every six months: almost all exceptions of this type stop being necessary before the first review.
Exercise 3
First difference — PasswordAuthentication yes on the server. Somebody disabled a baseline control by hand on a production server. It is the most serious item in the report for three reasons: it reopens password authentication, and with it brute force and credential stuffing; it is exactly what an attacker would do to guarantee a way back after losing their original access; and it is an unauthorised change, that is, an indicator of compromise until proven otherwise. Immediate actions: do not revert it silently. First, check the auditd log (-w /etc/ssh/sshd_config -p wa) to find out who and when; review successful password-based SSH logins since that moment; and only then reapply the baseline. If no legitimate explanation with a ticket turns up, it is treated as an S2 incident under 04-05.
Second difference — support-tmp in AllowUsers. A temporary account — the name gives it away — with SSH access to production, which outlived the reason that created it. It is the same family of problems as A-19's exception with no expiry and as the http.server: the temporary thing that stays. You check who uses it, when it was created, whether it has a key associated and what it did; you delete the account and not just the AllowUsers line; and you check whether equivalent accounts exist on the other servers.
Third difference — auditd rules absent. The file does not exist on api-prod-2. It is less alarming than the previous two because it may simply be that the machine was created before the rule entered the baseline, but it has a serious consequence for this very exercise: without auditd, the investigation of the first point may have no data, which is why you should check first whether the file ever existed. Action: apply the complete baseline and verify that it is present on the other servers.
Cross-cutting conclusion of the exercise. All three findings appeared because there was an automated baseline and a weekly check. Without --check --diff, the PasswordAuthentication yes would have stayed invisible until the next pentest or until somebody used it. That is the whole argument of the lesson: hardening is not applying configurations, it is keeping a difference equal to zero.
Conclusion
You have applied to the operating system the surface reduction that module 1 set out in the abstract. You know what hardening is and, above all, what a baseline is: a set of configurations written down, versioned and applicable by a machine, without which every server ends up different, nobody knows which one is correct and drift is invisible. You know the CIS Benchmarks and the STIGs, and the honest procedure for using them — assess, filter by level and role, test in preproduction and document exceptions — along with the warning that avoids the most common failure: applying 300 controls blindly breaks the service and kills the project in the first week. You know how to assess before you touch with lynis and OpenSCAP, and to read their output looking for real exposure rather than showing off an index.
You have the complete hardening of the Linux server: minimising services — where the python -m http.server from 01-04 finally dies, by removing the unit and not just killing the process — named accounts and scoped sudo, mounts with noexec, nosuid and nodev that cut off at the root the download-and-run pattern from /tmp, kernel parameters, and AppArmor/SELinux with the rule of tuning them rather than switching them off. You have a command of sshd_config directive by directive, with PermitRootLogin no, no passwords, Ed25519 keys only, AllowUsers as an allowlist, idle timeouts, LogLevel VERBOSE to record the fingerprint of the key used, and the warning that changing the port is not a control: what is a control is being reachable only from the bastion host. You know how to configure auditd with a few well-chosen rules and -e 2 to freeze them, where to place fail2ban as the second line, and how to enable automatic security updates with judgement.
You can handle patch management as a process — inventory, intake, P0-P4 prioritisation, testing, window, reboot and verification — with the key that dissolves the availability-versus-risk dilemma: reducing the cost of patching until it takes twenty minutes. You have the endpoint baseline with its eight controls, headed by the one that stops the most attacks and generates the most resistance — the account without administrator privileges — and you can distinguish antivirus from EDR by the question each one answers: "is this file bad?" versus "how far did it get?". You know what MDM contributes, how to approach BYOD in a way compatible with POL-04 and with the law, and how to use osquery to turn estimates into dated numbers, including the query that would have found the http.server on day one. And you take away what makes all of the above sustainable: automating with Ansible in an idempotent, versioned, self-documenting way, the evolution towards immutable infrastructure that destroys any persistence on every deployment, and the weekly verification with --check --diff, because hardening is not applying configurations but keeping a difference equal to zero. With the legacy system, the answer is to isolate, compensate, monitor more and document the risk with an expiry date.
Notice the last idea, because it is the bridge. Immutable infrastructure, golden images and the API's containers have been appearing throughout the lesson as the best way to apply a baseline, and yet classic hardening does not cover them: a container is not a virtual machine, it is not patched, it is rebuilt; and the cloud account that runs it has a surface of its own that no lynis looks at. That is where most breaches happen today, and not through an exploit but through a configuration error: a public bucket, a permission policy that is too broad, backups living in the same account as production. In Cloud and Container Security (05-07) we close the course's technical plan: shared responsibility on the technical side, identity as the new perimeter, bucket A-02 properly configured at last, network and backups isolated by account, cloud logging and alerting, automated posture with prowler and checkov, secure images with their Dockerfile before and after, signing with cosign, running with minimal capabilities and runtime detection with Falco.
Fundamentals of Information Security Course
Module 1: Introduction to Information Security
- Basic Concepts of Information Security
- Types of Threats and Vulnerabilities
- Principles of Information Security
- Assets, Attack Surface and Threat Actors
Module 2: Cybersecurity
- Definition and Scope of Cybersecurity
- Types of Cyber Attacks
- Social Engineering and Phishing
- Protection Measures in Cybersecurity
- Identity, Authentication and Access Control
- Cybersecurity Incident Case Studies
Module 3: Cryptography
- Introduction to Cryptography
- Symmetric Cryptography
- Asymmetric Cryptography
- Hash Functions, HMAC and Password Storage
- Cryptographic Protocols
- Key Management, Certificates and PKI
- Applications of Cryptography
Module 4: Risk Management and Protection Measures
- Risk Assessment
- Security Policies
- Security Controls
- Third-Party and Supply Chain Risk
- Incident Response Plan
- Disaster Recovery and Business Continuity
Module 5: Security Tools and Techniques
- Vulnerability Analysis Tools
- Monitoring and Detection Techniques
- Penetration Testing
- Network Security
- Application Security
- System Hardening and Endpoint Security
- Cloud and Container Security
Module 6: Best Practices and Regulations
- Best Practices in Information Security
- Security Regulations and Standards
- Personal Data Protection and GDPR in Practice
- Compliance and Auditing
- Training and Awareness
- Ethics, Legal Aspects and Responsible Disclosure
