Skip to content

SSH

SSH is the remote terminal, and on most Linux images it's the door an attacker tries first. The Mint benchmark, written for a workstation, simply says the server shouldn't be running (CIS 2.1.11); the Debian benchmark has a full section on hardening it (Debian CIS 5.1.1 to 5.1.24), and a contest README often requires it, so this page covers both cases. The item numbers below are the Debian ones. The server reads /etc/ssh/sshd_config and every file in /etc/ssh/sshd_config.d/. If the README doesn't require remote administration, stop and disable it; if it does, harden it.

Not required

sudo systemctl disable --now ssh
sudo ufw delete allow 22/tcp

Required: the settings

Edit /etc/ssh/sshd_config (sudo nano), one directive per line, no leading #. Files in sshd_config.d/ are read first and win, so check them for the same directives.

Directive Value Why
PermitRootLogin (5.1.20) no Root never logs in over the network; administrators log in as themselves and use sudo
PasswordAuthentication no if the README's users have keys; otherwise yes with the lockout below Key-only login can't be brute-forced
PermitEmptyPasswords (5.1.19) no A blank password can't be used remotely even if one exists
MaxAuthTries (5.1.16) 2 to 5 (use 3; the benchmark says 4 or fewer) Attempts per connection before the server drops it
LoginGraceTime (5.1.13) 30 to 60 seconds (use 60) An unauthenticated connection is dropped after this long, which limits how many an attacker can hold open
ClientAliveInterval (5.1.7) 60 to 600 seconds (use 300) with ClientAliveCountMax 3; STIG: 600 and 1 (UBTU-22-255030, 255035) Idle sessions are closed after interval × count with no response (the benchmark wants both above 0)
DisableForwarding (5.1.8) yes One switch that turns off X11, TCP, agent, and tunnel forwarding together
X11Forwarding no (STIG UBTU-22-255040, CAT I) No graphical forwarding through the tunnel
X11UseLocalhost yes (STIG UBTU-22-255045) If X11 forwarding is ever on, only local clients can reach the display
AllowTcpForwarding no No tunnelling other traffic through this server
AllowAgentForwarding no A compromised server can't use the admin's forwarded keys
PermitTunnel no No VPN-style tunnels
HostbasedAuthentication (5.1.10) no No trust based on the connecting machine's name
IgnoreRhosts (5.1.11) yes .rhosts files are ignored
GSSAPIAuthentication (5.1.9) no Kerberos-style auth that isn't in use is one less code path
MaxStartups (5.1.17) 10:30:60 After 10 unauthenticated connections, new ones are dropped with rising probability until 60; blunts connection floods
MaxSessions (5.1.18) 10 or less Sessions per connection
Ciphers (5.1.6) remove 3des-cbc, aes128-cbc, aes192-cbc, aes256-cbc, chacha20-poly1305@openssh.com CBC ciphers are weak; chacha20 had the Terrapin bug (CVE-2023-48795). The STIG (UBTU-22-255050 to 255060) goes further with explicit allow-lists, below.
KexAlgorithms (5.1.12, 5.1.23) remove diffie-hellman-group1-sha1, diffie-hellman-group14-sha1, diffie-hellman-group-exchange-sha1 SHA-1 key exchange
MACs (5.1.15) remove the MD5, RIPEMD, SHA1-96, and umac-64 variants Weak integrity checks
ListenAddress (5.1.24, Level 2) the machine's management address Only listens on one interface
PermitUserEnvironment (5.1.21) no Users can't set environment variables at login, which could redirect commands
Protocol 2 (on old images; modern OpenSSH ignores it) SSH 1 is broken
UsePAM (5.1.22) yes Login goes through the faillock and quality rules
Banner (5.1.5) /etc/issue.net A warning banner before login
AllowUsers or AllowGroups (5.1.4) The README's administrators Only listed accounts can even try
LogLevel (5.1.14) VERBOSE or INFO Logs the key fingerprint used, which the auth log otherwise omits

