Skip to content

Instantly share code, notes, and snippets.

@lucabased
Last active January 7, 2025 16:02
Show Gist options
  • Select an option

  • Save lucabased/b2ea276aec5b1a0f8faad1b6c910a13c to your computer and use it in GitHub Desktop.

Select an option

Save lucabased/b2ea276aec5b1a0f8faad1b6c910a13c to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
#
# harden_lynis.sh
#
# Description:
# This script automates several recommendations from a Lynis scan
# on a Debian/Ubuntu-like system. It installs recommended packages,
# modifies SSH and password policies, restricts some kernel modules,
# and sets up basic banners. Many items require manual review or
# additional configuration to be truly effective.
#
# Usage:
# 1. Make executable: chmod +x harden_lynis.sh
# 2. Run as root: sudo ./harden_lynis.sh
#
# DISCLAIMER:
# - This is a DEMO script. Some changes may BREAK your system if
# not reviewed or tested. Always BACK UP your system first.
# - Partitioning changes, GRUB passwords, advanced MAC frameworks,
# or rewriting file systems are not fully automated here.
# - Adjust steps according to your environment, especially if
# certain services or protocols are needed.
set -euo pipefail
#=== HELPER FUNCTIONS =================================================
info() {
echo -e "\e[32m[INFO]\e[0m $*"
}
warning() {
echo -e "\e[33m[WARN]\e[0m $*"
}
error() {
echo -e "\e[31m[ERROR]\e[0m $*" >&2
}
check_root() {
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root."
exit 1
fi
}
backup_file() {
local file="$1"
if [[ -f "$file" ]]; then
cp -v "$file" "${file}.bak.$(date +%Y%m%d%H%M%S)"
info "Created backup for $file"
else
warning "$file not found, skipping backup."
fi
}
#=== 1) INSTALL RECOMMENDED PACKAGES ==================================
install_packages() {
info "Installing recommended packages per Lynis suggestions..."
apt update
apt -y install \
libpam-tmpdir \
apt-listbugs \
needrestart \
debsums \
apt-show-versions \
libpam-pwquality \
sysstat \
acct
# Explanation:
# - libpam-tmpdir: sets $TMP/$TMPDIR for each user session
# - apt-listbugs: see critical bugs before installing/upgrading
# - needrestart: check which daemons need a restart after updates
# - debsums: verify installed package files against checksums
# - apt-show-versions: list available package versions
# - pam-cracklib: enforce stronger password policy
# - sysstat: collects accounting data (CPU, I/O usage, etc.)
# - acct: enables process accounting
# Initialize or enable some of these if needed
info "Enabling sysstat..."
sed -i 's/ENABLED="false"/ENABLED="true"/' /etc/default/sysstat || true
systemctl enable sysstat
systemctl start sysstat
info "Enabling process accounting (acct)..."
systemctl enable acct
systemctl start acct
}
#=== 2) PASSWORD & AUTH HARDENING =====================================
harden_auth() {
info "Hardening password and authentication settings..."
# Install libpam-pwquality
apt update
apt install -y libpam-pwquality
# Configure /etc/security/pwquality.conf
local pwquality_conf="/etc/security/pwquality.conf"
backup_file "$pwquality_conf"
cat <<EOF >"$pwquality_conf"
minlen = 12
minclass = 3
difok = 4
dictcheck = 1
maxrepeat = 3
EOF
# Configure PAM in /etc/pam.d/common-password
local pam_common_pwd="/etc/pam.d/common-password"
backup_file "$pam_common_pwd"
sed -i \
's/^\s*password\s\+requisite\s\+pam_cracklib.so.*/# Removed pam_cracklib/' \
"$pam_common_pwd"
echo "password requisite pam_pwquality.so retry=3 minlen=12 difok=4 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root" >> "$pam_common_pwd"
# Enforce password aging policies
local login_defs="/etc/login.defs"
backup_file "$login_defs"
sed -i 's/^\s*PASS_MIN_DAYS.*/PASS_MIN_DAYS 1/' "$login_defs"
sed -i 's/^\s*PASS_MAX_DAYS.*/PASS_MAX_DAYS 90/' "$login_defs"
sed -i 's/^\s*PASS_WARN_AGE.*/PASS_WARN_AGE 7/' "$login_defs"
info "Password and authentication hardening completed."
}
#=== 3) SSH HARDENING =================================================
harden_ssh() {
info "Hardening SSH configuration..."
local ssh_config="/etc/ssh/sshd_config"
backup_file "$ssh_config"
# Example changes from Lynis:
# - Change SSH port from 22 -> e.g. 2222 (adjust as desired)
# - Reduce MaxAuthTries from 6 to 3
# - Reduce MaxSessions from 10 to 2
# - Disable compression
# - Set LogLevel to VERBOSE
# - Disallow AgentForwarding, TCPForwarding, X11Forwarding, etc.
sed -i 's/^#\?Port .*/Port 2222/' "$ssh_config"
sed -i 's/^#\?MaxAuthTries .*/MaxAuthTries 3/' "$ssh_config"
sed -i 's/^#\?MaxSessions .*/MaxSessions 2/' "$ssh_config"
sed -i 's/^#\?Compression .*/Compression no/' "$ssh_config"
sed -i 's/^#\?AllowTcpForwarding .*/AllowTcpForwarding no/' "$ssh_config"
sed -i 's/^#\?AllowAgentForwarding .*/AllowAgentForwarding no/' "$ssh_config"
sed -i 's/^#\?ClientAliveCountMax .*/ClientAliveCountMax 2/' "$ssh_config"
sed -i 's/^#\?TCPKeepAlive .*/TCPKeepAlive no/' "$ssh_config"
sed -i 's/^#\?LogLevel .*/LogLevel VERBOSE/' "$ssh_config"
# If you do rely on forwarding or other features, skip changing them to 'no'.
# Make sure the new port is open in your firewall:
ufw allow 2222/tcp || true
systemctl restart sshd
info "SSH port changed to 2222 (if that line wasn't commented out)."
}
#=== 4) GRUB PASSWORD (MANUAL STEP RECOMMENDED) =======================
set_grub_password() {
# Automating GRUB password can be tricky in a script. Typically:
# 1. Generate hashed password: `grub-mkpasswd-pbkdf2`
# 2. Add config to /boot/grub/grub.cfg or /etc/grub.d/40_custom
# We'll just demonstrate:
warning "Setting a GRUB password is recommended but can break headless servers."
warning "Manual step: run 'grub-mkpasswd-pbkdf2' then add it to /boot/grub/grub.cfg."
# Example lines might be:
# cat <<EOF >> /etc/grub.d/40_custom
# set superusers="root"
# password_pbkdf2 root grub.pbkdf2.sha512.10000...
# EOF
}
#=== 5) MISC HARDENING STEPS ==========================================
misc_hardening() {
# 5a) Restrict core dumps in /etc/security/limits.conf
local limits_conf="/etc/security/limits.conf"
backup_file "$limits_conf"
if ! grep -q "hard core 0" "$limits_conf"; then
echo "* hard core 0" >> "$limits_conf"
fi
# 5b) Set legal banners /etc/issue and /etc/issue.net
# WARNING: Some systems show these on login attempts.
echo "Authorized uses only. All activity may be monitored and reported." > /etc/issue
echo "Authorized uses only. All activity may be monitored and reported." > /etc/issue.net
# 5c) Enable process accounting (already done in install_packages)
# 5d) Enable sysstat data collection (already done in install_packages)
# 5e) AIDE config: Use stronger checksums (sha512). Typically in /etc/aide/aide.conf
local aide_conf="/etc/aide/aide.conf"
if [[ -f "$aide_conf" ]]; then
backup_file "$aide_conf"
# Example of forcing AIDE to use stronger checksums
sed -i 's/^Checksums = .*/Checksums = sha512/' "$aide_conf" || true
fi
# 5f) Possibly disable unneeded protocols: dccp, sctp, rds, tipc
# You can blacklist them in /etc/modprobe.d/blacklist.conf
local blacklist_conf="/etc/modprobe.d/blacklist-lynis.conf"
{
echo "blacklist dccp"
echo "blacklist sctp"
echo "blacklist rds"
echo "blacklist tipc"
} >> "$blacklist_conf"
info "Blacklisted protocols dccp, sctp, rds, tipc (if not needed)."
# 5g) Purge old packages
# We'll do a quick apt autoremove/clean. For a thorough purge, you'd do:
info "Purging old/removed packages..."
apt -y autoremove
apt -y autoclean
# If you know the package names that Lynis identified, do `dpkg --purge <pkg>`.
# 5h) Add your server's FQDN to /etc/hosts if missing
# Example FQDN: myserver.example.com
# Example IP: 192.168.1.10
# Adjust to your actual IP and hostname
# echo "192.168.1.10 myserver.example.com myserver" >> /etc/hosts
warning "Remember to add your system's IP, FQDN, and hostname to /etc/hosts if needed."
# 5i) Check or remove unneeded iptables rules (manual step)
# For a thorough approach, run: iptables -L --line-numbers
# 5j) Restrict file permissions
# This step can be large; here's a minimal example:
find / -xdev -type f -perm -0002 -exec chmod o-w {} \; 2>/dev/null || true
# More advanced checks require manual review of each path.
# 5k) Home directories check
# This sets 700 on all home directories:
for d in /home/*; do
if [ -d "$d" ]; then
chmod 700 "$d" || true
fi
done
info "Misc hardening steps completed. Some require manual verification."
}
#=== PRIVACY ==========================================================
randomize_hostname() {
info "Randomizing hostname..."
# Generate a random hostname (8 characters: letters and digits)
local new_hostname
new_hostname=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 8)
# Optionally, use a wordlist instead of random characters
# Uncomment the following lines if you prefer word-based hostnames:
# local wordlist=("alpha" "bravo" "charlie" "delta" "echo" "foxtrot" "golf" "hotel")
# new_hostname="${wordlist[$RANDOM % ${#wordlist[@]}]}-$RANDOM"
info "Generated random hostname: $new_hostname"
# Backup the current hostname
local current_hostname
current_hostname=$(hostname)
echo "$current_hostname" > /etc/hostname.bak
info "Current hostname backed up to /etc/hostname.bak"
# Set the new hostname (temporary and persistent)
hostnamectl set-hostname "$new_hostname"
# Update /etc/hosts for the new hostname
if grep -q "$current_hostname" /etc/hosts; then
sed -i "s/$current_hostname/$new_hostname/g" /etc/hosts
else
echo "127.0.0.1 $new_hostname" >> /etc/hosts
fi
info "Hostname changed to $new_hostname"
}
#=== MAIN EXECUTION ===================================================
main() {
check_root
install_packages
harden_auth
harden_ssh
# set_grub_password
misc_hardening
randomize_hostname
info "All steps completed. Review any warnings above, then reboot if needed."
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment