How to harden SSH access to a FreeBSD 11.2 box

Recently I was requested to suggest some hardening advice 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.

Leave a Reply