For a decade, if you asked how to harden an SSH server, the answer included a line about ciphers: turn off the old CBC modes, and prefer chacha20-poly1305@openssh.com. It was good advice. ChaCha20-Poly1305 is a modern authenticated cipher, fast, constant-time, and free of the sharp edges that made the CBC modes a recurring source of bugs. Every checklist had it near the top.
In December 2023, three researchers at Ruhr University Bochum — Fabian Bäumer, Marcus Brinkmann, and Jörg Schwenk — published an attack called Terrapin, and the punchline was that chacha20-poly1305@openssh.com was the cipher it hit the cleanest. Not because ChaCha20 was weak. Because the flaw wasn’t in the cipher at all. It was in the part of SSH that no cipher was responsible for, and the “good cipher” and the “bad cipher” were both exposed to it — for different structural reasons, to the same effect.
Terrapin let a network attacker do something that sounds impossible for an encrypted, authenticated protocol: delete a message from the beginning of the connection, cleanly, and have neither end notice. Not decrypt it. Not read it. Delete it, and leave no trace that anything is missing.
The number SSH counts but never checks
To see how, you have to know one small thing about how SSH frames data. Underneath the encryption, SSH speaks in packets — the Binary Packet Protocol — and every packet gets a sequence number. The first packet on a connection is number zero, the next is one, and so on, monotonically, forever. This number is never sent on the wire. Both sides simply count, independently, and trust that their counters agree.
That sequence number is not decorative. It’s an input to the message authentication code — the MAC — that protects each encrypted packet. When the receiver checks a packet’s MAC, the sequence number goes into the computation, which is what stops an attacker from replaying an old packet or reordering the stream: change the position of a packet and its MAC no longer verifies. The counter is the protocol’s sense of where it is in the conversation.
Here is the assumption that held for twenty-five years and turned out to be wrong: that because the MAC covers the sequence number on every encrypted packet, the count itself is trustworthy end to end. It covers each packet you send. It says nothing about a packet you removed before the counting that mattered began.
The trick
An SSH connection starts in the clear. The two sides exchange their supported algorithms, run a key exchange, and only then switch on encryption. The early handshake packets aren’t encrypted, and — this is the load-bearing detail — they aren’t all folded into the signed transcript that the key exchange authenticates. Some message types are, by design, ignorable. SSH_MSG_IGNORE is exactly what it sounds like: a no-op packet the receiver is required to discard. It exists for padding and keepalives. Nobody thought hard about it because it does nothing.
Except it does one thing. Receiving it bumps the receiver’s sequence counter by one.
So the attack, from a man-in-the-middle sitting on the wire, goes like this. During the unencrypted handshake, inject a single SSH_MSG_IGNORE packet toward one side. The victim discards its contents, as required — but its counter is now one ahead of where the other end thinks it is. The injected packet left no fingerprint in the signed key-exchange transcript, so the handshake still validates perfectly. Both sides finish the key exchange convinced everything is normal.
Then encryption switches on, and the first real encrypted message goes across. In modern SSH that first message is usually SSH_MSG_EXT_INFO — extension negotiation. The attacker simply deletes it.
Deleting an encrypted packet should be suicide. The next packet’s MAC includes its sequence number, so if you drop a packet, the receiver’s count and the sender’s count diverge, the very next MAC fails to verify, and the connection tears down. That’s the whole point of putting the sequence number in the MAC. But the attacker already offset the count by one, back in the clear, with that ignored packet. Delete one encrypted packet now and the two counters snap back into alignment. The receiver computes the next MAC with exactly the sequence number the sender used. It verifies. The connection sails on, one message shorter than it was supposed to be, and nothing anywhere reports a problem.
That’s the entire attack: add one packet where it can’t be seen, remove one packet where it would otherwise be caught, and let the two cancel out. Prefix truncation. The reason it needs chacha20-poly1305@openssh.com or a CBC cipher in Encrypt-then-MAC mode is that those constructions tie a packet’s integrity to its sequence number in a way that this add-one-remove-one swap leaves consistent. AES-GCM, as it happens, binds its nonce to the sequence number differently and doesn’t line up — so GCM wasn’t vulnerable. The “modern, preferred” cipher was; one of the ciphers people were migrating toward wasn’t. Cipher-choice advice, which is what the hardening guides gave you, was aimed at the wrong layer to help here.
What you actually get for it
I want to be honest about the payoff, because the gap between “silently delete a message from SSH” and what that buys an attacker is where a lot of coverage got breathless.
Against OpenSSH, deleting SSH_MSG_EXT_INFO is a downgrade, not a takeover. Extension negotiation (RFC 8308) is where the two sides agree on newer capabilities — including server-sig-algs, which lets a client safely use stronger signature algorithms, and a keystroke-timing obfuscation feature that pads out the timing of your typing so an observer can’t infer what you’re typing from packet timing. Strip EXT_INFO and you roll those back to the pre-negotiation defaults. You have silently turned off a privacy feature and nudged the connection toward weaker choices. Real, but modest — and it requires an attacker already positioned as a man-in-the-middle on the network, with the connection already using a vulnerable cipher. This is not remote root. It’s a scalpel, and a shallow one, against OpenSSH.
The sharper edge was in a specific implementation. AsyncSSH, a Python SSH library, had two additional bugs (CVE-2023-46445 and CVE-2023-46446) that Terrapin’s primitive could trigger into something much worse — including a “rogue session” attack that quietly logs the victim’s client into an account the attacker controls, so whatever the victim types goes to the attacker’s session. That’s the real-world “this could actually hurt” case, and it lived in one library’s handling of the manipulated handshake, not in the protocol flaw itself. The protocol flaw got the headline CVE, CVE-2023-48795; the frightening outcomes needed an implementation bug to stand on.
So the scary number — the researchers scanned the internet and found that 77% of SSH servers supported a vulnerable mode, and 57% listed one as their preferred choice, and a few weeks later the Shadowserver Foundation counted nearly 11 million internet-reachable servers still exposed — is best read as “almost everything was structurally exposed to the primitive,” not “almost everything was one packet from compromise.” Both of those sentences are true. They are very different sentences.
The fix is almost insultingly simple
Once you see the flaw, the fix writes itself, and it did. It’s called strict key exchange — “strict kex” — and it shipped in OpenSSH 9.6 on the same day the attack went public, then spread to PuTTY, libssh, and the rest.
Strict kex adds two rules. First: if any unexpected message — an SSH_MSG_IGNORE, anything not strictly part of the key exchange — shows up during the initial handshake, don’t discard it and move on. Abort the whole connection. The injected packet that the attack depends on is now fatal instead of invisible. Second: when the first key exchange completes, reset both sequence counters to zero. Any offset an attacker managed to sneak in during the handshake evaporates at the boundary; it can’t be carried across into the encrypted channel to cancel a later deletion.
Reset the counter to zero. That’s the heart of it. A quarter-century-old design gap, closed by insisting that the number both sides had been quietly trusting gets re-synchronized to a known value the instant the connection becomes something worth protecting. The only catch is that both ends have to support it — strict kex only engages if the client and the server both offer it — so the long tail of un-updated embedded devices, appliances, and network gear will keep negotiating the old way for years.
The seam nobody was guarding
Here’s what Terrapin is actually about, and why it’s worth your attention even though the OpenSSH damage was mild.
SSH had two things everyone had scrutinized to death. The handshake: signed, transcript-authenticated, poked at by cryptographers for twenty-five years. The data channel: every packet encrypted and MAC’d, sequence numbers and all. Both were, on their own terms, correct. The bug lived in neither of them. It lived in the seam — the moment the connection hands off from the authenticated-handshake world to the authenticated-channel world — and the seam was the one place nobody had made anyone responsible for. The handshake didn’t count the ignored packet. The channel trusted a counter it inherited. Each component verified its own contents and assumed the other end of the join was someone else’s problem.
That’s the transferable lesson, and it’s not specific to SSH. When you assemble a secure system out of secure pieces, the security of the pieces does not add up to the security of the whole. The joints are their own attack surface, and they’re the part least likely to have a test, an audit, or an owner, precisely because each adjacent component looks correct in isolation. Terrapin didn’t break SSH’s cryptography. It walked through the door between two rooms that were each locked, and that nobody had thought to put a door on.
And the checklist that told you to prefer chacha20-poly1305@openssh.com? It wasn’t wrong about the cipher. It was answering a question — which encryption algorithm is strongest — that had nothing to do with where the actual weakness was. Good advice, aimed one layer too high to help. The uncomfortable version of the lesson is that a hardening guide made entirely of correct statements can still leave you exposed, because the flaw was never in the box any of those statements were about.