
Recently I was requested to suggest some hardening advices about SSH access to *nix boxes. Now, I like to write down a short guide on this topic.
Let’s take a fresh installed FreeBSD server (11.2-RELEASE-p7 at the time of writing) with a direct connection to the Internet.
The protocol I’m using is Secure shell version 2, described in RFC 4251, included in FreeBSD 11.2 as OpenSSH_7.5p1 with OpenSSL 1.0.2o-freebsd.
The default port for SSH is TCP 22 as registered at IANA on document Service Name and Transport Protocol Port Number Registry.
You may want to specify a different TCP port to avoid receiving random probes by lazy kids around the world. When choosing a TCP port for your daemons, it’s important to bear in mind how the 16 bits space is articulated: 0-1023 are well-known ports assigned by IANA to system services (DNS, SMTP, HTTP, BGP, etc) by the “IETF Review” or “IESG Approval” procedures described in RFC 8126; 1024-49151 are user ports (OPENVPN, NFS, MYSQL, SIP, etc) assigned by IANA using the “IETF Review” process, the “IESG Approval” process, or the “Expert Review” process, as per RFC 6335; 49152-65535 are dynamic ports, not assigned.
The choice is up to you. In this guide I’ll use the TCP port 54321, so look at the following sshd_config example:
AddressFamily inet6 AuthenticationMethods publickey AuthorizedKeysFile .ssh/authorized_keys ChallengeResponseAuthentication no Ciphers aes256-ctr,[email protected],[email protected] ClientAliveCountMax 2 ClientAliveInterval 300 DenyGroups wheel DenyUsers root DisableForwarding yes HostKey /etc/ssh/ssh_host_ed25519_key HostKeyAlgorithms ssh-ed25519 KexAlgorithms [email protected] ListenAddress [2001:db8:48:64::1]:54321 LoginGraceTime 1m LogLevel DEBUG3 MACs [email protected],[email protected] MaxAuthTries 1 MaxSessions 4 MaxStartups 2:6:20 PermitRootLogin no PrintMotd no RekeyLimit default 10m SyslogFacility AUTH TCPKeepAlive no UseDNS no UsePAM no VersionAddendum none
You may have noticed that I specified publickey as the unique authentication method. It means that a user cannot gain access via user/password credentials but only by providing a valid key (private) that the server will validate against an authorized key (public) stored in his home directory.
Here is a reminder on how to generate a key:
ssh-keygen -t ed25519 -a 100 Generating public/private ed25519 key pair. Enter file in which to save the key (/home/exampleUSR/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): **************** Enter same passphrase again: **************** Your identification has been saved in /home/exampleUSR/.ssh/id_ed25519. Your public key has been saved in /home/exampleUSR/.ssh/id_ed25519.pub. The key fingerprint is: SHA256:kVlGbutC3oZgf8TSvtZXFlzvvEq2joMdaqJaW2RFHMM [email protected]:db8:48:64:80:96:112:ffff The key's randomart image is: +--[ED25519 256]--+ | o== | | .E. .| | +.o . o| | .= . o.| | ooS = o.| | .o= B . =| | . .= X..o o.| | . o. B.+= o. | | ..o. o...o=. | +----[SHA256]-----+
Here we have two output files.
id_ed25519, my private key:
-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCwPVoDZl tArbiR63jSvQwvAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAID4hSjQPAII1oYaR 895cmAjnNrROsK+CR5Oppj+jK2NuAAAAsNdO7sdO6tKGCgALwhZAvrVX0gHQAe/M7s0BDN 7/tA4Z+JaHiBMAeQjY4nQyTDVNfbVrdZiJ4oN+DZQHRHzUrTJImC11JCLLc+qvG4cLs/Ym EnTfwPLxxpphDVyU8VqVo+pw05fXf3xwip5PWH/oxCPsls7ckMgnlUFaHJPUibJ7lGjGPg +FjprZip2BuZMNmMmRbSMSvjL7i3MbSLutvRenAFt8ipsSHSjcTuOHZF22 -----END OPENSSH PRIVATE KEY-----
id_ed25519.pub, my public key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID4hSjQPAII1oYaR895cmAjnNrROsK+CR5Oppj+jK2Nu [email protected]:db8:48:64:80:96:112:ffff
What I should do now is to let my server authorize the key, so I put my public key content in .ssh/authorized_keys
cat id_ed25519.pub >> /home/exampleUSR/.ssh/authorized_keys
At this point I should be able to log into the server:
ssh -6 -i id_ed25519 [email protected]:db8:48:64::1 -p54321
What I have now is an SSH server listening on port TCP 54321 IPv6 only accepting one ed25519 key.
It is possible to harden this configuration further, so let’s enhance our sshd_config file:
AddressFamily inet6 AuthenticationMethods publickey AuthorizedKeysFile .ssh/authorized_keys ChallengeResponseAuthentication no Ciphers aes256-ctr,[email protected],[email protected] ClientAliveCountMax 2 ClientAliveInterval 300 DenyGroups wheel DenyUsers root DisableForwarding HostKey /etc/ssh/ssh_host_ed25519_key HostKeyAlgorithms ssh-ed25519 KexAlgorithms [email protected] ListenAddress [2001:db8:48:64::1]:54321 LoginGraceTime 1m LogLevel DEBUG3 MACs [email protected],[email protected] MaxAuthTries 1 MaxSessions 4 MaxStartups 2:6:20 PermitRootLogin no PrintMotd no RekeyLimit default 10m SyslogFacility AUTH TCPKeepAlive no UseDNS no UsePAM no VersionAddendum none ##### AllowUsers [email protected]:db8:48:64:80:96:112:ffff Subsystem sftp internal-sftp Match User exampleUSR ChrootDirectory %h ForceCommand internal-sftp
This way I have a tight rule: just exampleUSR can log on my server from that single specified IP. Moreover, that user can only use sftp commands inside his home directory.
On the public key side, I can put one more constraint:
from="2001:db8:48:64:80:96:112:ffff" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID4hSjQPAII1oYaR895cmAjnNrROsK+CR5Oppj+jK2Nu [email protected]:db8:48:64:80:96:112:ffff
So that public key will have the chance to pair only with a client coming from that IP.
If you’re still not happy, you can add a layer to check who tries to connect to TCP 54321 allowing just 2001:db8:48:64:80:96:112:ffff
echo "sshd : [2001:db8:48:64:80:96:112:ffff] : allow sshd : ALL : deny $(cat /etc/hosts.allow)" > /etc/hosts.allow
So when a connection from an unauthorized host happens, the server replies with:
Dec 22 15:52:08 sshd[60184]: twist sandbox.ironwall.io to /bin/echo "You are not welcome to use sshd from sandbox.ironwall.io."
This is just a short guide, a starting point for your research. It’s not meant to be complete, so please read all the relevant documentation.