CVE-2021-42073 (published 2021-11-08T04:15Z)

An issue was discovered in Barrier before 2.4.0. An attacker can enter an active session state with the barriers component (aka the server-side implementation of Barrier) simply by supplying a client label that identifies a valid client configuration. This label is "Unnamed" by default but could instead be guessed from hostnames or other publicly available information. In the active session state, an attacker can capture input device events from the server, and also modify the clipboard content on the server.

Severity: MEDIUM
CVSS v2 base score: 5.8 (AV:N/AC:M/Au:N/C:P/I:P/A:N)
CVSS v3 base score: 8.2, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N)

Weakness information:
cwe_id: CWE-384
cwe_name: Session Fixation
description: Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.
extended_description: 
            Such a scenario is commonly observed when:
            
               A web application authenticates a user without first invalidating the existing session, thereby continuing to use the session already associated with the user.
               An attacker is able to force a known session identifier on a user so that, once the user authenticates, the attacker has access to the authenticated session.
               The application or container uses predictable session identifiers.
	    
	    In the generic exploit of session fixation vulnerabilities, an attacker creates a new session on a web application and records the associated session identifier. The attacker then causes the victim to associate, and possibly authenticate, against the server using that session identifier, giving the attacker access to the user's account through the active session.
         
         

Fix information:
repo_url: https://github.com/debauchee/barrier
commit_details: repo_url: https://github.com/debauchee/barrier
author: Povilas Kanapickas
author_date: 2021-11-01 05:15:48+02:00
msg: Merge pull request #1346 from p12tic/client-identity-verification

Implement client identity verification [SECURITY VULNERABILITIES CVE-2021-42072, CVE-2021-42073]
repository: repo_url: https://github.com/debauchee/barrier
repo_name: debauchee/barrier
description: Open-source KVM software
repo_language: C
owner: debauchee
repo_url: https://github.com/debauchee/barrier
commit_details: repo_url: https://github.com/debauchee/barrier
author: Povilas Kanapickas
author_date: 2021-11-01 04:50:15+02:00
msg: Implement client identity verification

This commit fixes two security vulnerabilities: CVE-2021-42072 and
CVE-2021-42073.

The issues have been reported by Matthias Gerstner <mgerstner@suse.de>.
repository: repo_url: https://github.com/debauchee/barrier
repo_name: debauchee/barrier
description: Open-source KVM software
repo_language: C
owner: debauchee
file_changes: filename: client-certificate-checking.bugfix
change_type: ModificationType.ADD
diff: @@ -0,0 +1,7 @@
+SECURITY ISSUE
+
+Barrier now supports client identity verification (fixes CVE-2021-42072, CVE-2021-42073).
+
+To support seamless upgrades from older versions of Barrier this is currently disabled by default.
+The feature can be enabled in the settings dialog. If enabled, older clients of Barrier will be
+rejected.

programming_language: Markdown
filename: MainWindow.cpp
change_type: ModificationType.MODIFY
diff: @@ -427,7 +427,7 @@ void MainWindow::checkConnected(const QString& line)
 
 void MainWindow::checkFingerprint(const QString& line)
 {
-    QRegExp fingerprintRegex(".*server fingerprint \\(SHA1\\): ([A-F0-9:]+) \\(SHA256\\): ([A-F0-9:]+)");
+    QRegExp fingerprintRegex(".*peer fingerprint \\(SHA1\\): ([A-F0-9:]+) \\(SHA256\\): ([A-F0-9:]+)");
     if (!fingerprintRegex.exactMatch(line)) {
         return;
     }
@@ -442,7 +442,11 @@ void MainWindow::checkFingerprint(const QString& line)
         barrier::string::from_hex(fingerprintRegex.cap(2).toStdString())
     };
 
