#!/bin/sh # audit.sh - basic hardening audit of the server you run it on. # # curl -fsSL https://sitereliability.sh/audit.sh | sh # # Source: https://sitereliability.sh/audit.sh (read it before you pipe it) # Checksums: https://sitereliability.sh/checksums.txt # # Read-only: inspects local configuration. It never changes anything and never # needs sudo — checks it cannot make without root are reported as skipped. # Built for Linux servers; degrades gracefully elsewhere. set -u if [ -t 1 ]; then C_OK="$(printf '\033[32m')"; C_WARN="$(printf '\033[33m')" C_BAD="$(printf '\033[31m')"; C_DIM="$(printf '\033[2m')"; C_OFF="$(printf '\033[0m')" else C_OK=""; C_WARN=""; C_BAD=""; C_DIM=""; C_OFF="" fi PASS=0; WARN=0; FAIL=0 ok() { PASS=$((PASS+1)); printf ' %spass%s %s\n' "$C_OK" "$C_OFF" "$1"; } warn() { WARN=$((WARN+1)); printf ' %swarn%s %s\n' "$C_WARN" "$C_OFF" "$1"; } bad() { FAIL=$((FAIL+1)); printf ' %sfail%s %s\n' "$C_BAD" "$C_OFF" "$1"; } skip() { printf ' %sskip %s%s\n' "$C_DIM" "$1" "$C_OFF"; } printf '\nsitereliability.sh/audit.sh — %s, %s\n\n' "$(hostname 2>/dev/null || echo unknown-host)" "$(uname -sr)" # --- sshd ---------------------------------------------------------------- SSHD_CONF="/etc/ssh/sshd_config" if [ -r "$SSHD_CONF" ]; then # effective value = last uncommented directive; sshd_config.d overrides are # root-readable only on most distros, so note the limitation eff() { awk -v k="$1" 'tolower($1)==tolower(k){v=$2} END{print v}' "$SSHD_CONF"; } case "$(eff PermitRootLogin)" in no|prohibit-password) ok "sshd: root login is ${C_DIM}$(eff PermitRootLogin)${C_OFF}" ;; "") warn "sshd: PermitRootLogin not set (default varies by version)" ;; *) bad "sshd: PermitRootLogin is '$(eff PermitRootLogin)'" ;; esac case "$(eff PasswordAuthentication)" in no) ok "sshd: password authentication disabled" ;; "") warn "sshd: PasswordAuthentication not set explicitly (default is yes)" ;; *) bad "sshd: password authentication is enabled — keys only, please" ;; esac [ -d /etc/ssh/sshd_config.d ] && skip "sshd_config.d/ overrides need root to read — rerun with sudo for certainty" else skip "cannot read $SSHD_CONF (not present or needs root)" fi # --- firewall ------------------------------------------------------------ if command -v ufw >/dev/null 2>&1; then UFW_STATE="$(ufw status 2>/dev/null | head -1)" case "$UFW_STATE" in *active*) ok "ufw firewall is active" ;; *inactive*) bad "ufw is installed but inactive" ;; *) skip "ufw status needs root — run: sudo ufw status" ;; esac elif command -v nft >/dev/null 2>&1 || command -v iptables >/dev/null 2>&1; then skip "nftables/iptables rules need root to inspect" else warn "no host firewall tooling found (ufw/nftables/iptables)" fi # --- automatic security updates ------------------------------------------ if command -v apt-get >/dev/null 2>&1; then if [ -f /etc/apt/apt.conf.d/20auto-upgrades ] \ && grep -q 'Unattended-Upgrade "1"' /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null; then ok "unattended-upgrades is enabled" else warn "unattended-upgrades not enabled — security patches wait for a human" fi fi # --- filesystem hygiene --------------------------------------------------- UMASK="$(umask)" case "$UMASK" in 0022|022|0027|027|0077|077) ok "umask is $UMASK" ;; *) warn "unusual umask: $UMASK" ;; esac WW_PATH="" OLDIFS="$IFS"; IFS=: for d in $PATH; do [ -d "$d" ] || continue case "$(ls -ld "$d" 2>/dev/null | cut -c9)" in w) WW_PATH="$d" ;; esac done IFS="$OLDIFS" if [ -n "$WW_PATH" ]; then bad "world-writable directory on PATH: $WW_PATH" else ok "no world-writable directories on PATH" fi for f in /etc/passwd /etc/group; do [ -w "$f" ] && [ "$(id -u)" -ne 0 ] && bad "$f is writable by this non-root user" done if [ -e /etc/shadow ] && [ -r /etc/shadow ] && [ "$(id -u)" -ne 0 ]; then bad "/etc/shadow is readable by this non-root user" else ok "shadow file not readable by this user" fi # --- accounts ------------------------------------------------------------ if [ -r /etc/passwd ]; then UID0="$(awk -F: '$3==0 && $1!="root"{print $1}' /etc/passwd | tr '\n' ' ')" if [ -n "$UID0" ]; then bad "extra UID-0 account(s): $UID0"; else ok "root is the only UID-0 account"; fi fi if [ -r /etc/sudoers ] 2>/dev/null; then grep -E '^[^#]*NOPASSWD' /etc/sudoers >/dev/null 2>&1 \ && warn "sudoers contains NOPASSWD entries" \ || ok "no NOPASSWD in main sudoers" else skip "sudoers needs root to inspect" fi # --- summary ------------------------------------------------------------- printf '\n%d pass, %d warn, %d fail\n' "$PASS" "$WARN" "$FAIL" if [ "$FAIL" -gt 0 ]; then printf '%sverdict: needs attention%s\n' "$C_BAD" "$C_OFF" elif [ "$WARN" -gt 0 ]; then printf '%sverdict: decent, could be tighter%s\n' "$C_WARN" "$C_OFF" else printf '%sverdict: looking sharp%s\n' "$C_OK" "$C_OFF"; fi printf '\n%sHardening is half the job — the other half is noticing when things\ndrift. Cert, domain, and uptime watching: https://sitereliability.sh%s\n\n' "$C_DIM" "$C_OFF" [ "$FAIL" -eq 0 ]