This is a followup article to
Five
Minutes to a More Secure SSH. My impetus for writing the original
was seeing too many client's servers left in a default state where
they are vulnerable to brute-force attacks. In it, I basically
advocate three things:
- Disabling password authentication
- Disabling root login
- Enabling key-based authentication
Those recommendations still hold true and I would
encourage you to follow them. However, OpenSSH has many features and
there is more you can do to secure your SSH servers, without resorting
to external software.
Refresher and Important
Notes: The main OpenSSH server configuration file is called
sshd_config and will typically
be in the
/etc/ssh or
/etc/sshd directories. Like all of
the configuration files used by OpenSSH, it is in plain text and so
can be edited with any
text editor. After
editing your
sshd_config file,
you will need to reload your SSH server's configuration - restarting
the SSH daemon is not necessary. The command typically looks like this
(this is on Debian or Ubuntu):
/etc/init.d/ssh reload
or (on Red Hat/Fedora):
service sshd reload
Also be careful not to lock yourself
out of your SSH server when experimenting with these access
controls. It's a good idea to always have two SSH sessions into
the server, and to always make backup of the relevant configuration
files. If you log out of one session and get denied access, you still
have one active session to fix things.
Restricting Users and Hosts
OpenSSH allows you to restrict users and groups by host or IP
address. There are four different directives you can use in
your
sshd_config file (they
are evaluated in this order):
DenyUsers
AllowUsers
DenyGroups
AllowGroups
The format for all of them will be the same - a space-separated list
of users or group names, with optional host names. Here is an example:
AllowUsers vader@10.0.0.1 maul@sproing.evillittleman.net sidious tyranus@*.evillitleman.net
AllowGroups wheel staff
This tells sshd to only allow connections from the
user
vader and only from the
IP address
10.0.0.1. The
user
maul is also allowed, but
only from the host
sproing.evillittleman.net. User
sidious
is allowed from anywhere, and the
user
tyranus is also allowed,
from any host in
the
evillittleman.net domain
(the asterisk matches zero or more characters).
The
AllowGroups line allows
login only from users whose primary group name or supplementary group
list match one of 'wheel' or 'staff'.
Keep in mind that
using
AllowUsers
or
AllowGroups means that
anyone not matching one of the supplied patterns will be denied access
by default. Also, in order for sshd to allow access based on full or
partial hostnames, it needs to do a DNS lookup on the incoming IP
address. That means the connecting IP address must have a PTR
(reverse) entry that maps back to a real hostname. These aren't hard
to get if you have a static IP address, usually your ISP or server
hosting provider can do this for you on request.
In addition to the asterisk in hostname or group patterns, you can use
a question-mark to mean exactly one character, and an exclamation
point to negate the sense of a match:
* - Matches zero or more characters
? - Matches exactly one character
! - Negates the host pattern match
Note: In my tests,
using
! to negate the sense of
the hostname match did not work with
the
AllowUsers directive. It
only seems to work when used
with
authorized_keys file
restrictions (see below).
Restricting Access and Commands
SSH has the concept of
authorized
keys. If you are using key-based auth, like I suggested in
my
first
article, the user accounts on the SSH server will have
an
authorized_keys file (which
is by default in the
~/.ssh
directory of whatever user account you are logging into). This file
lists the public keys, one per line, that are authorized for access to
that account. Apart from just specifying which public keys are allowed
access, there are a some more options that you can use to further
restrict SSH sessions. Here are the most useful ones:
from='hostname1,hostname2,'' - Restricts access from the specified IP or hostname patterns
command='command' - Runs the specified command after authentication
no-pty - Does not allocate a pty (does not allow interactive login)
no-port-forwarding - Does not allow port forwarding
Here is an example showing part of
an
authorized_keys file:
from="deathstar.example.com,!jedi.example.com,10.0.0.?" ssh-rsa AAAAB5...2BQ== vader@evillittleman.net
from="pitofdespair.example.com",command="ls",no-pty,no-port-forwarding ssh-dss AAAAZ7...22Q== droidQBX12@evillittleman.net
The first line allows login with the specified RSA key from
deathstar.example.com, from
any host with IP address in 10.0.0.[0-9], but not from the
host
jedi.example.com. The
second line merely runs the 'ls' command whenever the specified DSA
key is used - it does not allow any other commands to be run, does not
allow interactive login, and does not allow port-forwarding. It also
restricts the source of that key to the
host
pitofdespair.example.com.
Running sshd on a Non-Standard Port
Admittedly this is an attempt at 'security through obscurity', but
that doesn't mean it's not useful when combined with other security
measures. You may not be able to restrict access by hostname or IP,
for example - you may always be sourcing your connections from a
dynamic IP address, or you may not be able to get a proper PTR record
created. It's quite simple, in
your
sshd_config file, just
change
Port=22
to
Port=nnnnn
(where
nnnnn is some high
port), then reload the sshd configuration. How do we pick a port
number? Some
are better than
others. First, assume that most port scans are being done
with
Nmap, and take a look at
the
nmap-services
file. This is a list of ports that Nmap will use by default if you
don't specify a port range on the command line. It's probably a fair
bet that most script-kiddies are using nmap is this manner. Just pick
a high port not on this list, most nmap scans won't notice it. You can
also use multiple
Port=
directives, meaning you can have sshd listen on multiple
ports. Connecting to an alternate port is also very easy, use the
following options depending on the command used:
ssh -p 65502 vader@deathstar.example.com
sftp -oPort=65502 vader@deathstar.example.com
scp -P 65502 deathstar_plans.doc vader@deathstar.example.com:
You can also edit
your client's
~/.ssh/config file, and
add the
Port= directive to one
of your host blocks:
...
Host evil
Hostname deathstar.example.com
User vader
Port 65502
...
Then just connecting with the
command
ssh evil will connect
with the specified user and port.
Hashing Known Hosts Files
When you connect to an SSH server, the ssh client stores the server's
hostname, IP address and host key in a file
named
known_hosts. It will by
default be in your
~/.ssh
directory. Having the IP addresses of the servers you connect to
regularly in plaintext can be a security risk if you are on a shared
host, or your client gets compromised (stolen laptop, for example). An
easy way to avoid this problem is to obscure the information in the
known_hosts file
by
hashing
it. Hashing your
known_hosts
file is easy, you just use
the
ssh-keygen command, giving
it the file path.
ssh-keygen -H -f ~/.ssh/known_hosts
While this hashes all existing host keys, any host keys that get added
to your
known_hosts file after
you hash it do
not get hashed
by default. To make it the default, add the
directive
HashKnownHosts to
your
~/.ssh/config file. Here
is an example of hashing
a
known_hosts file. First,
here is what the file looks like beforehand:
dmaxwell@kaylee:~/.ssh$ head known_hosts
10.100.6.151 ssh-rsa
AAAAB4NzaC1yc2EAAAABIwAAAIEAuVgRdptT3xsQoGkiNnJb4Zb02p07MaZX02MFs5JhoqmvV5X5Z/LEQH0S7ngSn3b8kQUnocGulJgLchwfThrd/1OkdyOKdpgXxH/rmDXfwh/MZBNBxnMWBa1HpXSc1gxyDfSSxo+VPa1NCP+ob0dWx4sI+JFJ5cVzbQng4rKp3x8=
10.100.6.162
ssh-rsa
AAAAB4NzaC1yc2EAAAABIwAAAIEAxpQuMJR4Dq/MmrpUryYlNbP+BIWgJlr0LAfaHTIU64Ho6F58Bb1QzlUeeHQSI9f6qFW9aPsBC3Gd5wgQBUj3byinXXHC/10c3vmb2aEujmyL6en2Pef4AN8bKgaRtJq2G/H4MkPWBzxqZPb/k9c3a26P/DjG4y01TMw9vCld+As=
...
Here we run the
ssh-keygen command:
dmaxwell@kaylee:~/.ssh$ ssh-keygen -H -f ~/.ssh/known_hosts
/home/dmaxwell/.ssh/known_hosts updated.
Original contents retained as /home/dmaxwell/.ssh/known_hosts.old
WARNING: /home/dmaxwell/.ssh/known_hosts.old contains unhashed entries
Delete this file to ensure privacy of hostnames
And here is what the file looks like afterward (Note that we deleted
the backup file when we were done):
dmaxwell@kaylee:~/.ssh$ head known_hosts
|1|PdThGCuhg23t9bcURxyitJTmfKk=|/z+Xvh4xPuDni8PTB5iK7KKnGdA= ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAIEAuVgRdptT3xsQoGkiNnJb4Zb02p07MaZX02MFs5JhoqmvV5X5Z/LEQH0S7ngSn3b8kQUnocGulJgLchwfThrd/1OkdyOKdpgXxH/rmDXfwh/MZBNBxnMWBa1HpXSc1gxyDfSSxo+VPa1NCP+ob0dWx4sI+JFJ5cVzbQng4rKp3x8=
|1|vkLZ22nl30gyJ3gIX74FUF7b3eg=|uy5oSZ8avgZQZE+dwMd/mXGoA38=
ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAIEAxpQuMJR4Dq/MmrpUryYlNbP+BIWgJlr0LAfaHTIU64Ho6F58Bb1QzlUeeHQSI9f6qFW9aPsBC3Gd5wgQBUj3byinXXHC/10c3vmb2aEujmyL6en2Pef4AN8bKgaRtJq2G/H4MkPWBzxqZPb/k9c3a26P/DjG4y01TMw9vCld+As=
...
dmaxwell@kaylee:~/.ssh$
rm known_hosts.old