[PATCH 1/1] Resolve kex-strict incompatibility with MobaXterm

Chris Rapier rapier at psc.edu
Thu Sep 17 03:16:20 AEST 2026


Hey,

I'm not sure if you will want this but it recently bit me so I thought I
would pass it along. MobaXterm is a windows terminal/ssh client that has a
GUI based SFTP module. When using that module OpenSSH servers newer than
9.6 will get a corrupted MAC on input on AES-CTR rekeys. This happens
because the MobaXterm is using a SFTP library from a company called
n-software (in the banner it shows up as SecureBlackBox.9). This version of
the library doesn't cache the kex-strict flag and expects it to be resent
on every KEXINIT. So it sets the correct seqnr on the initial KEX but won't
reset it on subsequent ones because it's not getting the marker. Which
means the seqnr is no longer in sync and the MAC fails.

I'm assuming most people never run into this issue because of the 32GB
default rekey limit. I only ran into it because with AES-CTR I do an
immediate rekey post-auth to load the parallel AES-CTR cipher. However, I
confirmed that this is a problem with OpenSSH by setting the rekey limit to
1MB. MobaXterm crashes out in that scenario as unlikely as it might be.
I've filed a bug report to MobaXterm and encouraged them to file one with
n-software. In the meantime I created a patch to set a compat flag if the
remote advertises SecureBlackBox. Tested and works as advertised. I don't
think this has any security implications. Again, not sure if tis is
anything you care about but it does hit OpenSSH in the right conditions.

--- a/compat.c  2026-09-16 13:01:54.361487706 -0400
+++ b/compat.c  2026-09-16 13:02:38.664696940 -0400
@@ -121,6 +121,8 @@
                                        SSH_BUG_UTF8TTYMODE },
                { "Twisted_*",          0 },
                { "Twisted*",           SSH_BUG_DEBUG },
+               /* MobaXterm's SFTP engine, see kex_input_newkeys */
+               { "SecureBlackbox*",    SSH_BUG_STRICT_KEX_REKEY },
                { NULL,                 0 }
        };

diff -ruN a/compat.h b/compat.h
--- a/compat.h  2026-09-16 13:01:54.352487665 -0400
+++ b/compat.h  2026-09-16 13:02:35.816683009 -0400
@@ -46,6 +46,7 @@
 /* #define unused              0x00010000 */
 /* #define unused              0x00020000 */
 /* #define unused              0x00040000 */
+#define SSH_BUG_STRICT_KEX_REKEY       0x00080000
 /* #define unused              0x00100000 */
 #define SSH_BUG_EXTEOF         0x00200000
 #define SSH_BUG_PROBE          0x00400000
diff -ruN a/kex.c b/kex.c
--- a/kex.c     2026-09-16 13:01:54.371487751 -0400
+++ b/kex.c     2026-09-16 13:02:47.351739831 -0400
@@ -530,6 +530,7 @@
        struct kex *kex = ssh->kex;
        int r, initial = (kex->flags & KEX_INITIAL) != 0;
        char *cp, **prop;
+       const char *rekey_denylist;

        debug("SSH2_MSG_NEWKEYS received");
        if (kex->ext_info_c && initial)
@@ -541,13 +542,27 @@
        if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0)
                return r;
        if (initial) {
-               /* Remove initial KEX signalling from proposal for rekeying
*/
+               /*
+                * Remove initial KEX signalling from proposal for rekeying.
+                * SecureBlackbox re-derives strict KEX from every KEXINIT
+                * instead of latching it at the initial exchange. Without
+                * the marker in the rekey it stops resetting its sequence
+                * number while we still reset ours and the MAC desyncs, so
+                * keep advertising kex-strict to it on rekeys. Compliant
+                * peers ignore the marker after the initial exchange.
+                */
+               if (ssh->compat & SSH_BUG_STRICT_KEX_REKEY) {
+                       rekey_denylist = kex->server ?
+                           "ext-info-s" : "ext-info-c";
+               } else {
+                       rekey_denylist = kex->server ?
+                           "ext-info-s,kex-strict-s-v00 at openssh.com" :
+                           "ext-info-c,kex-strict-c-v00 at openssh.com";
+               }
                if ((r = kex_buf2prop(kex->my, NULL, &prop)) != 0)
                        return r;
                if ((cp = match_filter_denylist(prop[PROPOSAL_KEX_ALGS],
-                   kex->server ?
-                   "ext-info-s,kex-strict-s-v00 at openssh.com" :
-                   "ext-info-c,kex-strict-c-v00 at openssh.com")) == NULL) {
+                   rekey_denylist)) == NULL) {
                        error_f("match_filter_denylist failed");
                        goto fail;
                }


More information about the openssh-unix-dev mailing list