-    auto db_path = barrier::DataDirectories::trusted_servers_ssl_fingerprints_path();
+    bool is_client = barrierType() == barrierClient;
+
+    auto db_path = is_client
+            ? barrier::DataDirectories::trusted_servers_ssl_fingerprints_path()
+            : barrier::DataDirectories::trusted_clients_ssl_fingerprints_path();
 
     auto db_dir = db_path.parent_path();
     if (!barrier::fs::exists(db_dir)) {
@@ -461,17 +465,17 @@ void MainWindow::checkFingerprint(const QString& line)
     static bool messageBoxAlreadyShown = false;
 
     if (!messageBoxAlreadyShown) {
-        stopBarrier();
+        if (is_client) {
+            stopBarrier();
+        }
 
-        messageBoxAlreadyShown = true;
-        QMessageBox::StandardButton fingerprintReply =
-            QMessageBox::information(
-            this, tr("Security question"),
-            tr("Do you trust this fingerprint?\n\n"
+        QString message;
+        if (is_client) {
+            message = tr("Do you trust this fingerprint?\n\n"
                "SHA256:\n"
                "%1\n"
                "%2\n\n"
-               "SHA1 (obsolete, when using old Barrier server):\n"
+               "SHA1 (obsolete, when using old Barrier client):\n"
                "%3\n\n"
                "This is a server fingerprint. You should compare this "
                "fingerprint to the one on your server's screen. If the "
@@ -483,14 +487,38 @@ void MainWindow::checkFingerprint(const QString& line)
             .arg(QString::fromStdString(barrier::format_ssl_fingerprint(fingerprint_sha256.data)))
             .arg(QString::fromStdString(
                      barrier::create_fingerprint_randomart(fingerprint_sha256.data)))
-            .arg(QString::fromStdString(barrier::format_ssl_fingerprint(fingerprint_sha1.data))),
+            .arg(QString::fromStdString(barrier::format_ssl_fingerprint(fingerprint_sha1.data)));
+        } else {
+            message = tr("Do you trust this fingerprint?\n\n"
+               "SHA256:\n"
+               "%1\n"
+               "%2\n\n"
+               "This is a client fingerprint. You should compare this "
+               "fingerprint to the one on your client's screen. If the "
+               "two don't match exactly, then it's probably not the client "
+               "you're expecting (it could be a malicious user).\n\n"
+               "To automatically trust this fingerprint for future "
+               "connections, click Yes. To reject this fingerprint and "
+               "disconnect the client, click No.")
+            .arg(QString::fromStdString(barrier::format_ssl_fingerprint(fingerprint_sha256.data)))
+            .arg(QString::fromStdString(
+                     barrier::create_fingerprint_randomart(fingerprint_sha256.data)));
+        }
+
+        messageBoxAlreadyShown = true;
+        QMessageBox::StandardButton fingerprintReply =
+            QMessageBox::information(
+            this, tr("Security question"),
+            message,
             QMessageBox::Yes | QMessageBox::No);
 
         if (fingerprintReply == QMessageBox::Yes) {
             // restart core process after trusting fingerprint.
             db.add_trusted(fingerprint_sha256);
             db.write(db_path);
-            startBarrier();
+            if (is_client) {
+                startBarrier();
+            }
         }
 
         messageBoxAlreadyShown = false;
@@ -734,6 +762,10 @@ bool MainWindow::serverArgs(QStringList& args, QString& app)
         args << "--log" << appConfig().logFilenameCmd();
     }
 
+    if (!appConfig().getRequireClientCertificate()) {
+        args << "--disable-client-cert-checking";
+    }
+
     QString configFilename = this->configFilename();
 #if defined(Q_OS_WIN)
     // wrap in quotes in case username contains spaces.

programming_language: C++
filename: ArgParser.cpp
change_type: ModificationType.MODIFY
diff: @@ -65,7 +65,9 @@ ArgParser::parseServerArgs(ServerArgs& args, int argc, const char* const* argv)
             // save screen change script path
             args.m_screenChangeScript = argv[++i];
         }
-        else {
+        else if (isArg(i, argc, argv, nullptr, "--disable-client-cert-checking")) {
+            args.check_client_certificates = false;
+        } else {
             LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_exename.c_str(), argv[i], args.m_exename.c_str()));
             return false;
         }

programming_language: Objective-C
filename: ServerApp.cpp
change_type: ModificationType.MODIFY
diff: @@ -148,7 +148,10 @@ ServerApp::help()
            << "Options:\n"
            << "  -a, --address <address>  listen for clients on the given address.\n"
            << "  -c, --config <pathname>  use the named configuration file instead.\n"
-           << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO << HELP_COMMON_INFO_2 << "\n"
+           << HELP_COMMON_INFO_1
+           << "      --disable-client-cert-checking disable client SSL certificate \n"
+              "                                     checking (deprecated)\n"
+           << WINAPI_INFO << HELP_SYS_INFO << HELP_COMMON_INFO_2 << "\n"
            << "Default options are marked with a *\n"
            << "\n"
            << "The argument for --address is of the form: [<hostname>][:<port>].  The\n"
@@ -658,6 +661,9 @@ ServerApp::openClientListener(const NetworkAddress& address)
     auto security_level = ConnectionSecurityLevel::PLAINTEXT;
     if (args().m_enableCrypto) {
         security_level = ConnectionSecurityLevel::ENCRYPTED;
+        if (args().check_client_certificates) {
+            security_level = ConnectionSecurityLevel::ENCRYPTED_AUTHENTICATED;
+        }
     }
 
     ClientListener* listen = new ClientListener(

programming_language: C++
filename: ServerArgs.h
change_type: ModificationType.MODIFY
diff: @@ -30,4 +30,5 @@ public:
     String                m_configFile;
     Config*                m_config;
     String                m_screenChangeScript;
+    bool check_client_certificates = true;
 };

programming_language: Java
filename: Client.cpp
change_type: ModificationType.MODIFY
diff: @@ -129,7 +129,8 @@ Client::connect()
 
     auto security_level = ConnectionSecurityLevel::PLAINTEXT;
     if (m_useSecureNetwork) {
-        security_level = ConnectionSecurityLevel::ENCRYPTED;
+        // client always authenticates server
+        security_level = ConnectionSecurityLevel::ENCRYPTED_AUTHENTICATED;
     }
 
     try {

programming_language: C++
filename: ConnectionSecurityLevel.h
change_type: ModificationType.MODIFY
diff: @@ -21,6 +21,7 @@
 enum class ConnectionSecurityLevel {
     PLAINTEXT,
     ENCRYPTED,
+    ENCRYPTED_AUTHENTICATED
 };
 
 #endif // BARRIER_LIB_NET_CONNECTION_SECURITY_LEVEL_H

programming_language: C
filename: SecureSocket.cpp
change_type: ModificationType.MODIFY
diff: @@ -361,6 +361,11 @@ bool SecureSocket::load_certificates(const barrier::fs::path& path)
     return true;
 }
 
+static int cert_verify_ignore_callback(X509_STORE_CTX*, void*)
+{
+    return 1;
+}
+
 void
 SecureSocket::initContext(bool server)
 {
@@ -396,6 +401,14 @@ SecureSocket::initContext(bool server)
     if (m_ssl->m_context == NULL) {
         showError("");
     }
+
+    if (security_level_ == ConnectionSecurityLevel::ENCRYPTED_AUTHENTICATED) {
+        // We want to ask for peer certificate, but not verify it. If we don't ask for peer
+        // certificate, e.g. client won't send it.
+        SSL_CTX_set_verify(m_ssl->m_context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                           nullptr);
+        SSL_CTX_set_cert_verify_callback(m_ssl->m_context, cert_verify_ignore_callback, nullptr);
+    }
 }
 
 void
@@ -436,6 +449,24 @@ SecureSocket::secureAccept(int socket)
 
     // If not fatal and no retry, state is good
     if (retry == 0) {
+        if (security_level_ == ConnectionSecurityLevel::ENCRYPTED_AUTHENTICATED) {
+            if (verify_cert_fingerprint(
+                        barrier::DataDirectories::trusted_clients_ssl_fingerprints_path())) {
+                LOG((CLOG_INFO "accepted secure socket"));
+                if (!ensure_peer_certificate()) {
+                    retry = 0;
+                    disconnect();
+                    return -1;// Cert fail, error
+                }
+            }
+            else {
+                LOG((CLOG_ERR "failed to verify server certificate fingerprint"));
+                retry = 0;
+                disconnect();
+                return -1; // Fingerprint failed, error
+            }
+        }
+
         m_secureReady = true;
         LOG((CLOG_INFO "accepted secure socket"));
         if (CLOG->getFilter() >= kDEBUG1) {
@@ -670,7 +701,7 @@ bool SecureSocket::verify_cert_fingerprint(const barrier::fs::path& fingerprint_
     }
 
     // note: the GUI parses the following two lines of logs, don't change unnecessarily
-    LOG((CLOG_NOTE "server fingerprint (SHA1): %s (SHA256): %s",
+    LOG((CLOG_NOTE "peer fingerprint (SHA1): %s (SHA256): %s",
          barrier::format_ssl_fingerprint(fingerprint_sha1.data).c_str(),
          barrier::format_ssl_fingerprint(fingerprint_sha256.data).c_str()));
 

programming_language: C++