CVE-2022-36436: Improper Authentication (CWE-287)
value: OSU Open Source Lab VNCAuthProxy through 1.1.1 is affected by an vncap/vnc/protocol.py VNCServerAuthenticator authentication-bypass vulnerability that could allow a malicious actor to gain unauthorized access to a VNC session or to disconnect a legitimate user from a VNC session. A remote attacker with network access to the proxy server could leverage this vulnerability to connect to VNC servers protected by the proxy server without providing any authentication credentials. Exploitation of this issue requires that the proxy server is currently accepting connections for the target VNC server.
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 9.8
Published: 2022-09-14T11:15Z
Repository: https://github.com/osuosl/twisted_vncauthproxy
Language: Python
Files changed: vncap/tests/test_protocol.py, vncap/vnc/protocol.py
Commit date: 2022-07-29 08:40:09 -0700
Commit message: Fix CVE-2022-36436 - Authentication bypass in RFB security handshake (#1)


Diff: diff --git a/vncap/tests/test_protocol.py b/vncap/tests/test_protocol.py
index 151d059..81bef84 100644
--- a/vncap/tests/test_protocol.py
+++ b/vncap/tests/test_protocol.py
@@ -11,10 +11,13 @@ class DummyTransport(object):
         self.buf += data
 
     def loseConnection(self):
         self.lost = True
 
+    def pauseProducing(self):
+        pass
+
 class TestVNCServerAuthenticator(unittest.TestCase):
 
     def setUp(self):
         self.p = VNCServerAuthenticator("password", {})
         self.t = DummyTransport()
@@ -27,11 +30,22 @@ class TestVNCServerAuthenticator(unittest.TestCase):
         self.assertEqual(self.t.buf, "RFB 003.008\n")
 
     def test_check_version(self):
         self.t.buf = ""
         self.p.check_version("RFB 003.008\n")
-        self.assertEqual(self.t.buf, "\x02\x01\x02")
+        self.assertEqual(self.t.buf, "\x01\x02")
 
     def test_check_invalid_version(self):
         self.t.buf = ""
         self.p.check_version("RFB 002.000\n")
         self.assertTrue(self.t.lost)
+
+    def test_select_security_type_none(self):
+        self.t.buf = ""
+        self.p.select_security_type("\x01")
+        self.assertTrue(self.t.lost)
+
+    def test_select_security_type_vnc_auth(self):
+        self.t.buf = ""
+        self.p.select_security_type("\x02")
+        self.assertFalse(self.t.lost)
+        self.assertEqual(len(self.t.buf), 16)
diff --git a/vncap/vnc/protocol.py b/vncap/vnc/protocol.py
index aee50d3..d1351bd 100644
--- a/vncap/vnc/protocol.py
+++ b/vncap/vnc/protocol.py
@@ -71,12 +71,12 @@ class VNCServerAuthenticator(VNCAuthenticator):
         handshake.
         """
 
         if version == self.VERSION:
             log.msg("Client version %s is valid" % version.strip())
-            # Hardcoded: 2 security types: None and VNC Auth.
-            self.transport.write("\x02\x01\x02")
+            # Hardcoded: 1 security type: VNC Auth.
+            self.transport.write("\x01\x02")
             return self.select_security_type, 1
         else:
             log.err("Can't handle VNC version %r" % version)
             self.transport.loseConnection()
 
@@ -91,13 +91,10 @@ class VNCServerAuthenticator(VNCAuthenticator):
             # VNC authentication. Issue our challenge.
             self.challenge = urandom(16)
             self.transport.write(self.challenge)
 
             return self.vnc_authentication_result, 16
-        elif security_type == 1:
-            # No authentication. Just move to the SecurityResult.
-            self.authenticated()
         else:
             log.err("Couldn't agree on an authentication scheme!")
             self.transport.loseConnection()
 
     def vnc_authentication_result(self, response):

Vulnerable code:         self.assertEqual(self.t.buf, "\x02\x01\x02")
            # Hardcoded: 2 security types: None and VNC Auth.
            self.transport.write("\x02\x01\x02")
        elif security_type == 1:
            # No authentication. Just move to the SecurityResult.
            self.authenticated()
Fixed code:     def pauseProducing(self):
        pass

        self.assertEqual(self.t.buf, "\x01\x02")

    def test_select_security_type_none(self):
        self.t.buf = ""
        self.p.select_security_type("\x01")
        self.assertTrue(self.t.lost)

    def test_select_security_type_vnc_auth(self):
        self.t.buf = ""
        self.p.select_security_type("\x02")
        self.assertFalse(self.t.lost)
        self.assertEqual(len(self.t.buf), 16)
            # Hardcoded: 1 security type: VNC Auth.
            self.transport.write("\x01\x02")