]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Mitigate DoS attacks that use client-initiated SSL/TLS renegotiation.
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 27 Jan 2017 16:14:19 +0000 (05:14 +1300)
committerAmos Jeffries <squid3@treenet.co.nz>
Fri, 27 Jan 2017 16:14:19 +0000 (05:14 +1300)
There is a well-known DoS attack using client-initiated SSL/TLS
renegotiation. The severety or uniqueness of this attack method
is disputed, but many believe it is serious/real.
There is even a (disputed) CVE 2011-1473:
    https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-1473

The old Squid code tried to disable client-initiated renegotiation, but
it did not work reliably (or at all), depending on Squid version, due
to OpenSSL API changes and conflicting SslBump callbacks. That
code is now removed and client-initiated renegotiations are allowed.

With this change, Squid aborts the TLS connection, with a level-1 ERROR
message if the rate of client-initiated renegotiate requests exceeds
5 requests in 10 seconds (approximately). This protection and the rate
limit are currently hard-coded but the rate is not expected to be
exceeded under normal circumstances.

This is a Measurement Factory project.

src/Makefile.am
src/ssl/bio.cc
src/ssl/bio.h
src/ssl/support.cc

index f38f690ee9255829bab18fc2e9be8d081511de15..f427d1c51daff352ed20107dad912daf9fe4d34f 100644 (file)
@@ -1673,6 +1673,7 @@ tests_testDiskIO_SOURCES = \
        tests/stub_ETag.cc \
        EventLoop.cc \
        event.cc \
+       FadingCounter.cc \
        fatal.h \
        tests/stub_fatal.cc \
        fd.h \
@@ -3177,6 +3178,7 @@ tests_testUfs_SOURCES = \
        store_rebuild.h \
        tests/stub_store_rebuild.cc \
        tests/stub_store_stats.cc \
+       FadingCounter.cc \
        fatal.h \
        tests/stub_fatal.cc \
        fd.h \
@@ -3359,6 +3361,7 @@ tests_testRock_SOURCES = \
        ETag.cc \
        EventLoop.cc \
        event.cc \
+       FadingCounter.cc \
        fatal.h \
        fatal.cc \
        fd.h \
index af6f1e7c0c29c5faf080b3a80ab274ad0cddad90..6347a6756face4b5fc73cd8f4f4df26f9f1d5b9e 100644 (file)
@@ -175,6 +175,16 @@ Ssl::Bio::prepReadBuf()
         rbuf.init(4096, 65536);
 }
 
