proftpd: Fix CVE-2023-48795

Upstream-Status: Backport from bcec15efe6

Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
(cherry picked from commit 6c8ae54fc3)
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Vijay Anusuri
2025-12-10 23:08:37 +05:30
committed by Anuj Mittal
parent 5775e1a643
commit b4812b18ee
2 changed files with 786 additions and 0 deletions

View File

@@ -0,0 +1,785 @@
From 05afec052cea4de87a8467f9c5583ffe226ce06e Mon Sep 17 00:00:00 2001
From: TJ Saunders <tj@castaglia.org>
Date: Tue, 19 Dec 2023 18:55:58 -0800
Subject: [PATCH] Issue #1760: Implement the "strict KEX" mitigations for the
Terrapin SSH protocol attack (CVE-2023-48795).
Upstream-Status: Backport [https://github.com/proftpd/proftpd/commit/bcec15efe6c53dac40420731013f1cd2fd54123b]
CVE: CVE-2023-48795
Signed-off-by: Vijay Anusuri <vanusuri@mvista.com>
Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
contrib/mod_sftp/kex.c | 126 +++++++---
contrib/mod_sftp/mod_sftp.c | 8 +-
contrib/mod_sftp/mod_sftp.h.in | 35 +--
contrib/mod_sftp/packet.c | 12 +
contrib/mod_sftp/packet.h | 9 +-
contrib/mod_sftp/tap.c | 26 ++-
contrib/mod_sftp/tap.h | 5 +-
doc/contrib/mod_sftp.html | 15 +-
tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm | 217 ++++++++++++++++++
9 files changed, 398 insertions(+), 55 deletions(-)
diff --git a/contrib/mod_sftp/kex.c b/contrib/mod_sftp/kex.c
index 6a19c88bb..3d9068139 100644
--- a/contrib/mod_sftp/kex.c
+++ b/contrib/mod_sftp/kex.c
@@ -149,6 +149,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 =
@@ -1578,6 +1585,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;
}
@@ -2159,6 +2176,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 "
@@ -4391,7 +4423,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,
@@ -4442,6 +4473,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.
*/
mesg_type = sftp_ssh2_packet_get_mesg_type(pkt);
@@ -4469,36 +4504,44 @@ static struct ssh2_packet *read_kex_packet(pool *p, struct sftp_kex *kex,
break;
}
- switch (mesg_type) {
- case SFTP_SSH2_MSG_DEBUG:
- sftp_ssh2_packet_handle_debug(pkt);
- pr_response_set_pool(NULL);
- pkt = NULL;
- break;
-
+ switch (msg_type) {
+ /* 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_mesg_type_desc(mesg_type), mesg_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);
@@ -4519,7 +4562,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
@@ -4528,7 +4571,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 {
@@ -4564,6 +4607,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.
*
@@ -4622,7 +4683,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) {
@@ -4645,7 +4706,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) {
@@ -4770,7 +4831,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");
@@ -4794,6 +4855,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.
*/
@@ -4813,6 +4879,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.
@@ -4849,6 +4918,12 @@ int sftp_kex_handle(struct ssh2_packet *pkt) {
cmd = NULL;
}
+ /* 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;
@@ -4878,7 +4953,7 @@ int sftp_kex_free(void) {
destroy_kex(rekey_kex);
}
- if (kex_pool) {
+ if (kex_pool != NULL) {
destroy_pool(kex_pool);
kex_pool = NULL;
}
@@ -5050,7 +5125,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");
}
@@ -5085,4 +5160,3 @@ int sftp_kex_send_first_kexinit(void) {
destroy_pool(pkt->pool);
return 0;
}
-
diff --git a/contrib/mod_sftp/mod_sftp.c b/contrib/mod_sftp/mod_sftp.c
index a88162c0d..1f94e4d9d 100644
--- a/contrib/mod_sftp/mod_sftp.c
+++ b/contrib/mod_sftp/mod_sftp.c
@@ -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
@@ -1415,8 +1415,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);
@@ -1480,6 +1481,9 @@ MODRET set_sftpoptions(cmd_rec *cmd) {
} else if (strcmp(cmd->argv[i], "NoExtensionNegotiation") == 0) {
opts |= SFTP_OPT_NO_EXT_INFO;
+ } 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));
diff --git a/contrib/mod_sftp/mod_sftp.h.in b/contrib/mod_sftp/mod_sftp.h.in
index 9af590e8e..bde722f23 100644
--- a/contrib/mod_sftp/mod_sftp.h.in
+++ b/contrib/mod_sftp/mod_sftp.h.in
@@ -126,23 +126,24 @@
#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_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_STRICT_KEX 0x040000
/* mod_sftp service flags */
#define SFTP_SERVICE_FL_SFTP 0x0001
diff --git a/contrib/mod_sftp/packet.c b/contrib/mod_sftp/packet.c
index b0067ea80..d8c97e92b 100644
--- a/contrib/mod_sftp/packet.c
+++ b/contrib/mod_sftp/packet.c
@@ -1742,6 +1742,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;
diff --git a/contrib/mod_sftp/packet.h b/contrib/mod_sftp/packet.h
index a424e9b25..fe538cbd7 100644
--- a/contrib/mod_sftp/packet.h
+++ b/contrib/mod_sftp/packet.h
@@ -1,6 +1,6 @@
/*
* ProFTPD - mod_sftp packet IO
- * Copyright (c) 2008-2020 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
@@ -107,6 +107,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 *);
diff --git a/contrib/mod_sftp/tap.c b/contrib/mod_sftp/tap.c
index 95f388e43..7eaf959e2 100644
--- a/contrib/mod_sftp/tap.c
+++ b/contrib/mod_sftp/tap.c
@@ -1,6 +1,6 @@
/*
* ProFTPD - mod_sftp traffic analysis protection
- * Copyright (c) 2008-2016 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
@@ -149,7 +149,6 @@ static void set_policy_chance(struct sftp_tap_policy *policy) {
}
static void set_policy_timer(struct sftp_tap_policy *policy) {
-
/* Start a timer which checks the last times we received and sent packets.
* From there, we may want to inject a TAP message, depending on the
* policy.
@@ -177,6 +176,16 @@ int sftp_tap_send_packet(void) {
int rnd;
unsigned int chance;
+ /* Due to chances of violating client-side "strict KEX" Terrapin
+ * mitigations, we will not send packets if we are in the middle of a KEX.
+ */
+ if (!(sftp_sess_state & SFTP_SESS_STATE_HAVE_KEX) ||
+ (sftp_sess_state & SFTP_SESS_STATE_REKEYING)) {
+ pr_trace_msg(trace_channel, 11,
+ "unwilling to send TAP packet during KEX");
+ return 0;
+ }
+
if (!sftp_interop_supports_feature(SFTP_SSH2_FEAT_IGNORE_MSG)) {
pr_trace_msg(trace_channel, 3,
"unable to send TAP packet: IGNORE not supported by client");
@@ -205,7 +214,7 @@ int sftp_tap_send_packet(void) {
struct ssh2_packet *pkt;
unsigned int max_datalen = 8192;
- if (curr_policy.max_datalen) {
+ if (curr_policy.max_datalen > 0) {
max_datalen = curr_policy.max_datalen;
}
@@ -246,15 +255,15 @@ int sftp_tap_send_packet(void) {
int sftp_tap_set_policy(const char *policy) {
register unsigned int i;
- if (tap_pool) {
+ if (tap_pool != NULL) {
/* Special case: IFF the existing policy is 'none' AND the given
* policy is 'rogaway', just return. The 'none' policy must have been
* explicitly configured, and it should override the automatic use of
* the 'rogaway' policy.
*/
- if (strncmp(curr_policy.policy, "none", 5) == 0 &&
- strncasecmp(policy, "rogaway", 8) == 0) {
+ if (strcasecmp(curr_policy.policy, "none") == 0 &&
+ strcasecmp(policy, "rogaway") == 0) {
(void) pr_log_writefile(sftp_logfd, MOD_SFTP_VERSION,
"'none' traffic policy explicitly configured, ignoring '%s' policy",
policy);
@@ -278,7 +287,6 @@ int sftp_tap_set_policy(const char *policy) {
if (strcasecmp(tap_policies[i].policy, policy) == 0) {
copy_policy(&curr_policy, &(tap_policies[i]));
set_policy_chance(&curr_policy);
- set_policy_timer(&curr_policy);
return 0;
}
}
@@ -286,3 +294,7 @@ int sftp_tap_set_policy(const char *policy) {
errno = ENOENT;
return -1;
}
+
+void sftp_tap_start_policy(void) {
+ set_policy_timer(&curr_policy);
+}
diff --git a/contrib/mod_sftp/tap.h b/contrib/mod_sftp/tap.h
index 4a4c065d2..312223595 100644
--- a/contrib/mod_sftp/tap.h
+++ b/contrib/mod_sftp/tap.h
@@ -1,6 +1,6 @@
/*
* ProFTPD - mod_sftp traffic analysis protection
- * Copyright (c) 2008-2016 TJ Saunders
+ * Copyright (c) 2008-2013 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
@@ -63,4 +63,7 @@ int sftp_tap_send_packet(void);
*/
int sftp_tap_set_policy(const char *);
+/* Sets the configured TAP policy in motion. */
+void sftp_tap_start_policy(void);
+
#endif /* MOD_SFTP_TAP_H */
diff --git a/doc/contrib/mod_sftp.html b/doc/contrib/mod_sftp.html
index 60c3436fa..cfc639d05 100644
--- a/doc/contrib/mod_sftp.html
+++ b/doc/contrib/mod_sftp.html
@@ -1186,6 +1186,19 @@ The currently implemented options are:
<code>proftpd-1.3.7rc4</code>.
</li>
+ <p>
+ <li><code>NoStrictKex</code><br>
+ <p>
+ By default, <code>mod_sftp</code> will honor/support the OpenSSH
+ "strict KEX" mode extension, "kex-strict-c-v00@openssh.com" and
+ "kex-strict-s-v00@openssh.com". Use this option to disable support for
+ these custom OpenSSH extensions.
+
+ <p>
+ <b>Note</b> that this option first appeared in
+ <code>proftpd-1.3.9rc1</code>.
+ </li>
+
<p>
<li><code>OldProtocolCompat</code><br>
<p>
@@ -2642,7 +2655,7 @@ deal with this issue, then, you can hopefully upgrade to ProFTPD 1.3.6 or later,
<p>
<hr>
<font size=2><b><i>
-&copy; Copyright 2008-2021 TJ Saunders<br>
+&copy; Copyright 2008-2023 TJ Saunders<br>
All Rights Reserved<br>
</i></b></font>
<hr>
diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
index b4bdf516b..8c2be5465 100644
--- a/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
+++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_sftp.pm
@@ -87,6 +87,11 @@ my $TESTS = {
test_class => [qw(forking ssh2)],
},
+ ssh2_ext_kex_strict_terrapin_issue1760 => {
+ order => ++$order,
+ test_class => [qw(bug forking ssh2)],
+ },
+
ssh2_hostkey_rsa => {
order => ++$order,
test_class => [qw(forking ssh2)],
@@ -3885,6 +3890,218 @@ EOC
unlink($log_file);
}
+sub ssh2_ext_kex_strict_terrapin_issue1760 {
+ my $self = shift;
+ my $tmpdir = $self->{tmpdir};
+ my $setup = test_setup($tmpdir, 'sftp');
+
+ my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_rsa_key');
+ my $dsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/ssh_host_dsa_key');
+
+ my $rsa_priv_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key');
+ my $rsa_pub_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/test_rsa_key.pub');
+ my $rsa_rfc4716_key = File::Spec->rel2abs('t/etc/modules/mod_sftp/authorized_rsa_keys');
+
+ my $authorized_keys = File::Spec->rel2abs("$tmpdir/.authorized_keys");
+ unless (copy($rsa_rfc4716_key, $authorized_keys)) {
+ die("Can't copy $rsa_rfc4716_key to $authorized_keys: $!");
+ }
+
+ my $ssh_config = File::Spec->rel2abs("$tmpdir/ssh.conf");
+ if (open(my $fh, "> $ssh_config")) {
+ print $fh <<EOC;
+HostKeyAlgorithms rsa-sha2-256
+IdentityAgent none
+PubkeyAcceptedKeyTypes rsa-sha2-256
+EOC
+ unless (close($fh)) {
+ die("Can't write $ssh_config: $!");
+ }
+
+ } else {
+ die("Can't open $ssh_config: $!");
+ }
+
+ my $batch_file = File::Spec->rel2abs("$tmpdir/sftp-batch.conf");
+ if (open(my $fh, "> $batch_file")) {
+ print $fh "ls -l\n";
+
+ unless (close($fh)) {
+ die("Can't write $batch_file: $!");
+ }
+
+ } else {
+ die("Can't open $batch_file: $!");
+ }
+
+ my $config = {
+ PidFile => $setup->{pid_file},
+ ScoreboardFile => $setup->{scoreboard_file},
+ SystemLog => $setup->{log_file},
+ TraceLog => $setup->{log_file},
+ Trace => 'ssh2:30 sftp:20 scp:20',
+
+ AuthUserFile => $setup->{auth_user_file},
+ AuthGroupFile => $setup->{auth_group_file},
+ AuthOrder => 'mod_auth_file.c',
+
+ IfModules => {
+ 'mod_delay.c' => {
+ DelayEngine => 'off',
+ },
+
+ 'mod_sftp.c' => [
+ "SFTPEngine on",
+ "SFTPLog $setup->{log_file}",
+
+ "SFTPHostKey $rsa_host_key",
+ "SFTPHostKey $dsa_host_key",
+
+ "SFTPAuthorizedUserKeys file:~/.authorized_keys",
+ ],
+ },
+ };
+
+ my ($port, $config_user, $config_group) = config_write($setup->{config_file},
+ $config);
+
+ # Open pipes, for use between the parent and child processes. Specifically,
+ # the child will indicate when it's done with its test by writing a message
+ # to the parent.
+ my ($rfh, $wfh);
+ unless (pipe($rfh, $wfh)) {
+ die("Can't open pipe: $!");
+ }
+
+ require Net::SSH2;
+
+ my $ex;
+
+ # Fork child
+ $self->handle_sigchld();
+ defined(my $pid = fork()) or die("Can't fork: $!");
+ if ($pid) {
+ eval {
+ # We use OpenSSH-9.6p1 to test our "strict KEX" Terrapin mitigations.
+ my $sftp = '/Users/tj/local/openssh-9.6p1/bin/sftp';
+
+ my @cmd = (
+ $sftp,
+ '-F',
+ $ssh_config,
+ '-oBatchMode=yes',
+ '-oCheckHostIP=no',
+ '-oCompression=yes',
+ "-oPort=$port",
+ "-oIdentityFile=$rsa_priv_key",
+ '-oPubkeyAuthentication=yes',
+ '-oStrictHostKeyChecking=no',
+ '-oUserKnownHostsFile=/dev/null',
+ '-vvv',
+ '-b',
+ $batch_file,
+ "$setup->{user}\@127.0.0.1",
+ );
+
+ my $sftp_rh = IO::Handle->new();
+ my $sftp_wh = IO::Handle->new();
+ my $sftp_eh = IO::Handle->new();
+
+ $sftp_wh->autoflush(1);
+
+ sleep(1);
+
+ local $SIG{CHLD} = 'DEFAULT';
+
+ # Make sure that the perms on the priv key are what OpenSSH wants
+ unless (chmod(0400, $rsa_priv_key)) {
+ die("Can't set perms on $rsa_priv_key to 0400: $!");
+ }
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "Executing: ", join(' ', @cmd), "\n";
+ }
+
+ my $sftp_pid = open3($sftp_wh, $sftp_rh, $sftp_eh, @cmd);
+ waitpid($sftp_pid, 0);
+ my $exit_status = $?;
+
+ # Restore the perms on the priv key
+ unless (chmod(0644, $rsa_priv_key)) {
+ die("Can't set perms on $rsa_priv_key to 0644: $!");
+ }
+
+ my ($res, $errstr);
+ if ($exit_status >> 8 == 0) {
+ $errstr = join('', <$sftp_eh>);
+ $res = 0;
+
+ } else {
+ $errstr = join('', <$sftp_eh>);
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "Stderr: $errstr\n";
+ }
+
+ $res = 1;
+ }
+
+ unless ($res == 0) {
+ die("Can't list files on server: $errstr");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ $wfh->print("done\n");
+ $wfh->flush();
+
+ } else {
+ eval { server_wait($setup->{config_file}, $rfh) };
+ if ($@) {
+ warn($@);
+ exit 1;
+ }
+
+ exit 0;
+ }
+
+ # Stop server
+ server_stop($setup->{pid_file});
+ $self->assert_child_ok($pid);
+
+ eval {
+ if (open(my $fh, "< $setup->{log_file}")) {
+ my $ok = 0;
+
+ while (my $line = <$fh>) {
+ chomp($line);
+
+ if ($ENV{TEST_VERBOSE}) {
+ print STDERR "# $line\n";
+ }
+
+ if ($line =~ /client signaled strict KEX support/) {
+ $ok = 1;
+ last;
+ }
+ }
+
+ close($fh);
+
+ $self->assert($ok, test_msg("Did not see expected 'strict KEX' TraceLog message"));
+
+ } else {
+ die("Can't read $setup->{log_file}: $!");
+ }
+ };
+ if ($@) {
+ $ex = $@;
+ }
+
+ test_cleanup($setup->{log_file}, $ex);
+}
+
sub ssh2_hostkey_rsa {
my $self = shift;
my $tmpdir = $self->{tmpdir};

View File

@@ -17,6 +17,7 @@ SRC_URI = "git://github.com/proftpd/proftpd.git;branch=${BRANCH};protocol=https
file://proftpd.service \
file://CVE-2023-51713.patch \
file://CVE-2024-57392.patch \
file://CVE-2023-48795.patch \
"
S = "${WORKDIR}/git"