
Modular Bash toolkit that hardens Debian/Ubuntu systems for CyberPatriot competitions, automating account, firewall, SSH, PAM, and service hardening with logging and backups.
A modular Bash toolkit for hardening Debian/Ubuntu-family systems under competition time pressure. Built and refined across multiple seasons of the CyberPatriot National Youth Cyber Defense Competition, most recently placing Platinum tier in the Linux division at the 2025 semifinal round.
This is not a general-purpose compliance framework. It is a checklist automator for a six-hour timed exercise: it does the scriptable majority of a Linux hardening pass correctly, quickly, and idempotently, logs everything it touched, and leaves the judgment calls to the person running it.
CyberPatriot's Linux rounds score a live image against a rubric that rewards a fairly predictable set of hardening steps -- account hygiene, password policy, firewall configuration, service exposure, file permissions, patch level -- under a hard time limit, usually with no prior notice of exactly which vulnerabilities were planted. Doing that checklist by hand, correctly, under a countdown, is where teams lose easy points to typos and forgotten steps, not to unknown material.
This toolkit started as a single monolithic script written under exactly that pressure. This repository is a rewrite of that script: same checklist coverage, restructured into small, single-purpose modules that are easier to read, test, and reason about independently, with every non-obvious decision tied back to a specific CIS Benchmark section or NIST SP 800-53 control (see Security controls and references).
flowchart TD
A[bin/harden.sh] --> B[lib/common.sh<br/>logging, backups, run wrapper]
A --> C[Service-role prompts<br/>or --config file]
A --> D[lib/packages.sh<br/>updates, attack-tool removal]
A --> E[lib/firewall.sh<br/>default-deny + ufw]
A --> F[lib/ssh.sh]
A --> G[lib/services.sh<br/>samba/ftp/mail/http/mysql/dns]
A --> H[lib/users.sh<br/>account review, hidden UID 0]
A --> I[lib/kernel.sh<br/>sysctl hardening]
A --> J[lib/pam.sh<br/>password policy, lockout]
A --> K[lib/filesystem.sh<br/>permissions, cron, banners]
A --> L[lib/monitoring.sh<br/>fail2ban, auditd, rkhunter]
A --> M[lib/forensics.sh<br/>baseline snapshot]
D & E & F & G & H & I & J & K & L & M --> N[(~/hardening-run/<br/>log + backups + baseline)]
Every module is sourced by bin/harden.sh, which owns argument parsing,
the service-role questionnaire, and execution order. Modules do not call
each other directly, and every state-changing command in every module
goes through the run() wrapper in lib/common.sh, which gives the
whole project one place to implement dry-run support, consistent logging,
and non-fatal error handling.
git clone <this-repo>
cd cyberpatriot-linux-hardening
sudo ./bin/harden.sh
You will be asked a short series of yes/no questions about the machine's
role (does it need Samba, FTP, SSH, a web server, and so on), then it
runs unattended through the modules listed above. A log, a full set of
timestamped config backups, and a system baseline snapshot are written to
~/hardening-run/.
In an actual round, skip the per-package confirmation prompts and answer the role questions from a prepared answer file instead of typing them live:
cp examples/config.env.example my-machine.env
# edit my-machine.env for this box's actual role
sudo ./bin/harden.sh --config my-machine.env --auto-approve
Want to see exactly what it would do before it touches anything?
sudo ./bin/harden.sh --dry-run --config my-machine.env
| Module | Does |
|---|---|
lib/packages.sh | Full system update; removes password crackers and exploitation tools automatically; reviews dual-use tools (nmap, Wireshark, netcat) and legacy services (VNC, NFS, telnet) before removing them |
lib/firewall.sh | Default-deny inbound / default-allow outbound via ufw, plus an explicit block on a known common backdoor port |
lib/ssh.sh | Modern ciphers/KEX/MACs, no root login, connection and session limits -- or removes SSH entirely if the role doesn't need it |
lib/services.sh | Samba, FTP, mail, printing, MySQL, HTTP, DNS: each is installed and minimally hardened if the role needs it, or purged and firewalled off if not |
lib/users.sh | Interactive review of existing accounts (admin rights, deletion, password reset), detection of hidden UID-0 accounts and empty passwords |
lib/kernel.sh | Network-stack and kernel self-protection sysctl settings (source routing, ICMP redirects, ASLR, ptrace scope, dmesg/kptr restriction) |
lib/pam.sh | Password complexity and history via pam_pwquality/pam_pwhistory, account lockout via pam_faillock, password aging in login.defs |
lib/filesystem.sh | Core file permissions, cron/at restriction, a minimal rc.local, legal login banners, read-only SUID/world-writable/unowned-file scan |
lib/monitoring.sh | fail2ban and auditd by default; ClamAV and a full rkhunter/chkrootkit sweep are opt-in (see Competition safety notes) |
lib/forensics.sh | Read-only snapshot of users, processes, listening ports, and installed packages for later comparison |
tools/find-port-owner.sh and tools/list-nonstandard-users.sh are
small standalone utilities for the same kind of triage work, usable
independently of the main script -- see their headers for usage.
A hardening script that breaks the machine it's supposed to protect is worse than useless in a timed round. A few defaults reflect that, and are worth understanding before you run this unattended: