11/*
22 * ProFTPD - mod_proxy SSH key exchange (kex)
3- * Copyright (c) 2021-2022 TJ Saunders
3+ * Copyright (c) 2021-2023 TJ Saunders
44 *
55 * This program is free software; you can redistribute it and/or modify
66 * it under the terms of the GNU General Public License as published by
@@ -183,6 +183,13 @@ static struct proxy_ssh_kex *kex_first_kex = NULL;
183183static struct proxy_ssh_kex * kex_rekey_kex = NULL ;
184184static int kex_sent_kexinit = FALSE;
185185
186+ /* Using strict kex? Note that we maintain this value here, rather than
187+ * in the proxy_ssh_kex struct, so that any "use strict KEX" flag set via the
188+ * first KEXINIT is used through any subsequent KEXINITs.
189+ */
190+ static int use_strict_kex = FALSE;
191+ static int kex_done_first_kex = FALSE;
192+
186193/* Diffie-Hellman group moduli */
187194
188195static const char * dh_group1_str =
@@ -1660,6 +1667,16 @@ static const char *get_kexinit_exchange_list(pool *p) {
16601667 res = pstrcat (p , res , * res ? "," : "" , pstrdup (p , "ext-info-c" ), NULL );
16611668 }
16621669
1670+ if (!(proxy_opts & PROXY_OPT_SSH_NO_STRICT_KEX )) {
1671+ /* Indicate support for OpenSSH's custom "strict KEX" mode extension,
1672+ * but only if we have not done/completed our first KEX.
1673+ */
1674+ if (kex_done_first_kex == FALSE) {
1675+ res = pstrcat (p , res , * res ? "," : "" ,
1676+ pstrdup (p , "kex-strict-c-v00@openssh.com" ), NULL );
1677+ }
1678+ }
1679+
16631680 return res ;
16641681}
16651682
@@ -2271,6 +2288,21 @@ static int get_session_names(struct proxy_ssh_kex *kex, int *correct_guess) {
22712288 pr_trace_msg (trace_channel , 20 , "server %s EXT_INFO support" ,
22722289 kex -> use_ext_info ? "signaled" : "did not signal" );
22732290
2291+ if (!(proxy_opts & PROXY_OPT_SSH_NO_STRICT_KEX )) {
2292+ /* Did the server indicate "strict kex" support (Issue 257)?
2293+ *
2294+ * Note that we only check for this if it is our first KEXINIT.
2295+ * The "strict kex" extension is ignored in any subsequent KEXINITs, as
2296+ * for rekeys.
2297+ */
2298+ if (kex_done_first_kex == FALSE) {
2299+ use_strict_kex = proxy_ssh_misc_namelist_contains (kex -> pool ,
2300+ server_list , "kex-strict-s-v00@openssh.com" );
2301+ pr_trace_msg (trace_channel , 20 , "server %s strict KEX support" ,
2302+ use_strict_kex ? "signaled" : "did not signal" );
2303+ }
2304+ }
2305+
22742306 } else {
22752307 (void ) pr_log_writefile (proxy_logfd , MOD_PROXY_VERSION ,
22762308 "no shared key exchange algorithm found (client sent '%s', server sent "
@@ -3018,6 +3050,10 @@ static struct proxy_ssh_packet *read_kex_packet(pool *p,
30183050 /* Per RFC 4253, Section 11, DEBUG, DISCONNECT, IGNORE, and UNIMPLEMENTED
30193051 * messages can occur at any time, even during KEX. We have to be prepared
30203052 * for this, and Do The Right Thing(tm).
3053+ *
3054+ * However, due to the Terrapin attack, if we are using a "strict KEX"
3055+ * mode, then only DISCONNECT messages can occur during KEX; DEBUG,
3056+ * IGNORE, and UNIMPLEMENTED messages will lead to disconnecting.
30213057 */
30223058
30233059 msg_type = proxy_ssh_packet_get_msg_type (pkt );
@@ -3046,35 +3082,43 @@ static struct proxy_ssh_packet *read_kex_packet(pool *p,
30463082 }
30473083
30483084 switch (msg_type ) {
3049- case PROXY_SSH_MSG_DEBUG :
3050- proxy_ssh_packet_handle_debug (pkt );
3051- pr_response_set_pool (NULL );
3052- pkt = NULL ;
3053- break ;
3054-
3085+ /* DISCONNECT messages are always allowed. */
30553086 case PROXY_SSH_MSG_DISCONNECT :
30563087 proxy_ssh_packet_handle_disconnect (pkt );
30573088 pr_response_set_pool (NULL );
30583089 pkt = NULL ;
30593090 break ;
30603091
3092+ case PROXY_SSH_MSG_DEBUG :
3093+ if (use_strict_kex == FALSE) {
3094+ proxy_ssh_packet_handle_debug (pkt );
3095+ pr_response_set_pool (NULL );
3096+ pkt = NULL ;
3097+ break ;
3098+ }
3099+
30613100 case PROXY_SSH_MSG_IGNORE :
3062- proxy_ssh_packet_handle_ignore (pkt );
3063- pr_response_set_pool (NULL );
3064- pkt = NULL ;
3065- break ;
3101+ if (use_strict_kex == FALSE) {
3102+ proxy_ssh_packet_handle_ignore (pkt );
3103+ pr_response_set_pool (NULL );
3104+ pkt = NULL ;
3105+ break ;
3106+ }
30663107
30673108 case PROXY_SSH_MSG_UNIMPLEMENTED :
3068- proxy_ssh_packet_handle_unimplemented (pkt );
3069- pr_response_set_pool (NULL );
3070- pkt = NULL ;
3071- break ;
3109+ if (use_strict_kex == FALSE) {
3110+ proxy_ssh_packet_handle_unimplemented (pkt );
3111+ pr_response_set_pool (NULL );
3112+ pkt = NULL ;
3113+ break ;
3114+ }
30723115
30733116 default :
30743117 /* For any other message type, it's considered a protocol error. */
30753118 (void ) pr_log_writefile (proxy_logfd , MOD_PROXY_VERSION ,
3076- "received %s (%d) unexpectedly, disconnecting" ,
3077- proxy_ssh_packet_get_msg_type_desc (msg_type ), msg_type );
3119+ "received %s (%d) unexpectedly%s, disconnecting" ,
3120+ proxy_ssh_packet_get_msg_type_desc (msg_type ), msg_type ,
3121+ use_strict_kex ? " during strict KEX" : "" );
30783122 pr_response_set_pool (NULL );
30793123 destroy_kex (kex );
30803124 destroy_pool (pkt -> pool );
@@ -4903,6 +4947,25 @@ int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt,
49034947 return -1 ;
49044948 }
49054949
4950+ if (use_strict_kex == TRUE &&
4951+ kex_done_first_kex == FALSE) {
4952+ uint32_t server_seqno ;
4953+
4954+ server_seqno = proxy_ssh_packet_get_server_seqno ();
4955+ if (server_seqno != 1 ) {
4956+ /* Receiving any messages other than a KEXINIT as the first server
4957+ * message indicates the possibility of the Terrapin attack being
4958+ * conducted (Issue 257). Thus we disconnect the server in such
4959+ * cases.
4960+ */
4961+ (void ) pr_log_writefile (proxy_logfd , MOD_PROXY_VERSION ,
4962+ "'strict KEX' violation, as KEXINIT was not the first message; disconnecting" );
4963+ destroy_kex (kex );
4964+ PROXY_SSH_DISCONNECT_CONN (proxy_sess -> backend_ctrl_conn ,
4965+ PROXY_SSH_DISCONNECT_BY_APPLICATION , NULL );
4966+ }
4967+ }
4968+
49064969 /* Once we have received the server KEXINIT message, we can compare what we
49074970 * want to send against what we already received from the server.
49084971 *
@@ -5063,6 +5126,11 @@ int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt,
50635126 sent_newkeys = TRUE;
50645127 }
50655128
5129+ if (use_strict_kex == TRUE) {
5130+ proxy_ssh_packet_reset_client_seqno ();
5131+ proxy_ssh_packet_reset_server_seqno ();
5132+ }
5133+
50665134 /* Last but certainly not least, set up the keys for encryption and
50675135 * authentication, based on H and K.
50685136 */
@@ -5075,6 +5143,9 @@ int proxy_ssh_kex_handle(struct proxy_ssh_packet *pkt,
50755143 PROXY_SSH_DISCONNECT_BY_APPLICATION , NULL );
50765144 }
50775145
5146+ /* We've now completed our KEX, possibly our first. */
5147+ kex_done_first_kex = TRUE;
5148+
50785149 destroy_pool (pkt -> pool );
50795150 destroy_kex (kex );
50805151 return 0 ;
0 commit comments