Skip to content

Commit

Permalink
Issue #1760: Implement the "strict KEX" mitigations for the Terrapin …
Browse files Browse the repository at this point in the history
…SSH protocol attack (CVE-2023-48795).
  • Loading branch information
Castaglia committed Dec 20, 2023
1 parent bc081c6 commit bcec15e
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 55 deletions.
124 changes: 99 additions & 25 deletions contrib/mod_sftp/kex.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ static struct sftp_kex *kex_first_kex = NULL;
static struct sftp_kex *kex_rekey_kex = NULL;
static int kex_sent_kexinit = FALSE;

/* Using strict kex? Note that we maintain this value here, rather than
* in the sftp_kex struct, so that any "use strict KEX" flag set via the
* first KEXINIT is used through any subsequent KEXINITs.
*/
static int use_strict_kex = FALSE;
static int kex_done_first_kex = FALSE;

/* Diffie-Hellman group moduli */

static const char *dh_group1_str =
Expand Down Expand Up @@ -1612,6 +1619,16 @@ static const char *get_kexinit_exchange_list(pool *p) {
res = pstrcat(p, res, *res ? "," : "", pstrdup(p, "ext-info-s"), NULL);
}

if (!(sftp_opts & SFTP_OPT_NO_STRICT_KEX)) {
/* Indicate support for OpenSSH's custom "strict KEX" mode extension,
* but only if we have not done/completed our first KEX.
*/
if (kex_done_first_kex == FALSE) {
res = pstrcat(p, res, *res ? "," : "",
pstrdup(p, "kex-strict-s-v00@openssh.com"), NULL);
}
}

return res;
}

