1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
|
From 140abfad5538b9660e9339d31213702f6c0b832b Mon Sep 17 00:00:00 2001
From: Simon Tatham <anakin@pobox.com>
Date: Wed, 29 Nov 2023 08:50:45 +0000
Subject: Warn about Terrapin vulnerability for unpatched servers.
If the KEXINIT exchange results in a vulnerable cipher mode, we now
give a warning, similar to the 'we selected a crypto primitive below
the warning threshold' one. But there's nothing we can do about it at
that point other than let the user abort the connection.
Origin: backport, https://git.tartarus.org/?p=simon/putty.git;a=commit;h=0b00e4ce26d89cd010e31e66fd02ac77cb982367
Last-Update: 2023-12-18
Patch-Name: terrapin-warning.patch
---
ssh.h | 10 +++++-
ssh/common.c | 32 +++++++++++++----
ssh/login1.c | 2 +-
ssh/transport2.c | 90 ++++++++++++++++++++++++++++++++++++++++++++----
ssh/transport2.h | 4 +++
5 files changed, 123 insertions(+), 15 deletions(-)
diff --git a/ssh.h b/ssh.h
index 3b9df7d2..b7290bc5 100644
--- a/ssh.h
+++ b/ssh.h
@@ -1901,6 +1901,13 @@ void add_to_commasep(strbuf *buf, const char *data);
void add_to_commasep_pl(strbuf *buf, ptrlen data);
bool get_commasep_word(ptrlen *list, ptrlen *word);
+/* Reasons why something warned by confirm_weak_crypto_primitive might
+ * be considered weak */
+typedef enum WeakCryptoReason {
+ WCR_BELOW_THRESHOLD, /* user has told us to consider it weak */
+ WCR_TERRAPIN, /* known vulnerability CVE-2023-48795 */
+} WeakCryptoReason;
+
SeatPromptResult verify_ssh_host_key(
InteractionReadySeat iseat, Conf *conf, const char *host, int port,
ssh_key *key, const char *keytype, char *keystr, const char *keydisp,
@@ -1908,7 +1915,8 @@ SeatPromptResult verify_ssh_host_key(
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
SeatPromptResult confirm_weak_crypto_primitive(
InteractionReadySeat iseat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
+ WeakCryptoReason wcr);
SeatPromptResult confirm_weak_cached_hostkey(
InteractionReadySeat iseat, const char *algname, const char **betteralgs,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx);
diff --git a/ssh/common.c b/ssh/common.c
index af534e3b..9ccf078b 100644
--- a/ssh/common.c
+++ b/ssh/common.c
@@ -1077,7 +1077,8 @@ SeatPromptResult verify_ssh_host_key(
SeatPromptResult confirm_weak_crypto_primitive(
InteractionReadySeat iseat, const char *algtype, const char *algname,
- void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
+ void (*callback)(void *ctx, SeatPromptResult result), void *ctx,
+ WeakCryptoReason wcr)
{
SeatDialogText *text = seat_dialog_text_new();
const SeatDialogPromptDescriptions *pds =
@@ -1085,11 +1086,30 @@ SeatPromptResult confirm_weak_crypto_primitive(
seat_dialog_text_append(text, SDT_TITLE, "%s Security Alert", appname);
- seat_dialog_text_append(
- text, SDT_PARA,
- "The first %s supported by the server is %s, "
- "which is below the configured warning threshold.",
- algtype, algname);
+ switch (wcr) {
+ case WCR_BELOW_THRESHOLD:
+ seat_dialog_text_append(
+ text, SDT_PARA,
+ "The first %s supported by the server is %s, "
+ "which is below the configured warning threshold.",
+ algtype, algname);
+ break;
+ case WCR_TERRAPIN:
+ seat_dialog_text_append(
+ text, SDT_PARA,
+ "The %s selected for this session is %s, "
+ "which, with this server, is vulnerable to the 'Terrapin' attack "
+ "CVE-2023-48795, potentially allowing an attacker to modify "
+ "the encrypted session.",
+ algtype, algname);
+ seat_dialog_text_append(
+ text, SDT_PARA,
+ "Upgrading, patching, or reconfiguring this SSH server is the "
+ "best way to avoid this vulnerability, if possible.");
+ break;
+ default:
+ unreachable("bad WeakCryptoReason");
+ }
/* In batch mode, we print the above information and then this
* abort message, and stop. */
diff --git a/ssh/login1.c b/ssh/login1.c
index ec316575..2bf1ab3b 100644
--- a/ssh/login1.c
+++ b/ssh/login1.c
@@ -324,7 +324,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
if (warn) {
s->spr = confirm_weak_crypto_primitive(
ppl_get_iseat(&s->ppl), "cipher", cipher_string,
- ssh1_login_dialog_callback, s);
+ ssh1_login_dialog_callback, s, WCR_BELOW_THRESHOLD);
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
diff --git a/ssh/transport2.c b/ssh/transport2.c
index e6229ec9..b6dc4a47 100644
--- a/ssh/transport2.c
+++ b/ssh/transport2.c
@@ -33,6 +33,11 @@ const static ptrlen kex_strict_c =
const static ptrlen kex_strict_s =
PTRLEN_DECL_LITERAL("kex-strict-s-v00@openssh.com");
+/* Pointer value to store in s->weak_algorithms_consented_to to
+ * indicate that the user has accepted the risk of the Terrapin
+ * attack */
+static const char terrapin_weakness[1];
+
static ssh_compressor *ssh_comp_none_init(void)
{
return NULL;
@@ -86,6 +91,8 @@ static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl);
static void ssh2_transport_set_max_data_size(struct ssh2_transport_state *s);
static unsigned long sanitise_rekey_time(int rekey_time, unsigned long def);
static void ssh2_transport_higher_layer_packet_callback(void *context);
+static const char *terrapin_vulnerable(
+ bool strict_kex, const transport_direction *d);
static const PacketProtocolLayerVtable ssh2_transport_vtable = {
.free = ssh2_transport_free,
@@ -106,7 +113,7 @@ static bool ssh2_transport_timer_update(struct ssh2_transport_state *s,
unsigned long rekey_time);
static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
struct ssh2_transport_state *s, const char *type, const char *name,
- const void *alg);
+ const void *alg, WeakCryptoReason wcr);
static const char *const kexlist_descr[NKEXLIST] = {
"key exchange algorithm",
@@ -1571,7 +1578,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
if (s->warn_kex) {
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
- s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg);
+ s, "key-exchange algorithm", s->kex_alg->name, s->kex_alg,
+ WCR_BELOW_THRESHOLD);
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
ssh_spr_close(s->ppl.ssh, s->spr, "kex warning");
@@ -1623,7 +1631,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
* warning prompt */
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
s, "host key type", s->hostkey_alg->ssh_id,
- s->hostkey_alg);
+ s->hostkey_alg, WCR_BELOW_THRESHOLD);
}
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
@@ -1635,7 +1643,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
if (s->warn_cscipher) {
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
s, "client-to-server cipher", s->out.cipher->ssh2_id,
- s->out.cipher);
+ s->out.cipher, WCR_BELOW_THRESHOLD);
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
@@ -1646,7 +1654,7 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
if (s->warn_sccipher) {
s->spr = ssh2_transport_confirm_weak_crypto_primitive(
s, "server-to-client cipher", s->in.cipher->ssh2_id,
- s->in.cipher);
+ s->in.cipher, WCR_BELOW_THRESHOLD);
crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
if (spr_is_abort(s->spr)) {
ssh_spr_close(s->ppl.ssh, s->spr, "cipher warning");
@@ -1654,6 +1662,44 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
}
}
+ {
+ s->terrapin.csvuln = terrapin_vulnerable(s->strict_kex, s->cstrans);
+ s->terrapin.scvuln = terrapin_vulnerable(s->strict_kex, s->sctrans);
+ s->terrapin.wcr = WCR_TERRAPIN;
+
+ if (s->terrapin.csvuln || s->terrapin.scvuln) {
+ ppl_logevent("SSH connection is vulnerable to 'Terrapin' attack "
+ "(CVE-2023-48795)");
+ }
+
+ if (s->terrapin.csvuln) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "client-to-server cipher", s->terrapin.csvuln,
+ terrapin_weakness, s->terrapin.wcr);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "vulnerability warning");
+ return;
+ }
+ }
+
+ if (s->terrapin.scvuln) {
+ s->spr = ssh2_transport_confirm_weak_crypto_primitive(
+ s, "server-to-client cipher", s->terrapin.scvuln,
+ terrapin_weakness, s->terrapin.wcr);
+ crMaybeWaitUntilV(s->spr.kind != SPRK_INCOMPLETE);
+ if (spr_is_abort(s->spr)) {
+ ssh_spr_close(s->ppl.ssh, s->spr, "vulnerability warning");
+ return;
+ }
+ }
+
+ if (s->terrapin.csvuln || s->terrapin.scvuln) {
+ ppl_logevent("Continuing despite 'Terrapin' vulnerability, "
+ "at user request");
+ }
+ }
+
/*
* If the other side has sent an initial key exchange packet that
* we must treat as a wrong guess, wait for it, and discard it.
@@ -2463,14 +2509,15 @@ static int ca_blob_compare(void *av, void *bv)
*/
static SeatPromptResult ssh2_transport_confirm_weak_crypto_primitive(
struct ssh2_transport_state *s, const char *type, const char *name,
- const void *alg)
+ const void *alg, WeakCryptoReason wcr)
{
if (find234(s->weak_algorithms_consented_to, (void *)alg, NULL))
return SPR_OK;
add234(s->weak_algorithms_consented_to, (void *)alg);
return confirm_weak_crypto_primitive(
- ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback, s);
+ ppl_get_iseat(&s->ppl), type, name, ssh2_transport_dialog_callback,
+ s, wcr);
}
static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl)
@@ -2481,3 +2528,32 @@ static size_t ssh2_transport_queued_data_size(PacketProtocolLayer *ppl)
return (ssh_ppl_default_queued_data_size(ppl) +
ssh_ppl_queued_data_size(s->higher_layer));
}
+
+/* Check the settings for a transport direction to see if they're
+ * vulnerable to the Terrapin attack, aka CVE-2023-48795. If so,
+ * return a string describing the vulnerable thing. */
+static const char *terrapin_vulnerable(
+ bool strict_kex, const transport_direction *d)
+{
+ /*
+ * Strict kex mode eliminates the vulnerability. (That's what it's
+ * for.)
+ */
+ if (strict_kex)
+ return NULL;
+
+ /*
+ * ChaCha20-Poly1305 is vulnerable and perfectly exploitable.
+ */
+ if (d->cipher == &ssh2_chacha20_poly1305)
+ return "ChaCha20-Poly1305";
+
+ /*
+ * CBC-mode ciphers with OpenSSH's ETM modification are vulnerable
+ * and probabilistically exploitable.
+ */
+ if (d->etm_mode && (d->cipher->flags & SSH_CIPHER_IS_CBC))
+ return "a CBC-mode cipher in OpenSSH ETM mode";
+
+ return NULL;
+}
diff --git a/ssh/transport2.h b/ssh/transport2.h
index 1322cf5b..ea739df9 100644
--- a/ssh/transport2.h
+++ b/ssh/transport2.h
@@ -180,6 +180,10 @@ struct ssh2_transport_state {
int nbits, pbits;
bool warn_kex, warn_hk, warn_cscipher, warn_sccipher;
+ struct {
+ const char *csvuln, *scvuln;
+ WeakCryptoReason wcr;
+ } terrapin;
mp_int *p, *g, *e, *f;
strbuf *ebuf, *fbuf;
strbuf *kex_shared_secret;
|