]> source.dussan.org Git - tigervnc.git/commitdiff
Add delay on authentication failures
authorPierre Ossman <ossman@cendio.se>
Mon, 1 Apr 2019 12:22:01 +0000 (14:22 +0200)
committerPierre Ossman <ossman@cendio.se>
Mon, 1 Apr 2019 12:22:01 +0000 (14:22 +0200)
This provides some basic rate limiting that will make it difficult
for an attacker to brute force passwords. Only relevant when the
blacklist is disabled as otherwise the attacker only gets a very
limited number of attempts.

common/rfb/SConnection.cxx
common/rfb/SConnection.h
common/rfb/VNCSConnectionST.cxx
common/rfb/VNCSConnectionST.h

index 690653a24fdda5da50fdd90286247c3c9b4a585d..4e224aa1ae60871a58970080e8d86d8085626703 100644 (file)
@@ -236,11 +236,8 @@ void SConnection::processSecurityMsg()
     }
   } catch (AuthFailureException& e) {
     vlog.error("AuthFailureException: %s", e.str());
-    os->writeU32(secResultFailed);
-    if (!client.beforeVersion(3,8)) // 3.8 onwards have failure message
-      os->writeString(e.str());
-    os->flush();
-    throw;
+    state_ = RFBSTATE_SECURITY_FAILURE;
+    authFailure(e.str());
   }
 }
 
@@ -315,6 +312,19 @@ void SConnection::authSuccess()
 {
 }
 
+void SConnection::authFailure(const char* reason)
+{
+  if (state_ != RFBSTATE_SECURITY_FAILURE)
+    throw Exception("SConnection::authFailure: invalid state");
+
+  os->writeU32(secResultFailed);
+  if (!client.beforeVersion(3,8)) // 3.8 onwards have failure message
+    os->writeString(reason);
+  os->flush();
+
+  throw AuthFailureException(reason);
+}
+
 void SConnection::queryConnection(const char* userName)
 {
   approveConnection(true);
index 26302403fcb0e6d951e2023a34fa4b28452c3809..31d1cb2e1b66dfe4f696e339060f9f4e9243f835 100644 (file)
@@ -92,6 +92,10 @@ namespace rfb {
     // authSuccess() is called when authentication has succeeded.
     virtual void authSuccess();
 
+    // authFailure() is called when authentication has failed. The default
+    // implementation will inform the client and throw a AuthFailureException.
+    virtual void authFailure(const char* reason);
+
     // queryConnection() is called when authentication has succeeded, but
     // before informing the client.  It can be overridden to query a local user
     // to accept the incoming connection, for example.  The userName argument
@@ -160,6 +164,7 @@ namespace rfb {
       RFBSTATE_PROTOCOL_VERSION,
       RFBSTATE_SECURITY_TYPE,
       RFBSTATE_SECURITY,
+      RFBSTATE_SECURITY_FAILURE,
       RFBSTATE_QUERYING,
       RFBSTATE_INITIALISATION,
       RFBSTATE_NORMAL,
index ea5c52aacaac503de41f1b5577bc6ddc41197173..fe00dab6011088bebbdd3102d9b67f23a6c21d3c 100644 (file)
@@ -52,7 +52,8 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
     losslessTimer(this), server(server_),
     updateRenderedCursor(false), removeRenderedCursor(false),
     continuousUpdates(false), encodeManager(this), idleTimer(this),
-    pointerEventTime(0), clientHasCursor(false)
+    pointerEventTime(0), clientHasCursor(false),
+    authFailureTimer(this)
 {
   setStreams(&sock->inStream(), &sock->outStream());
   peerEndpoint.buf = sock->getPeerEndpoint();
@@ -151,6 +152,15 @@ void VNCSConnectionST::processMessages()
     sock->cork(true);
 
     while (getInStream()->checkNoWait(1)) {
+      // Silently drop any data if we are currently delaying an
+      // authentication failure response as otherwise we would close
+      // the connection on unexpected data, and an attacker could use
+      // that to detect our delayed state.
+      if (state() == RFBSTATE_SECURITY_FAILURE) {
+        getInStream()->skip(1);
+        continue;
+      }
+
       if (pendingSyncFence) {
         syncFence = true;
         pendingSyncFence = false;
@@ -407,6 +417,14 @@ void VNCSConnectionST::authSuccess()
   updates.add_changed(server->getPixelBuffer()->getRect());
 }
 
+void VNCSConnectionST::authFailure(const char* reason)
+{
+  // Introduce a slight delay of the authentication failure response
+  // to make it difficult to brute force a password
+  authFailureMsg.replaceBuf(strDup(reason));
+  authFailureTimer.start(100);
+}
+
 void VNCSConnectionST::queryConnection(const char* userName)
 {
   server->queryConnection(this, userName);
@@ -745,6 +763,8 @@ bool VNCSConnectionST::handleTimeout(Timer* t)
     if ((t == &congestionTimer) ||
         (t == &losslessTimer))
       writeFramebufferUpdate();
+    else if (t == &authFailureTimer)
+      SConnection::authFailure(authFailureMsg.buf);
   } catch (rdr::Exception& e) {
     close(e.str());
   }
index 662d9f34d0c357ace4655da927974d09a26ce561..a9a8d3a42039b0f29f892ba5de05a22059c5fd0e 100644 (file)
@@ -109,6 +109,7 @@ namespace rfb {
     // These methods are invoked as callbacks from processMsg()
 
     virtual void authSuccess();
+    virtual void authFailure(const char* reason);
     virtual void queryConnection(const char* userName);
     virtual void clientInit(bool shared);
     virtual void setPixelFormat(const PixelFormat& pf);
@@ -184,6 +185,9 @@ namespace rfb {
     Point pointerEventPos;
     bool clientHasCursor;
 
+    Timer authFailureTimer;
+    CharArray authFailureMsg;
+
     CharArray closeReason;
   };
 }