SSH replaces Telnet, finally

Last weekend I disabled the Telnet daemon on every machine I administer.

This is later than it should have been. SSH has existed since 1995 in usable form. The case for replacing Telnet has been clear for years. I had been running both, with the half-formed excuse that some old client of mine somewhere might still need Telnet. Last weekend I checked. It did not. So I removed it.

I want to write about why this transition matters more than people give it credit for, and about a few details of running SSH that have surprised me.

What is wrong with Telnet, exactly

Telnet sends everything in the clear. Specifically: when you log in over Telnet, the username and password traverse the network as plain text. Anyone in a position to read the network traffic between you and the server — every router on the path, anyone on a shared network segment, anyone running tcpdump on a hub — can read your credentials.

This is bad enough at home, where you control most of the path. It is much worse at any office, ISP, or transit network, where you do not. Anyone with a packet sniffer and access to the right segment can collect a working credential to your shell account.

The attack on Telnet credentials is not theoretical. It is one of the oldest active attacks on the internet. Compromised university machines have been used as Telnet credential collectors continuously since at least the early 1990s. There are tools that do nothing else.

Why SSH solves this and a few other things

SSH — Secure SHell, originally Tatu Ylönen's work, now also implemented in the OpenSSH project from the OpenBSD team — encrypts the entire session. The username, the password, the commands you type, the output you receive: all of it.

The protocol does several other things on the side that turn out to matter:

Server authentication. When you connect to a server for the first time, SSH stores the server's public key. On subsequent connections, it verifies that the server presents the same key. This protects you against a particular attack — DNS hijacking, ARP spoofing — where you think you are connecting to your server but are in fact connecting to the attacker's machine. The first connection is still vulnerable to this, but every subsequent one is not.

Public-key authentication. Once you have a working key pair, you can authenticate without ever sending a password. The server holds your public key; the client proves possession of the matching private key cryptographically. The password vulnerability — to keystroke loggers, to shoulder surfing, to weak password hashes leaked from the server — does not apply.

Port forwarding. SSH can transparently tunnel arbitrary TCP traffic over the encrypted channel. This is more useful than people realise. A workstation behind a firewall can SSH out, then forward an arbitrary local port to a remote service through the tunnel. It is a simple, well-understood tunnelling primitive.

What I did over the weekend

A short procedure that worked:

  1. Install OpenSSH from source on every machine. The Slackware packages are a few revisions behind, and SSH security advisories are too important to be running old code. Build, install to /usr/local, point startup scripts at the new binary.
  2. Generate a new keypair on my workstation: ssh-keygen -t rsa -b 2048 -f ~/.ssh/id_rsa. The 2048-bit RSA key is the current sensible choice; 1024 was the old default and is starting to feel marginal.
  3. Distribute the public key to ~/.ssh/authorized_keys on every server.
  4. Edit sshd_config on every server to disable password authentication once I had verified the key worked. The directive is PasswordAuthentication no. This stops people brute-forcing my password — they can no longer try.
  5. Disable Telnet in inetd.conf, restart inetd. Verify that telnet myserver 23 is refused.
  6. Verify TCP wrappers are scoping sshd correctly.

This took an afternoon. I had been putting it off for months.

The non-obvious follow-up

The ~/.ssh/known_hosts file on the client is an important security boundary. It contains the public keys of every server you have connected to. When you connect to a server, SSH compares the presented key against this file. If they match, the connection proceeds. If they do not, you get a loud warning.

The loud warning is meaningful. Either: the server has been reinstalled and has a new key; or, more sinisterly, you are talking to a different machine than the one whose key you saved. The first case is benign. The second is a signal that something is wrong.

The trap is that, in the first case, the easy fix — delete the line from known_hosts and reconnect — is exactly the wrong thing to do without verifying that the new key is the legitimate one. Out-of-band verification (a phone call to the server's administrator, asking them to read out the key fingerprint) is the proper procedure. Most people, including me, until very recently, would just delete the line and move on. The current key fingerprint of my server is MD5:... — written down here so I can check.

A note on protocol versions

SSH currently has two incompatible protocol versions in the wild: SSH-1 and SSH-2. SSH-1 has known cryptographic weaknesses; SSH-2 fixes them. You want to be running SSH-2 on both sides.

In sshd_config, the directive is:

Protocol 2

This disables SSH-1 entirely. If you have a client that only speaks SSH-1 — there are still a few — you should be replacing the client, not weakening the server. SSH-1 is being phased out across the community, and the rate of newly-discovered weaknesses is sufficient that I would not run it under any circumstance.

Where this leaves me

My network is now exclusively SSH for shell access. Telnet is gone. The mental load of "is this client of mine on a network where Telnet is safe" is gone with it.

This transition was overdue and easy to do. The fact that it took as long as it did is a small lesson in how much friction even obviously-correct security improvements face. The technical work was straightforward; the procrastination was the hard part.

There is a wider point here, which I will write about another time, about how the security improvements that should be the easiest — replacing a clearly-broken protocol with a clearly-better one — somehow take years to actually happen. Some of that is inertia. Some of it is genuine cost. A lot of it is people being too busy with the security crisis of the day to schedule the basic work.


Back to all writing