+Ssl::ClientBio::ClientBio(const int anFd):
+    Bio(anFd),
+    holdRead_(false),
+    holdWrite_(false),
+    helloState(atHelloNone),
+    abortReason(nullptr)
+{
+    renegotiations.configure(10*1000);
+}
+
 bool
 Ssl::ClientBio::isClientHello(int state)
 {
@@ -194,11 +204,32 @@ void
 Ssl::ClientBio::stateChanged(const SSL *ssl, int where, int ret)
 {
     Ssl::Bio::stateChanged(ssl, where, ret);
+    // detect client-initiated renegotiations DoS (CVE-2011-1473)
+    if (where & SSL_CB_HANDSHAKE_START) {
+        const int reneg = renegotiations.count(1);
+
+        if (abortReason)
+            return; // already decided and informed the admin
+
+        if (reneg > RenegotiationsLimit) {
+            abortReason = "renegotiate requests flood";
+            debugs(83, DBG_IMPORTANT, "Terminating TLS connection [from " << fd_table[fd_].ipaddr << "] due to " << abortReason << ". This connection received " <<
+                   reneg << " renegotiate requests in the last " <<
+                   RenegotiationsWindow << " seconds (and " <<
+                   renegotiations.remembered() << " requests total).");
+        }
+    }
 }
 
 int
 Ssl::ClientBio::write(const char *buf, int size, BIO *table)
 {
+    if (abortReason) {
+        debugs(83, 3, "BIO on FD " << fd_ << " is aborted");
+        BIO_clear_retry_flags(table);
+        return -1;
+    }
+
     if (holdWrite_) {
         BIO_set_retry_write(table);
         return 0;
@@ -222,6 +253,12 @@ const char *objToString(unsigned char const *bytes, int len)
 int
 Ssl::ClientBio::read(char *buf, int size, BIO *table)
 {
+    if (abortReason) {
+        debugs(83, 3, "BIO on FD " << fd_ << " is aborted");
+        BIO_clear_retry_flags(table);
+        return -1;
+    }
+
     if (helloState < atHelloReceived) {
         int bytes = readAndBuffer(buf, size, table, "TLS client Hello");
         if (bytes <= 0)
index 4d1ea8968a1b067afd9cf84bf5ab95f97b0753fa..7ed7c11e38427fa4ea4c1062f7b7651760c5e09c 100644 (file)
@@ -9,6 +9,7 @@
 #ifndef SQUID_SSL_BIO_H
 #define SQUID_SSL_BIO_H
 
+#include "FadingCounter.h"
 #include "fd.h"
 #include "SBuf.h"
 
@@ -134,7 +135,7 @@ class ClientBio: public Bio
 public:
     /// The ssl hello message read states
     typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived} HelloReadState;
-    explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloState(atHelloNone) {}
+    explicit ClientBio(const int anFd);
 
     /// The ClientBio version of the Ssl::Bio::stateChanged method
     /// When the client hello message retrieved, fill the
@@ -156,11 +157,22 @@ public:
 private:
     /// True if the SSL state corresponds to a hello message
     bool isClientHello(int state);
+
+    /// approximate size of a time window for computing client-initiated renegotiation rate (in seconds)
+    static const time_t RenegotiationsWindow = 10;
+
+    /// the maximum tolerated number of client-initiated renegotiations in RenegotiationsWindow
+    static const int RenegotiationsLimit = 5;
+
     /// The futures retrieved from client SSL hello message
     Bio::sslFeatures features;
     bool holdRead_; ///< The read hold state of the bio.
     bool holdWrite_;  ///< The write hold state of the bio.
     HelloReadState helloState; ///< The SSL hello read state
+    FadingCounter renegotiations; ///< client requested renegotiations limit control
+
+    /// why we should terminate the connection during next TLS operation (or nil)
+    const char *abortReason;
 };
 
 /// BIO node to handle socket IO for squid server side
index 7451299720ae3af3913f28b54a2d965706339051..1e672b338ebe77d3bb9707ae6bab048f6aa5d4a9 100644 (file)
@@ -848,18 +848,6 @@ Ssl::readDHParams(const char *dhfile)
     return dh;
 }
 
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
-static void
-ssl_info_cb(const SSL *ssl, int where, int ret)
-{
-    (void)ret;
-    if ((where & SSL_CB_HANDSHAKE_DONE) != 0) {
-        // disable renegotiation (CVE-2009-3555)
-        ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
-    }
-}
-#endif
-
 static bool
 configureSslEECDH(SSL_CTX *sslContext, const char *curve)
 {
@@ -889,10 +877,6 @@ configureSslContext(SSL_CTX *sslContext, AnyP::PortCfg &port)
     int ssl_error;
     SSL_CTX_set_options(sslContext, port.sslOptions);
 
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
-    SSL_CTX_set_info_callback(sslContext, ssl_info_cb);
-#endif
-
     if (port.sslContextSessionId)
         SSL_CTX_set_session_id_context(sslContext, (const unsigned char *)port.sslContextSessionId, strlen(port.sslContextSessionId));
 
@@ -1261,10 +1245,6 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c
 
     SSL_CTX_set_options(sslContext, Ssl::parse_options(options));
 
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
-    SSL_CTX_set_info_callback(sslContext, ssl_info_cb);
-#endif
-
     if (cipher) {
         debugs(83, 5, "Using chiper suite " << cipher << ".");