From bc43c0d9468a8d50ba141c8a965f9f07ed0456ff Mon Sep 17 00:00:00 2001
From: Guilhem Moulin <guilhem@fripost.org>
Date: Mon, 3 Aug 2020 19:20:05 +0200
Subject: libinterimap: Fix response injection vulnerability after STARTTLS.

For background see https://gitlab.com/muttmua/mutt/-/issues/248 .
---
 tests/list                                |  1 +
 tests/starttls-injection/imapd            | 77 +++++++++++++++++++++++++++++++
 tests/starttls-injection/interimap.remote |  1 +
 tests/starttls-injection/remote.conf      |  6 +++
 tests/starttls-injection/t                | 16 +++++++
 5 files changed, 101 insertions(+)
 create mode 100755 tests/starttls-injection/imapd
 create mode 120000 tests/starttls-injection/interimap.remote
 create mode 100644 tests/starttls-injection/remote.conf
 create mode 100644 tests/starttls-injection/t

(limited to 'tests')

diff --git a/tests/list b/tests/list
index 402ec51..5522ba8 100644
--- a/tests/list
+++ b/tests/list
@@ -46,6 +46,7 @@ split-set   Split large sets to avoid extra-long command lines
 . SSL/TLS
     starttls-logindisabled  LOGINDISABLED STARTTLS
     starttls                STARTTLS
+    starttls-injection      STARTTLS response injection
     tls                     SSL/TLS handshake
     ... tls-verify-peer
     tls-pin-fingerprint     pubkey fingerprint pinning
