When configured as described in the previous section, SSH allows for a fairly flexible and secure communication solution. The one thing that does not make SSH fully secure is its reliance on end users to verify host public key fingerprints. When a user attempts a connection to a host for the first time, it systematically asks to verify the server's key fingerprints:
[emoret@renault-4l ˜]$ ssh simca-1000
The authenticity of host 'simca-1000 (10.157.12.98)' can't be established.
RSA key fingerprint is 7c:a9:fd:19:4c:24:85:db:30:74:17:ff:42:64:43:6b.
Are you sure you want to continue connecting (yes/no)?
Security-conscious users should use an out-of-band method for verifying those public key fingerprints. Out-of-band methods could include calling an administrator with physical access to the server and having them read key fingerprints to you. This command would be used by the administrator to retrieve the host's public key fingerprints:
[root@simca-1000 ˜]# ssh-keygen -l -f /etc/ssh/ssh_host_rsa_key.pub
2048 7c:a9:fd:19:4c:24:85:db:30:74:17:ff:42:64:43:6b /etc/ssh/ssh_host_rsa_key.pub
Once the fingerprints for a given server are verified and accepted, they are automatically added to the local ˜/.ssh/known_hosts file.
Of course if the server is reinstalled later on and keys were not backed up, the SSH client complains and alerts you to a possible man-in-the-middle attack.
As an administrator, you should always make backups of your SSH keys and store them in a safe location. You usually find them under /etc/ssh/ssh_host_*.
[emoret@renault-4l ˜]$ ssh simca-1000
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
1c:7c:fb:4d:d4:a8:cc:bc:9e:49:09:6b:0b:af:27:ca.
Please contact your system administrator.
Add correct host key in /home/emoret/.ssh/known_hosts to get rid of this message.
Offending key in /home/emoret/.ssh/known_hosts:1
RSA host key for simca-1000 has changed and you have requested strict checking.
Host key verification failed.
Figure 15-5 shows a typical man-in-the-middle attack in which the attacker sits between a client and a server on the path of SSH traffic and provides his own SSH public keys instead of the legitimate server. Once the client accepts the new key, two SSH sessions are created. The first one initiated by the legitimate client to the attacker's machine, the second one started from the attacker's machine to the legitimate server. All traffic is encrypted on the wire, but the attacker is able to locally capture decrypted data. Penetration testing suites such as dsniff with its sshmitm tool makes it easy for anybody to perform such exploits. By exploiting ARP poisoning, it is even possible to not sit on the traffic's path and still perform man-in-the-middle attacks.
It is virtually pointless to rely on end users to secure an entire network's communications. Even some of the best security experts do not bother verifying every key fingerprint that is presented to them. Wouldn't it be nice to never have to verify fingerprints and still be sure that we are connecting to legitimate servers? Of course it would be, so rejoice because this is exactly what RFC-4255 was drafted to do.
In fact, this RFC describes a way to out-of-band validate SSH key fingerprints by using the fairly new SSHFP DNS resource records. Now I can hear you say "What? Relying on DNS security to validate SSH key fingerprints? Over my dead body!" You have a point, in that DNS is widely known to be insecure and subject to UDP spoofing and cache poisoning. Now if you look a little closer at RFC-4255, you will notice that using DNSSEC is a requirement. DNSSEC is a set of normalized DNS protocol extensions providing authenticated queries, data integrity, and authenticated denial of existence.
The following descrives what you need to do to put this in place.
DNS configuration is outside the scope of this chapter. Assuming you have a DNS server configured with at least one forward zone, here is how to add the new resource record containing the SSH host key fingerprints:
On the SSH server, add to the zone file:
[emoret@simca-1000 ˜]$ssh-keygen -r
simca-1000
-f /etc/ssh/ssh_host_rsa_key.pub
simca-1000 IN SSHFP 1 1 c277aaa97ccf1807296941fc8efba029482707e3 [emoret@simca-1000 ˜]$ssh-keygen -r
simca-1000
-f /etc/ssh/ssh_host_dsa_key.pub
simca-1000 IN SSHFP 2 1 a6c2f2f5662c0a98f448c32dea880330d6d7950d
This mechanism is not supported for SSH-1, so there is no need to perform a similar action with the /etc/ssh/ssh_host_ key.pub file.
On the DNS server, paste the output of the Step 1 commands into the proper zone file.
Enable DNSSEC by adding the following line to the options section of the /etc/named.conf file:
dnssec-enable yes;
Generate the DNS key-signing key (KSK) and zone-signing key (ZSK).
[root@renault-4l ˜]#dnssec-keygen -r /dev/urandom -f KSK -a RSASHA1
-b 1024 -n ZONE sabre.juniper.net
Ksabre.juniper.net.+005+65418 [root@renault-4l ˜]#dnssec-keygen -r /dev/urandom -a RSASHA1 -b 1024 -n ZONE
sabre.juniper.net
Ksabre.juniper.net.+005+32690
Add your public keys to your zone file:
[root@renault-4l ˜]# cat >> sabre.juniper.net.db
$include Ksabre.juniper.net.+005+32690.key ; ZSK inserted 20040719
$include Ksabre.juniper.net.+005+65418.key ; KSK inserted 20040719
^D
Sign your zone file:
[root@renault-4l ˜]#dnssec-signzone -r /dev/urandom -o sabre.juniper.net -k
Ksabre.juniper.net.+005+65418 /var/named/sabre.juniper.net.db
Ksabre.juniper.net.+005+32690.key
You should end up with a signed zone file like /var/named/sabre.juniper.net.db.signed.
Edit your /etc/named.conf to refer to the signed zone file in place of the unsigned version, then restart your bind server.
On the SSH client side, add the configuration parameter allowing for DNS host key verification:
[root@simca-1000 ˜]#cat >> /etc/ssh/ssh_config
VerifyHostKeyDNS yes
^D
Verify that it is working. From the client side:
[emoret@simca-1000 ˜]$ ssh -v renault-4l.sabre.juniper.net
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: Connecting to renault-4l.sabre.juniper.net [10.157.12.99] port 22.
debug1: Connection established.
debug1: identity file /home/emoret/.ssh/identity type −1
debug1: identity file /home/emoret/.ssh/id_rsa type −1
debug1: identity file /home/emoret/.ssh/id_dsa type 2
debug1: Remote protocol version 2.0, remote software version OpenSSH_4.3
debug1: match: OpenSSH_4.3 pat OpenSSH*
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_4.3
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: server->client aes128-cbc hmac-md5 none
debug1: kex: client->server aes128-cbc hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP
debug1: SSH2_MSG_KEX_DH_GEX_INIT sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_REPLY
debug1: found 2 insecure fingerprints in DNS
debug1: matching host key fingerprint found in DNS
The authenticity of host 'renault-4l.sabre.juniper.net (10.157.12.99)'
can't be established.
RSA key fingerprint is 83:67:04:09:64:74:1e:b8:25:6a:79:1d:d1:29:83:5c.
Matching host key fingerprint found in DNS.
Are you sure you want to continue connecting (yes/no)?
As you can see at line 22, the host key fingerprints for renault-4l were found on the DNS server. However, line 21 shows that those fingerprints were retrieved in an insecure manner. It turns out that for the client machine to send DNSSEC queries, the resolver needs to implement EDNS0 extensions as described in RFC-2671. Fedora Core 5, at the time of this writing, does not implement these extensions. However, it seems that at least OpenBSD does, although not by default. If you are running OpenBSD, you have to enable this option in your DNS resolver by doing the following in the shell:
[emoret@openbsd ˜]$ export RES_OPTIONS=edns0
Remember that this is a global option for the resolver, meaning that all DNS servers configured on this host have to run DNSSEC and have their zones signed.
As an alternative to DNSSEC and SSHFP host key distribution, you could investigate the use of CFEngine to distribute host keys.
Another SSH shortcoming is that the various server implementations do not share the same key storage mechanism. This is particularly annoying when accessing a server for the first time and trying to push your public key to that server with the command line (see SSH Authentication). To remedy the situation, a protocol extension is currently being discussed by the IETF under the name draft-ietf-secsh-publickey-subsystem-07. With this new extension, a client will be able to send its public key to a server, which will then store it in the appropriate format and location.
In the meantime, while the just mentioned draft RFC is being finalized and implemented, I invite administrators to investigate the use of LDAP to automate users' public key storage. A project called Scalable Periodic LDAP Attribute Transmogrifier (SPLAT) will do just that.
Suppose you want to do backups to a remote machine over SSH, but you don't want to risk the private key used to start the remote backup command being used by a local unauthorized user to gain access to the backup machine. You could restrict SSH out-of-band to a given IP address using a firewall, but it may be too restrictive system-wise. The best approach would be to restrict remotely executable commands. OpenSSH supports a set of options in the authorized_keys file that were meant to provide such a feature. The options are configured on the remote hosts and associated with a given client's key. The following is a list of some of the most useful available options:
no-port-forwarding
, no-x11-forwarding
, no-agent-forwarding
These options take precedence over the ssh_config file on the client side. If you only want a backup user to run a single command, you probably do not want to allow any kind of forwarding.
permitopen="
host
:
port
"
This allows a user to specify which hosts and port numbers are allowed to be forwarded when the client uses the -L
command-line option.
from="
pattern-list
"
This option checks that a client's hostname matches a given pattern. The standard patterns include negation with !
and both the ?
and *
wildcards.
command="
local command
"
This option forces the execution of the given local
command
. It ignores any command passed by the client and does not automatically open a remote shell. The environment variable $SSH_ORIGINAL_COMMAND
should contain the command originally passed by the client.
Remember that options must be separated by commas and the last option should be followed by a blank space.
Here is an example: say I want to back up my home directory on simca-1000 to the remote renault-4l SSH server. The following code is what I could put in the authorized_keys file on renault-4l to remotely run a backup command on simca-1000 and then have that same host retrieve the backed up files locally. Note that the backup is initiated from the backed-up host with the scp command.
On the backup machine:
[emoret@renault-4l ˜]$cat ˜/.ssh/authorized_keys
no-port-forwarding,no-x11-forwarding,no-agent-forwarding
,from="simca-1000.sabre.juniper.net",command="
"if [ \"$SSH_ORIGINAL_COMMAND\" == \"scp -f .\" ];
then DATE='date +\"%Y.%m.%d-%H:%M:%S-%Z\"';
TEMP='mktemp -t emoret-$DATE.tar.bz2.XXXXXXXXXXXX';
tar jcfP $TEMP /home/emoret;scp -f $TEMP;rm -f $TEMP;fi"
ssh-dss AAAAB3NzaC1kc3MAAACBALiGKKac4JhleX6cRF7xVuZ0rm4Bsm23QKOrenmDoxdDEap7O7/ 4B2sq6J25Wm0kEIS/tnyHeXT57gZ+tEMmIKSNsTanK7hr5YsjBk6uSv22piWZaFmc8nCx SSN+fmAvakP1g0KtLsjH9Cb3Zxy7ZRVNK0Q5csMjiGAbs3W1QIRlAAAAFQDb1PeGkGDA8h XhEEbkL0slc5C0ZwAAAIEAryNsIdbdP7NjdUyvRg6Ra0voXguoubreAtTajuVmhMclQTw97 Dgxq8bfHT+dp0UMP/ebvyEbFjt2FiZW4g5l25KxftRTKrprwRR6seXlT70xOzscfCFaLbTM CyMBES7O5L2uTbqy+myxWL5Qe1MyYVDimyNhHvITwBpslWe11CkAAACAGxAGS8DX+UAt1yRB 5BEjwve7j5IZeG4mQJbsMeafPLQcy6wUrhzFvYevc7DV1iDNU/yCnrONrOlByI2+fnopshUs Ru2WZUW1rvObbSDNulR2mMhf9mnQHVyQGzYFlx+QzBomt5YFYnFT4NZhtoZg5hk7lTfMNs81 GFe2L08TfCM= emoret@simca-1000.sabre.juniper.net
scp -f $TEMP
sends file $TEMP
from the remote machine—in this case, the backed-up one to the local host, the backup server, over the encrypted tunnel.
Opening a regular SSH session from the backed-up machine to the backup server should not work:
[emoret@simca-1000 ˜]$ ssh renault-4l
Connection to renault-4l closed.
[emoret@simca-1000 ˜]$
However, running scp between the same two machines should actually trigger the backup of my home directory on simca-1000, pull it to renault-4l, and place it in the local /usr/local/backup directory.
[emoret@ simca-1000 ˜]$ scp renault-4l: /usr/local/backup
emoret-2006.09.20-19:13:18-PDT.tar.bz2.LDaUwyAW7197 100% 370KB 370.0KB/s 00:00
[emoret@simca-1000 ˜]$