Hardening SSH Against the Terrapin Prefix Truncation Attack (CVE-2023-48795)
Problem
Terrapin (CVE-2023-48795) is a prefix truncation attack against the SSH Binary Packet Protocol that was disclosed by researchers at Ruhr University Bochum in December 2023. It allows a network-positioned attacker to silently delete an arbitrary number of messages from the beginning of the SSH session handshake — without the client or server detecting the deletion, and without breaking the connection.
The attack targets the SSH handshake phase specifically. When two SSH peers negotiate extensions during key exchange, those extension messages are sent before the session is fully authenticated. The attack exploits a gap in the SSH protocol’s integrity guarantees: while the SSH Transport Layer Protocol uses sequence numbers to prevent replay and reordering, these sequence numbers are not authenticated under the session keys at the point they are first assigned. An attacker positioned between client and server can inject or remove messages during the initial handshake, manipulating the sequence number state before keys are established.
In practice, Terrapin’s most significant impact is on extension negotiation. OpenSSH uses the ext-info extension negotiation mechanism to advertise and agree on security-enhancing features between client and server. Two extensions are directly at risk:
keystroke-timing (OpenSSH 9.5+): added to obscure timing patterns in interactive SSH sessions that can leak information about typed keystrokes. Terrapin can strip the server’s server-sig-algs extension message, causing the client to fall back to a mode without keystroke timing obfuscation.
ping / channel request extensions: used for liveness detection and anti-idle mechanisms. Stripping these messages can cause the connection to behave differently than intended.
More broadly, any future SSH extension negotiated via ext-info is vulnerable to silent downgrade by a Terrapin attacker as long as strict key exchange is not in use.
The vulnerable cipher modes are specifically those that do not provide integrity protection over the sequence number assignment: ChaCha20-Poly1305 (chacha20-poly1305@openssh.com) and any CBC-mode cipher with Encrypt-then-MAC. Both are widely deployed. ChaCha20-Poly1305 is the preferred cipher in many modern SSH configurations precisely because it is fast and does not require hardware AES acceleration.
The fix is strict key exchange mode (kex-strict-c-v00@openssh.com / kex-strict-s-v00@openssh.com), introduced in OpenSSH 9.6 (December 2023). With strict KEX, the sequence numbers used in the handshake are cryptographically bound to the session keys — removing any message from the handshake causes an immediate authentication failure. Both client and server must support strict KEX for it to be active; if either side is unpatched, the session falls back silently to the vulnerable mode.
The deployment status as of early 2026 is mixed. OpenSSH 9.6+ is available on all major distributions, but many organizations have not uniformly upgraded servers, particularly on embedded systems, network appliances, CI runners, bastion hosts maintained by third parties, and legacy application servers. SSH clients on developer workstations are more likely to be patched, but SSH server heterogeneity means many sessions still negotiate without strict KEX.
Target systems: all SSH deployments using OpenSSH pre-9.6, Dropbear pre-2024.84, libssh pre-0.10.6, PuTTY pre-0.80; any SSH infrastructure in environments where network MITM is a plausible threat (shared cloud VPCs, multi-tenant networks, corporate Wi-Fi, third-party managed hosting).
Threat Model
Adversary 1 — Cloud VPC MITM. Access level: ability to perform ARP spoofing or route injection within a cloud VPC — achievable via a compromised VM in the same subnet. Objective: intercept SSH sessions to production servers, strip keystroke-timing extension negotiation, and exploit the resulting timing side channel to infer typed credentials or commands.
Adversary 2 — Corporate network MITM. Access level: position on the same network segment as a developer’s workstation (e.g., via a rogue Wi-Fi AP or ARP spoofing on a corporate LAN). Objective: strip extension negotiation messages from SSH handshakes to bastion hosts, enabling downgrade attacks on future extensions that provide stronger security guarantees.
Adversary 3 — Managed hosting or shared infrastructure. Access level: a malicious or compromised co-tenant on a shared hosting platform with access to the hypervisor or network switch. Objective: perform Terrapin against customers’ SSH sessions in the absence of strict KEX, enabling surveillance of session metadata or credential timing.
Adversary 4 — Supply chain targeting CI/CD. Access level: MITM position on a CI/CD runner’s outbound network path. Objective: strip extension negotiation to enable subsequent SSH-based attacks against deployment targets or intercept SSH git operations.
Without hardening: Terrapin allows silent extension stripping in ChaCha20-Poly1305 and CBC sessions; attacker removes keystroke-timing protection and any future security extensions without detection. With hardening: strict KEX makes any handshake message removal immediately detectable, causing the connection to fail rather than silently degrade.
Configuration / Implementation
Step 1 — Check OpenSSH versions and strict KEX support
# Check server version
sshd -V 2>&1
ssh -V 2>&1
# OpenSSH 9.6+ includes strict KEX fix
# Verify strict KEX is being negotiated in a live session
ssh -v user@server 2>&1 | grep -i "kex-strict\|ext-info\|terrapin"
# With 9.6+ client and server, you should see:
# debug1: kex: server->client cipher: chacha20-poly1305@openssh.com ...
# debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
# And no downgrade warning
# Test using the Terrapin scanner (checks if a host is vulnerable)
# Install: go install github.com/RUB-NDS/Terrapin-Scanner@latest
terrapin-scanner --connect server:22
# Output will show:
# - "Strict kex enabled" (patched) or
# - "Server does not support strict kex negotiation" (vulnerable)
Fleet-wide version audit:
# Via Ansible
ansible all -m shell -a "sshd -V 2>&1 | head -1" 2>/dev/null | \
awk '/OpenSSH/{print $1, $NF}' | sort
# Via kubectl (check SSH server versions on Kubernetes nodes)
kubectl get nodes -o name | while read -r node; do
ver=$(kubectl debug node/"${node#node/}" -it --image=busybox -- \
sh -c "ssh -V 2>&1" 2>/dev/null || echo "unknown")
echo "${node}: $ver"
done
Step 2 — Upgrade OpenSSH to 9.6+ on all servers
# Ubuntu 22.04 LTS — backport available
add-apt-repository ppa:ubuntu-security-proposals/openssh-backports
apt update && apt install --only-upgrade openssh-server openssh-client
sshd -V 2>&1 # Should show OpenSSH_9.6 or later
# Ubuntu 24.04 LTS — 9.6+ in main archive
apt update && apt install --only-upgrade openssh-server openssh-client
# RHEL 9 / Amazon Linux 2023
dnf update openssh openssh-server
sshd -V 2>&1
# Debian Bookworm (12)
apt update && apt install --only-upgrade openssh-server
# Bookworm ships 9.2 by default; backport needed:
# echo "deb http://deb.debian.org/debian bookworm-backports main" >> /etc/apt/sources.list
# apt update && apt install -t bookworm-backports openssh-server
# Alpine 3.19+
apk upgrade openssh
# Verify after upgrade
sshd -T | grep -E "^(ciphers|kexalgorithms|macs)"
Step 3 — Harden server cipher configuration to exclude CBC modes
As an additional control, remove CBC cipher modes and the specific ChaCha20 configuration that is most affected:
# /etc/ssh/sshd_config
# Prefer AES-GCM (authenticated encryption — also resistant to Terrapin)
# Retain ChaCha20 but ensure strict KEX is active
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
# Remove CBC modes entirely — they are obsolete and vulnerable to Terrapin without EtM
# Do NOT include: aes256-cbc, aes192-cbc, aes128-cbc, 3des-cbc
# Use only HMAC-SHA2 with Encrypt-then-MAC (EtM) — EtM modes are not vulnerable to Terrapin
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
# Key exchange algorithms — include strict-kex variants (OpenSSH 9.6+)
# These are negotiated automatically if both client and server support them
# No explicit config needed — they are preferred by default in 9.6+
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
# Disable old host key types
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# Additional hardening
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
LoginGraceTime 30
sshd -t # Test configuration syntax
systemctl reload sshd
Step 4 — Harden client configuration
Client-side strict KEX is negotiated automatically in OpenSSH 9.6+, but the client configuration should also enforce cipher restrictions to prevent session downgrade:
# /etc/ssh/ssh_config (global) or ~/.ssh/config (per-user)
Host *
# Prefer GCM modes (fully resistant to Terrapin even without strict KEX)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
# EtM MACs only
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
# Require host key verification — never accept unknown host keys
StrictHostKeyChecking yes
UpdateHostKeys yes
# Connection security
ServerAliveInterval 60
ServerAliveCountMax 3
HashKnownHosts yes
# Disable insecure forwarding unless explicitly needed
ForwardAgent no
X11Forwarding no
Step 5 — Scan your SSH infrastructure for vulnerable hosts
Use the Terrapin Scanner tool to enumerate vulnerable hosts:
# Install Terrapin Scanner
go install github.com/RUB-NDS/Terrapin-Scanner@latest
# Single host
~/go/bin/terrapin-scanner --connect host:22
# Output example for patched host:
# {"hostkey":"ssh-ed25519","cves":{"CVE-2023-48795":{"Affected":false}},"banner":"SSH-2.0-OpenSSH_9.6"}
# Output example for vulnerable host:
# {"hostkey":"ssh-ed25519","cves":{"CVE-2023-48795":{"Affected":true}},"banner":"SSH-2.0-OpenSSH_9.2"}
# Fleet scan via nmap NSE
nmap -p 22 --script ssh2-enum-algos 10.0.0.0/24 -oX ssh-audit.xml
# Check for absence of "kex-strict-c-v00@openssh.com" in KEX algorithms
# Or use ssh-audit
ssh-audit 10.0.0.1
# Install: pip3 install ssh-audit
# Flags Terrapin-vulnerable configurations explicitly
Automate fleet scanning as a weekly check:
#!/bin/bash
# /usr/local/bin/terrapin-fleet-scan.sh
HOSTS_FILE=/etc/ssh-inventory/hosts.txt
SCANNER=~/go/bin/terrapin-scanner
REPORT=/var/reports/terrapin-$(date +%Y%m%d).json
mkdir -p /var/reports
echo "[]" > "$REPORT"
while IFS= read -r host; do
result=$("$SCANNER" --connect "${host}:22" --output json 2>/dev/null)
jq --arg host "$host" '. + {host: $host}' <<< "$result" >> /tmp/result.json
if jq -e '.cves["CVE-2023-48795"].Affected == true' <<< "$result" >/dev/null 2>&1; then
echo "VULNERABLE: $host"
fi
done < "$HOSTS_FILE"
Step 6 — Monitor for Terrapin attack indicators
While strict KEX prevents Terrapin from succeeding, monitor for connection failures that may indicate an active attack attempt:
# Suspicious pattern: connection established then immediately failed with MAC error
# Indicates message was injected/removed during handshake
# In syslog/journald:
journalctl -u sshd -f | grep -E "Bad packet length|message authentication code incorrect|Disconnected from.*preauth"
# Alert if MAC errors spike (indicates handshake tampering attempts):
# Prometheus alert rule
- alert: SSHHandshakeMACFailureSpike
expr: |
rate(ssh_auth_failures_total{reason="mac_error"}[5m]) > 0.1
for: 3m
labels:
severity: warning
annotations:
summary: "SSH MAC failures on {{ $labels.instance }} — possible Terrapin attempt"
Expected Behaviour
| Signal | Before hardening | After hardening |
|---|---|---|
terrapin-scanner result |
Affected: true |
Affected: false |
ssh -v shows strict KEX |
Not present | debug1: kex: server->client ... kex-strict-s-v00 |
| CBC ciphers available on server | Present in cipher list | Absent from sshd -T | grep ciphers output |
sshd -V |
OpenSSH_9.2 or earlier |
OpenSSH_9.6 or later |
| Terrapin-style message stripping | Session continues without error | Immediate connection failure with MAC error |
Verification:
# Confirm no CBC ciphers on server
sshd -T | grep "^ciphers" | tr ',' '\n' | grep cbc
# Expected: no output
# Confirm strict KEX in negotiation
ssh -v -o BatchMode=yes user@server exit 2>&1 | grep "kex-strict"
# Expected: debug1: kex: server->client ... (strict KEX active)
# Run terrapin-scanner
~/go/bin/terrapin-scanner --connect server:22
# Expected: "CVE-2023-48795": {"Affected": false}
Trade-offs
| Aspect | Benefit | Cost | Mitigation |
|---|---|---|---|
| OpenSSH 9.6+ upgrade | Full strict KEX protection; no configuration required | Upgrade may require OS package backport or manual build | Test on staging; use Ansible for fleet upgrade; verify sshd config compatibility |
| Removing CBC ciphers | Eliminates Terrapin via CBC path; CBC was already deprecated | Some legacy SSH clients (embedded devices, old JDK JSch) require CBC | Maintain separate sshd_config fragment for legacy client hosts; audit and migrate legacy clients |
| Strict KEX negotiation | Cryptographic prevention; both peers must support | If one peer is unpatched, falls back silently to vulnerable mode | Enforce version minimum via RequiredRSASize or scan-based alerting; fail hard if strict KEX is not negotiated (requires patch to sshd) |
| Removing ChaCha20 (alternative) | Eliminates the primary Terrapin cipher | ChaCha20-Poly1305 is fast on systems without AES-NI; performance regression on ARM | Keep ChaCha20 with strict KEX active; removing it is only necessary as a compensating control if strict KEX cannot be deployed |
Failure Modes
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Legacy CI system uses old JSch (Java SSH) with CBC | Deployment pipeline fails to connect; “algorithm negotiation fail” | Build logs show SSH negotiation failure; application logs show no common cipher |
Add CBC temporarily to a scoped host match for the CI system; schedule legacy client upgrade |
| sshd restart fails after config change | SSH access lost to server | Pre-flight sshd -t check catches syntax errors; run before reload |
Always run sshd -t && systemctl reload sshd; maintain out-of-band console access during changes |
| Embedded device SSH server cannot be upgraded | Device remains vulnerable | Terrapin scanner flags device; no upgrade path available | Network-level mitigation: restrict device SSH access to management VLAN only; enforce ZTNA for admin access; schedule device replacement |
| Strict KEX not negotiated despite both sides running 9.6+ | Session works but ssh -v does not show strict KEX indicator |
ssh -v grep for kex-strict returns nothing |
Check for middlebox SSH proxy that intercepts and re-establishes sessions (corporate SSL inspection, cloud NAT); configure bypass or upgrade proxy |
Related Articles
- SSH Hardening — comprehensive OpenSSH server hardening beyond Terrapin: cipher selection, key types, access controls
- Post-Quantum SSH (OpenSSH) — deploying CRYSTALS-Kyber hybrid KEX in OpenSSH, which also negotiates strict KEX mode
- SSH Certificate Authority — certificate-based SSH authentication that reduces the credential exposure Terrapin-enabled timing attacks target
- SSH Bastion Hardening — hardening the bastion host layer where MITM attacks on SSH are most likely to be attempted
- Network Forensics and Packet Capture — capturing SSH handshake traffic to verify strict KEX negotiation in production