Expand Down Expand Up @@ -2323,6 +2340,21 @@ static int get_session_names(struct sftp_kex *kex, int *correct_guess) {
pr_trace_msg(trace_channel, 20, "client %s EXT_INFO support",
kex->use_ext_info ? "signaled" : "did not signal" );

if (!(sftp_opts & SFTP_OPT_NO_STRICT_KEX)) {
/* Did the client indicate "strict kex" support (Issue 1760)?
*
* Note that we only check for this if it is our first KEXINIT.
* The "strict kex" extension is ignored in any subsequent KEXINITs, as
* for rekeys.
*/
if (kex_done_first_kex == FALSE) {
use_strict_kex = sftp_misc_namelist_contains(kex->pool,
client_list, "kex-strict-c-v00@openssh.com");
pr_trace_msg(trace_channel, 20, "client %s strict KEX support",
use_strict_kex ? "signaled" : "did not signal" );
}
}

} else {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"no shared key exchange algorithm found (client sent '%s', server sent "
Expand Down Expand Up @@ -5058,7 +5090,6 @@ static int handle_kex_ecdh(struct ssh2_packet *pkt, struct sftp_kex *kex) {
destroy_pool(pkt->pool);
return 0;
}

#endif /* PR_USE_OPENSSL_ECC */

static struct ssh2_packet *read_kex_packet(pool *p, struct sftp_kex *kex,
Expand Down Expand Up @@ -5109,6 +5140,10 @@ static struct ssh2_packet *read_kex_packet(pool *p, struct sftp_kex *kex,
/* Per RFC 4253, Section 11, DEBUG, DISCONNECT, IGNORE, and UNIMPLEMENTED
* messages can occur at any time, even during KEX. We have to be prepared
* for this, and Do The Right Thing(tm).
*
* However, due to the Terrapin attack, if we are using a "strict KEX"
* mode, then only DISCONNECT messages can occur during KEX; DEBUG,
* IGNORE, and UNIMPLEMENTED messages will lead to disconnecting.
*/

msg_type = sftp_ssh2_packet_get_msg_type(pkt);
Expand Down Expand Up @@ -5137,35 +5172,43 @@ static struct ssh2_packet *read_kex_packet(pool *p, struct sftp_kex *kex,
}

switch (msg_type) {
case SFTP_SSH2_MSG_DEBUG:
sftp_ssh2_packet_handle_debug(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;

/* DISCONNECT messages are always allowed. */
case SFTP_SSH2_MSG_DISCONNECT:
sftp_ssh2_packet_handle_disconnect(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;

case SFTP_SSH2_MSG_DEBUG:
if (use_strict_kex == FALSE) {
sftp_ssh2_packet_handle_debug(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
}

case SFTP_SSH2_MSG_IGNORE:
sftp_ssh2_packet_handle_ignore(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
if (use_strict_kex == FALSE) {
sftp_ssh2_packet_handle_ignore(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
}

case SFTP_SSH2_MSG_UNIMPLEMENTED:
sftp_ssh2_packet_handle_unimplemented(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
if (use_strict_kex == FALSE) {
sftp_ssh2_packet_handle_unimplemented(pkt);
pr_response_set_pool(NULL);
pkt = NULL;
break;
}

default:
/* For any other message type, it's considered a protocol error. */
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"received %s (%d) unexpectedly, disconnecting",
sftp_ssh2_packet_get_msg_type_desc(msg_type), msg_type);
"received %s (%d) unexpectedly%s, disconnecting",
sftp_ssh2_packet_get_msg_type_desc(msg_type), msg_type,
use_strict_kex ? " during strict KEX" : "");
pr_response_set_pool(NULL);
destroy_kex(kex);
destroy_pool(pkt->pool);
Expand All @@ -5187,7 +5230,7 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
* initial connect (kex_first_kex not null), or because we
* are in a server-initiated rekeying (kex_rekey_kex not null).
*/
if (kex_first_kex) {
if (kex_first_kex != NULL) {
kex = kex_first_kex;

/* We need to assign the client/server versions, which this struct
Expand All @@ -5196,7 +5239,7 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
kex->client_version = kex_client_version;
kex->server_version = kex_server_version;

} else if (kex_rekey_kex) {
} else if (kex_rekey_kex != NULL) {
kex = kex_rekey_kex;

} else {
Expand Down Expand Up @@ -5233,6 +5276,24 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
return -1;
}

if (use_strict_kex == TRUE &&
kex_done_first_kex == FALSE) {
uint32_t client_seqno;

client_seqno = sftp_ssh2_packet_get_client_seqno();
if (client_seqno != 1) {
/* Receiving any messages other than a KEXINIT as the first client
* message indicates the possibility of the Terrapin attack being
* conducted (Issue 1760). Thus we disconnect the client in such
* cases.
*/
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"'strict KEX' violation, as KEXINIT was not the first message; disconnecting");
destroy_kex(kex);
SFTP_DISCONNECT_CONN(SFTP_SSH2_DISCONNECT_BY_APPLICATION, NULL);
}
}

/* Once we have received the client KEXINIT message, we can compare what we
* want to send against what we already received from the client.
*
Expand Down Expand Up @@ -5291,7 +5352,7 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {

destroy_pool(pkt->pool);

if (!kex_sent_kexinit) {
if (kex_sent_kexinit == FALSE) {
pkt = sftp_ssh2_packet_create(kex_pool);
res = write_kexinit(pkt, kex);
if (res < 0) {
Expand All @@ -5314,7 +5375,7 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
}
}

if (!kex_sent_kexinit) {
if (kex_sent_kexinit == FALSE) {
pkt = sftp_ssh2_packet_create(kex_pool);
res = write_kexinit(pkt, kex);
if (res < 0) {
Expand Down Expand Up @@ -5445,7 +5506,7 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
NULL, 1, SFTP_SSH2_MSG_NEWKEYS);

/* If we didn't send our NEWKEYS message earlier, do it now. */
if (!sent_newkeys) {
if (sent_newkeys == FALSE) {
struct ssh2_packet *pkt2;

pr_trace_msg(trace_channel, 9, "sending NEWKEYS message to client");
Expand All @@ -5469,6 +5530,11 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
destroy_pool(pkt2->pool);
}

if (use_strict_kex == TRUE) {
sftp_ssh2_packet_reset_client_seqno();
sftp_ssh2_packet_reset_server_seqno();
}

/* Last but certainly not least, set up the keys for encryption and
* authentication, based on H and K.
*/
Expand All @@ -5489,6 +5555,9 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
destroy_pool(pkt->pool);
cmd = NULL;

/* We've now completed our KEX, possibly our first. */
kex_done_first_kex = TRUE;

/* If extension negotiation has not been disabled, AND if we have not
* received a service request, AND if the client sent "ext-info-c", THEN
* send our EXT_INFO. We do not want send this during rekeys.
Expand Down Expand Up @@ -5542,6 +5611,12 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
}
}

/* Only start the TAP timer after we have completed our first KEX.
* Otherwise, we risk sending "illegal" packets prior to, or during,
* a "strict KEX" session (Issue 1760).
*/
sftp_tap_start_policy();

/* Reset this flag for the next time through. */
kex_sent_kexinit = FALSE;

Expand Down Expand Up @@ -5571,7 +5646,7 @@ int sftp_kex_free(void) {
destroy_kex(rekey_kex);
}

if (kex_pool) {
if (kex_pool != NULL) {
destroy_pool(kex_pool);
kex_pool = NULL;
}
Expand Down Expand Up @@ -5747,7 +5822,7 @@ int sftp_kex_send_first_kexinit(void) {
struct ssh2_packet *pkt;
int res;

if (!kex_pool) {
if (kex_pool == NULL) {
kex_pool = make_sub_pool(sftp_pool);
pr_pool_tag(kex_pool, "Kex Pool");
}
Expand Down Expand Up @@ -5782,4 +5857,3 @@ int sftp_kex_send_first_kexinit(void) {
destroy_pool(pkt->pool);
return 0;
}

8 changes: 6 additions & 2 deletions contrib/mod_sftp/mod_sftp.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* ProFTPD - mod_sftp
* Copyright (c) 2008-2022 TJ Saunders
* Copyright (c) 2008-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -1718,8 +1718,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
config_rec *c;
unsigned long opts = 0UL;

if (cmd->argc-1 == 0)
if (cmd->argc-1 == 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}

CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);

Expand Down Expand Up @@ -1786,6 +1787,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
} else if (strcmp(cmd->argv[i], "NoHostkeyRotation") == 0) {
opts |= SFTP_OPT_NO_HOSTKEY_ROTATION;

} else if (strcmp(cmd->argv[i], "NoStrictKex") == 0) {
opts |= SFTP_OPT_NO_STRICT_KEX;

} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown SFTPOption '",
cmd->argv[i], "'", NULL));
Expand Down
37 changes: 19 additions & 18 deletions contrib/mod_sftp/mod_sftp.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,25 @@
#define SFTP_SESS_STATE_HAVE_EXT_INFO 0x00010

/* mod_sftp option flags */
#define SFTP_OPT_IGNORE_SFTP_UPLOAD_PERMS 0x00001
#define SFTP_OPT_IGNORE_SCP_UPLOAD_PERMS 0x00002
#define SFTP_OPT_PESSIMISTIC_KEXINIT 0x00004
#define SFTP_OPT_OLD_PROTO_COMPAT 0x00008
#define SFTP_OPT_MATCH_KEY_SUBJECT 0x00010
#define SFTP_OPT_IGNORE_SFTP_SET_PERMS 0x00020
#define SFTP_OPT_IGNORE_SFTP_SET_TIMES 0x00040
#define SFTP_OPT_IGNORE_SFTP_SET_OWNERS 0x00080
#define SFTP_OPT_IGNORE_SCP_UPLOAD_TIMES 0x00100
#define SFTP_OPT_ALLOW_INSECURE_LOGIN 0x00200
#define SFTP_OPT_INSECURE_HOSTKEY_PERMS 0x00400
#define SFTP_OPT_ALLOW_WEAK_DH 0x00800
#define SFTP_OPT_IGNORE_FIFOS 0x01000
#define SFTP_OPT_IGNORE_SFTP_UPLOAD_XATTRS 0x02000
#define SFTP_OPT_IGNORE_SFTP_SET_XATTRS 0x04000
#define SFTP_OPT_INCLUDE_SFTP_TIMES 0x08000
#define SFTP_OPT_NO_EXT_INFO 0x10000
#define SFTP_OPT_NO_HOSTKEY_ROTATION 0x20000
#define SFTP_OPT_IGNORE_SFTP_UPLOAD_PERMS 0x000001
#define SFTP_OPT_IGNORE_SCP_UPLOAD_PERMS 0x000002
#define SFTP_OPT_PESSIMISTIC_KEXINIT 0x000004
#define SFTP_OPT_OLD_PROTO_COMPAT 0x000008
#define SFTP_OPT_MATCH_KEY_SUBJECT 0x000010
#define SFTP_OPT_IGNORE_SFTP_SET_PERMS 0x000020
#define SFTP_OPT_IGNORE_SFTP_SET_TIMES 0x000040
#define SFTP_OPT_IGNORE_SFTP_SET_OWNERS 0x000080
#define SFTP_OPT_IGNORE_SCP_UPLOAD_TIMES 0x000100
#define SFTP_OPT_ALLOW_INSECURE_LOGIN 0x000200
#define SFTP_OPT_INSECURE_HOSTKEY_PERMS 0x000400
#define SFTP_OPT_ALLOW_WEAK_DH 0x000800
#define SFTP_OPT_IGNORE_FIFOS 0x001000
#define SFTP_OPT_IGNORE_SFTP_UPLOAD_XATTRS 0x002000
#define SFTP_OPT_IGNORE_SFTP_SET_XATTRS 0x004000
#define SFTP_OPT_INCLUDE_SFTP_TIMES 0x008000
#define SFTP_OPT_NO_EXT_INFO 0x010000
#define SFTP_OPT_NO_HOSTKEY_ROTATION 0x020000
#define SFTP_OPT_NO_STRICT_KEX 0x040000

/* mod_sftp service flags */
#define SFTP_SERVICE_FL_SFTP 0x0001
Expand Down
12 changes: 12 additions & 0 deletions contrib/mod_sftp/packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -2066,6 +2066,18 @@ int sftp_ssh2_packet_rekey_set_size(off_t size) {
return 0;
}

uint32_t sftp_ssh2_packet_get_client_seqno(void) {
return packet_client_seqno;
}

void sftp_ssh2_packet_reset_client_seqno(void) {
packet_client_seqno = 0;
}

void sftp_ssh2_packet_reset_server_seqno(void) {
packet_server_seqno = 0;
}

int sftp_ssh2_packet_send_version(void) {
if (!sent_version_id) {
int res;
Expand Down
9 changes: 8 additions & 1 deletion contrib/mod_sftp/packet.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* ProFTPD - mod_sftp packet IO
* Copyright (c) 2008-2021 TJ Saunders
* Copyright (c) 2008-2023 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -117,6 +117,13 @@ int sftp_ssh2_packet_rekey_reset(void);
int sftp_ssh2_packet_rekey_set_seqno(uint32_t);
int sftp_ssh2_packet_rekey_set_size(off_t);

/* These are used for implementing the "strict KEX" mitigations of the Terrapin
* attack (Issue 1760).
*/
uint32_t sftp_ssh2_packet_get_client_seqno(void);
void sftp_ssh2_packet_reset_client_seqno(void);
void sftp_ssh2_packet_reset_server_seqno(void);

int sftp_ssh2_packet_send_version(void);
int sftp_ssh2_packet_set_poll_timeout(int);
int sftp_ssh2_packet_set_version(const char *);
Expand Down

0 comments on commit bcec15e

Please sign in to comment.