diff --git a/tests/starttls-injection/imapd b/tests/starttls-injection/imapd
new file mode 100755
index 0000000..9000c8d
--- /dev/null
+++ b/tests/starttls-injection/imapd
@@ -0,0 +1,77 @@
+#!/usr/bin/perl -T
+
+use warnings;
+use strict;
+
+use Errno qw/EINTR/;
+use Net::SSLeay qw/die_now die_if_ssl_error/;
+use Socket qw/INADDR_LOOPBACK AF_INET SOCK_STREAM pack_sockaddr_in
+    SOL_SOCKET SO_REUSEADDR SHUT_RDWR/;
+
+BEGIN {
+    Net::SSLeay::load_error_strings();
+    Net::SSLeay::SSLeay_add_ssl_algorithms();
+    Net::SSLeay::randomize();
+}
+
+socket(my $S, AF_INET, SOCK_STREAM, 0) or die;
+setsockopt($S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
+bind($S, pack_sockaddr_in(10143, INADDR_LOOPBACK)) or die "bind: $!\n";
+listen($S, 1) or die "listen: $!";
+
+my $CONFDIR = $ENV{HOME} =~ /\A(\p{Print}+)\z/ ? "$1/.dovecot/conf.d" : die;
+my $CTX = Net::SSLeay::CTX_new() or die_now("SSL_CTX_new");
+Net::SSLeay::CTX_set_mode($CTX,
+    Net::SSLeay::MODE_ENABLE_PARTIAL_WRITE() |
+    Net::SSLeay::MODE_ACCEPT_MOVING_WRITE_BUFFER() |
+    Net::SSLeay::MODE_AUTO_RETRY() | # don't fail SSL_read on renegotiation
+    Net::SSLeay::MODE_RELEASE_BUFFERS() );
+Net::SSLeay::CTX_use_PrivateKey_file($CTX, "$CONFDIR/dovecot.key", &Net::SSLeay::FILETYPE_PEM)
+    or die_if_ssl_error("Can't load private key: $!");
+Net::SSLeay::CTX_use_certificate_file($CTX, "$CONFDIR/dovecot.pem", &Net::SSLeay::FILETYPE_PEM)
+    or die_if_ssl_error("Can't load certificate: $!");
+
+while (1) {
+    my $sockaddr = accept(my $conn, $S) or do {
+        next if $! == EINTR;
+        die "accept: $!";
+    };
+
+    $conn->printflush("* OK IMAP4rev1 Server\r\n");
+
+    $conn->getline() =~ /\A(\S+) CAPABILITY\r\n\z/ or die;
+    $conn->printflush("* CAPABILITY IMAP4rev1 STARTTLS\r\n");
+    $conn->printflush("$1 OK CAPABILITY completed\r\n");
+
+    $conn->getline() =~ /\A(\S+) STARTTLS\r\n\z/ or die;
+
+    # These responses preceed the TLS handshake hence are not authenticated!
+    $conn->print("$1 OK Begin TLS\r\n");
+    $conn->print("* CAPABILITY IMAP4rev1 LOGINDISABLED X-injected\r\n");
+    # Note: tag format must match Net::IMAP::InterIMAP->_cmd_init()
+    $conn->printf("%06d OK CAPABILITY injected\r\n", $1+1);
+    $conn->flush();
+
+    my $ssl = Net::SSLeay::new($CTX) or die_if_ssl_error("SSL_new");
+    Net::SSLeay::set_fd($ssl, $conn) or die_if_ssl_error("SSL_set_fd");
+    Net::SSLeay::accept($ssl) and die_if_ssl_error("SSL_accept");
+
+    Net::SSLeay::ssl_read_CRLF($ssl) =~ /\A(\S+) CAPABILITY\r\n\z/ or die_now("SSL_read");
+    Net::SSLeay::ssl_write_CRLF($ssl, "* CAPABILITY IMAP4rev1 AUTH=LOGIN\r\n$1 OK CAPABILITY completed");
+
+    Net::SSLeay::ssl_read_CRLF($ssl) =~ /\A(\S+) LOGIN .*\r\n\z/ or die_now("SSL_read");
+    Net::SSLeay::ssl_write_CRLF($ssl, "$1 OK [CAPABILITY IMAP4rev1] LOGIN completed");
+
+    Net::SSLeay::free($ssl);
+    close($conn);
+
+    last;
+}
+
+END {
+    Net::SSLeay::CTX_free($CTX) if defined $CTX;
+    if (defined $S) {
+        shutdown($S, SHUT_RDWR) or warn "shutdown: $!";
+        close($S) or print STDERR "Can't close: $!\n";
+    }
+}
diff --git a/tests/starttls-injection/interimap.remote b/tests/starttls-injection/interimap.remote
new file mode 120000
index 0000000..ad49677
--- /dev/null
+++ b/tests/starttls-injection/interimap.remote
@@ -0,0 +1 @@
+../starttls/interimap.remote
\ No newline at end of file
diff --git a/tests/starttls-injection/remote.conf b/tests/starttls-injection/remote.conf
new file mode 100644
index 0000000..f23f3de
--- /dev/null
+++ b/tests/starttls-injection/remote.conf
@@ -0,0 +1,6 @@
+protocols = $protocols imap
+service imap-login {
+    inet_listener imap {
+        port = 0
+    }
+}
diff --git a/tests/starttls-injection/t b/tests/starttls-injection/t
new file mode 100644
index 0000000..d57aa7a
--- /dev/null
+++ b/tests/starttls-injection/t
@@ -0,0 +1,16 @@
+# Test unauthenticated response injection after the STARTTLS response
+# For background see https://gitlab.com/muttmua/mutt/-/issues/248
+
+env -i USER="remote" HOME="$HOME_remote" "$TESTDIR/imapd" & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+! interimap --debug || error
+
+# Make sure we show a warning but ignore ignore (unauthenticated) injected responses
+! grep -E 'remote: S: .*[ -]injected$' <"$STDERR" || error "unauthenticated response injection"
+grep -Fx 'remote: WARNING: Truncating non-empty output buffer (unauthenticated response injection?)' <"$STDERR" || error
+
+! grep -Fx 'remote: ERROR: Logins are disabled.' <"$STDERR" || error "injected capability wasn't ignored"
+grep -Fx 'remote: ERROR: Server did not advertise ENABLE (RFC 5161) capability.' <"$STDERR" || error "injected capability wasn't ignored"
+
+# vim: set filetype=sh :
-- 
cgit v1.2.3


From 3b2939febdeb7f92051f95a3b08cf86e221ce21d Mon Sep 17 00:00:00 2001
From: Guilhem Moulin <guilhem@fripost.org>
Date: Mon, 3 Aug 2020 20:27:38 +0200
Subject: libinterimap: abort on PREAUTH greeting received on plaintext
 connections

Set "STARTTLS = NO" to ignore.  This is similar to CVE-2020-12398 and
CVE-2020-14093.
---
 tests/list                               |  1 +
 tests/preauth-plaintext/imapd            | 44 ++++++++++++++++++++++++++++++++
 tests/preauth-plaintext/interimap.remote |  1 +
 tests/preauth-plaintext/t                | 19 ++++++++++++++
 4 files changed, 65 insertions(+)
 create mode 100755 tests/preauth-plaintext/imapd
 create mode 120000 tests/preauth-plaintext/interimap.remote
 create mode 100644 tests/preauth-plaintext/t

(limited to 'tests')

diff --git a/tests/list b/tests/list
index 5522ba8..db77f50 100644
--- a/tests/list
+++ b/tests/list
@@ -38,6 +38,7 @@ repair  --repair
     auth-login              LOGIN
     auth-logindisabled      LOGINDISABLED
     auth-noplaintext        abort when STARTTLS is not offered
+    preauth-plaintext       abort on MiTM via PREAUTH greeting
 
 compress    COMPRESS=DEFLATE
 condstore   CONDSTORE
diff --git a/tests/preauth-plaintext/imapd b/tests/preauth-plaintext/imapd
new file mode 100755
index 0000000..8f3ac30
--- /dev/null
+++ b/tests/preauth-plaintext/imapd
@@ -0,0 +1,44 @@
+#!/usr/bin/perl -T
+
+use warnings;
+use strict;
+
+use Errno qw/EINTR/;
+use Socket qw/INADDR_LOOPBACK AF_INET SOCK_STREAM pack_sockaddr_in
+    SOL_SOCKET SO_REUSEADDR SHUT_RDWR/;
+
+socket(my $S, AF_INET, SOCK_STREAM, 0) or die;
+setsockopt($S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) or die;
+bind($S, pack_sockaddr_in(10143, INADDR_LOOPBACK)) or die "bind: $!\n";
+listen($S, 1) or die "listen: $!";
+
+while (1) {
+    my $sockaddr = accept(my $conn, $S) or do {
+        next if $! == EINTR;
+        die "accept: $!";
+    };
+
+    # minimum CAPABILITY list, see tests/snippets/dovecot/interimap-required-capabilities.conf
+    $conn->printflush("* PREAUTH [CAPABILITY IMAP4rev1 ENABLE UIDPLUS LIST-EXTENDED QRESYNC LIST-STATUS] IMAP4rev1 Server\r\n");
+    my $x;
+
+    $x = $conn->getline() // next;
+    $x =~ /\A(\S+) ENABLE QRESYNC\r\n/ or die;
+    $conn->printflush("* ENABLED QRESYNC\r\n$1 OK ENABLE completed\r\n");
+
+    $x = $conn->getline() // next;
+    $x =~ /\A(\S+) LIST .*\r\n/ or die;
+    $conn->print("* LIST (\\Noselect) \"~\" \"\"\r\n");
+    $conn->print("* LIST () \"~\" INBOX\r\n");
+    $conn->print("* STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1 HIGHESTMODSEQ 1)\r\n");
+    $conn->printflush("$1 OK LIST completed\r\n");
+
+    close($conn);
+}
+
+END {
+    if (defined $S) {
+        shutdown($S, SHUT_RDWR) or warn "shutdown: $!";
+        close($S) or print STDERR "Can't close: $!\n";
+    }
+}
diff --git a/tests/preauth-plaintext/interimap.remote b/tests/preauth-plaintext/interimap.remote
new file mode 120000
index 0000000..ad49677
--- /dev/null
+++ b/tests/preauth-plaintext/interimap.remote
@@ -0,0 +1 @@
+../starttls/interimap.remote
\ No newline at end of file
diff --git a/tests/preauth-plaintext/t b/tests/preauth-plaintext/t
new file mode 100644
index 0000000..427d57b
--- /dev/null
+++ b/tests/preauth-plaintext/t
@@ -0,0 +1,19 @@
+# Test IMAP MiTM via PREAUTH greeting
+# For background see CVE-2020-12398, CVE-2020-14093 and
+# https://gitlab.com/muttmua/mutt/commit/3e88866dc60b5fa6aaba6fd7c1710c12c1c3cd01
+
+env -i USER="remote" HOME="$HOME_remote" "$TESTDIR/imapd" & PID=$!
+trap "ptree_abort $PID" EXIT INT TERM
+
+! interimap --debug || error
+grep -Fx 'remote: ERROR: PREAUTH greeting on plaintext connection? MiTM in action? Aborting, set "STARTTLS = NO" to ignore.' <"$STDERR" || error
+! grep '^remote: C: ' <"$STDERR" || error "wrote command in MiTM'ed PREAUTH connection!"
+
+
+# Ignore the warning when STARTTLS is explicitely disabled
+echo "STARTTLS = NO" >>"$XDG_CONFIG_HOME/interimap/config"
+interimap --debug || true
+
+grep -Fx "remote: S: * STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1 HIGHESTMODSEQ 1)" <"$STDERR" || error
+
+# vim: set filetype=sh :
-- 
cgit v1.2.3