Thwarting the Terrapin SSH Attack (Mitigation)

The Terrapin Attack is the biggest SSH vulnerability that we have seen in decades.  Terrapin splices TCP sequence numbers to truncate SSH extension negotiation. While this is a concern, effective exploitation of this vulnerability is very difficult, and mitigation is very easy! The paper notes that AES-GCM is not vulnerable to the attack:

AES-GCM (RFC5647) is not affected by Terrapin as it does not use the SSH sequence numbers. Instead, AES-GCM uses the IV obtained from key derivation as its nonce, incrementing it after sending a binary packet. In a healthy connection, this results in the nonce being at a fixed offset from the sequence number.

The original Encrypt-and-MAC paradigma from RFC4253 protects the integrity of the plaintext, thus thwarting our attack, which yields one pseudorandom block during decryption.

This means you can simply use the AES-GCM cipher in SSL communication by configuring your hosts to default to that protocol. The AES-GCM cipher has been supported in SSH since version 6.2 so pretty much all supported distributions going back to at least 2014 have an easy mitigation.

Mitigating on the server

Newer Servers that are using `update-crypto-policies` (RHEL, Alma, Rocky, Oracle, newer Ubuntu and Debian)

Newer operating systems that provide the command update-crypto-policies, create a new policy file and activate it.  You can modify `DEFAULT` to something different if you are using a different policy, but “DEFAULT” works for most systems.

# cat /etc/crypto-policies/policies/modules/TERRAPIN.pmod
cipher@ssh = -CHACHA20*
ssh_etm = 0

# update-crypto-policies --set DEFAULT:TERRAPIN
Setting system policy to DEFAULT:TERRAPIN
Note: System-wide crypto policies are applied on application start-up.
It is recommended to restart the system for the change of policies
to fully take place.

Older Servers are not using `update-crypto-policies`

Simply add this to the bottom of /etc/ssh/sshd_config:

Match All
Ciphers aes256-gcm@openssh.com

Note that Match all is added in case you have other match blocks. Of course, if you prefer, you can exclude the Match all line and insert the cipher above any match blocks so that it is a global option.

Forcing aes256-gcm is a bit of a hammer, it certainly works but may be more than you need.  Ultimately only Chacha20 and ETM-based MACs need to be disabled, so modify this as you see fit.

Testing Server Mitigation

The Terrapin research team has published a scanning/test tool.  There is GitHub page to compile it yourself, or pre-compiled binaries for the most common OS’s are available on the releases page.  The result should look something like this.  “Strict key exchange support” is unnecessary if the vulnerable ciphers are disabled.  As you can see, it says “NOT VULNERABLE” in green:

Output of a successful terrapin mitigation

Mitigating on the client

This can be done per user or system-wide.  To configure it for all users on a system, add this to the bottom of /etc/ssh/ssh_config:

Ciphers aes256-gcm@openssh.com

To configure it for a single user, add this to the top of the SSH configuration in your home directory (~/.ssh/config)

Ciphers aes256-gcm@openssh.com

Mitigating Windows SSH Clients

Unfortunately, the following Windows packages do not support AES-GCM and cannot be mitigated in this way:

If you use any of these then it would be a good idea to switch to an SSH client that supports AES-GCM.

Windows clients that support AES-GCM

Here are a few Windows clients that do support AES-GCM, in alphabetical order, by second letter:

The Fine Print

The examples above are one (very simple) way of dealing with this.  Ultimately you need to disable the ETM and ChaCha20 ciphers.  You can see what is configured in ssh as follows:

]$ ssh -Q mac|grep etm
hmac-sha1-etm@openssh.com
hmac-sha1-96-etm@openssh.com
hmac-sha2-256-etm@openssh.com
hmac-sha2-512-etm@openssh.com
hmac-md5-etm@openssh.com
hmac-md5-96-etm@openssh.com
umac-64-etm@openssh.com
umac-128-etm@openssh.com
]$ ssh -Q cipher|grep -i cha
chacha20-poly1305@openssh.com

-Eric

Forcing insserv to start sshd early

Many distributions are using the `insserv` based dependency following at boot time.  After a bit of searching, I found very little actual documentation on the subject.  Here’s the process:

  1. Add override files to /etc/insserv/override/
  2. The files must contain ‘### BEGIN INIT INFO’ and ‘### END INIT INFO’, else insserv will ignore them.
  3. Some have indicated that you can override missing LSB fields with this method, however, it does require the Default-Start and Default-Start options even though you wouldn’t expect to need to override those.
  4. The name of the file in /etc/insserv/override must be equal to the name in /etc/init.d *not* the name it “Provides:”.  In an ideal world, the name would be the same as provides—but in this case that isn’t always so.

For my purpose, I created overrides for all of my services in rc2.d with this script.  Note that the overrides are just copies of the content  from the /etc/init.d/ scripts:

cd /etc/rc2.d
# This is one long line; $f is filename, $p is the Provides value.
grep Provides * | cut -f1,3 -d: | tr -d : | while read f p; do perl -lne '$a++ if /BEGIN INIT INFO/; print if $a; $a-- if /END INIT INFO/' $f > /etc/insserv/overrides/$p;done

Note that the script writes the filename from the “Provides” field so you may need to change the filename if you have initscripts where /etc/init.d/script doesn’t match the Provides field.  Notably, Debian Wheezy does not follow this for ssh.  Provides is sshd, but the script is named ssh.

Next, I append sshd to the Require-Start line of all of my overrides:

cd /etc/insserv/overrides/
perl -i -lne 's/(Required-Start.*)$/$1 sshd/; print' *

This of course creates a cyclic dependency for ssh, so fix that one up by hand.  Feel free to make any other boot-order preferences while you’re in the overrides directory.  For this case, ssh  was made dependent on netplug.

Finally, run `insserv` and double-check that it did what you expected:

# cat /etc/init.d/.depend.start
TARGETS = rsyslog munin-node killprocs motd sysfsutils sudo netplug rsync ssh mysql openvpn ntp wd_keepalive apache2 bootlogs cron stop-readahead-fedora watchdog single rc.local rmnologin
INTERACTIVE =
netplug: rsyslog
rsync: rsyslog
ssh: rsyslog netplug
mysql: rsyslog ssh
ntp: rsyslog ssh
[...snip...]

Viola!  Now I can ssh to the host far earlier, and before services that can take a long time to start to troubleshoot in case of a problem.  In my opinion, ssh should always run directly after the network starts.

-Eric