Apply

Create a drop-in file. A name starting 00- is read before the main file and other drop-ins, so its values win:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf

Start with the four that matter most, one directive per line, no leading #:

PermitRootLogin no
PermitEmptyPasswords no
MaxAuthTries 3
AllowUsers alice bob

Then work down the table and add the rest, using the README's administrators on the AllowUsers line. For the three algorithm lines, a leading - means "remove these from the default list" rather than replacing the whole list, which is safer than typing an allow-list by hand:

Ciphers -3des-cbc,aes128-cbc,aes192-cbc,aes256-cbc,chacha20-poly1305@openssh.com

For a STIG baseline, use its FIPS allow-lists instead (no leading -): Ciphers aes256-ctr,aes256-gcm@openssh.com,aes192-ctr,aes128-ctr,aes128-gcm@openssh.com, MACs hmac-sha2-512,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-256-etm@openssh.com, KexAlgorithms ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256. Both forms satisfy the CIS check; the allow-list is stricter and can refuse an old SSH client.

Before restarting, test the file, because a typo here locks you out:

sudo sshd -t
sudo systemctl restart ssh
sudo chmod 600 /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf

sshd -t prints nothing when the config is good and names the file and line when it isn't. The config file itself is root-only, mode 600 (5.1.1). Also fix the same directives in the main /etc/ssh/sshd_config where they're set the wrong way; some readers look only there.

Host keys

The server's private keys in /etc/ssh/ssh_host_*_key must be readable by root only (mode 600; Debian CIS 5.1.2) and the public halves 644 (5.1.3). World-readable host keys let anyone impersonate the server.

sudo chmod 600 /etc/ssh/ssh_host_*_key
sudo chmod 644 /etc/ssh/ssh_host_*_key.pub

Authorized keys

sudo find /home /root -name authorized_keys -exec sh -c 'echo "== $1"; cat "$1"' _ {} \;

A key in an account's ~/.ssh/authorized_keys is a password that never expires. The README says nothing about keys, so a key on an account is either the organization's (ask what the README says about remote access) or an attacker's. Remove lines you can't account for, and root's authorized_keys should be empty.

Verify

sudo sshd -T | grep -iE '^(permitrootlogin|passwordauthentication|permitemptypasswords|maxauthtries|logingracetime|clientaliveinterval|clientalivecountmax|disableforwarding|x11forwarding|allowtcpforwarding|permittunnel|permituserenvironment|hostbasedauthentication|ignorerhosts|gssapiauthentication|maxstartups|maxsessions|usepam|banner|allowusers)'
sudo sshd -T | grep -iE '^(ciphers|kexalgorithms|macs)' | grep -iE 'cbc|sha1|md5|umac-64'    # nothing
stat -c '%a %n' /etc/ssh/ssh_host_*_key

sshd -T prints the effective configuration after all files are merged, which is the only reliable way to check.

Example

sshd -T shows permitrootlogin yes, maxauthtries 6, permitemptypasswords yes, x11forwarding yes. /etc/ssh/sshd_config.d/ contains 99-cloud.conf with PasswordAuthentication yes, which is fine because the README's users have no keys. Write the hardening drop-in, restart, re-run sshd -T. ls -l /etc/ssh/ssh_host_rsa_key shows -rw-r--r--; chmod 600. /root/.ssh/authorized_keys has one key labelled backdoor@kali; delete the file.

Try it

  1. Set PermitRootLogin no and try ssh root@host from another VM. Then MaxAuthTries 3 and type four wrong passwords.
  2. Break the drop-in on purpose and run sshd -t to see what the error looks like.

Build it

Your hardened 00-hardening.conf, kept as a file and copied in, followed by sshd -t and a restart. Then sshcheck.sh: sshd -T filtered to the directives on this page with the expected value beside each.

Next

Network Kernel Settings