CVE-2022-3959: Use of Insufficiently Random Values (CWE-330)
value: A vulnerability, which was classified as problematic, has been found in drogon up to 1.8.1. Affected by this issue is some unknown functionality of the component Session Hash Handler. The manipulation leads to small space of random values. The attack may be launched remotely. Upgrading to version 1.8.2 is able to address this issue. The name of the patch is c0d48da99f66aaada17bcd28b07741cac8697647. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-213464.
CWE description: The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
CVSS v3 base score: 5.3
Published: 2022-11-11T16:15Z
Repository: https://github.com/drogonframework/drogon
Language: C
Files changed: lib/inc/drogon/HttpRequest.h, lib/inc/drogon/HttpResponse.h, lib/inc/drogon/utils/Utilities.h, lib/src/HttpRequestImpl.h, lib/src/HttpResponseImpl.h, lib/src/Utilities.cc
Commit date: 2022-11-10 13:41:21 +0800
Commit message: Avoid HashDoS attacks via random per-session hash initial state (#1433)


Diff: diff --git a/lib/inc/drogon/HttpRequest.h b/lib/inc/drogon/HttpRequest.h
index 3ad1a163..8a85e5e5 100644
--- a/lib/inc/drogon/HttpRequest.h
+++ b/lib/inc/drogon/HttpRequest.h
@@ -146,25 +146,31 @@ class DROGON_EXPORT HttpRequest
 
     /// Get the cookie string identified by the field parameter
     virtual const std::string &getCookie(const std::string &field) const = 0;
 
     /// Get all headers of the request
-    virtual const std::unordered_map<std::string, std::string> &headers()
-        const = 0;
+    virtual const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &headers() const = 0;
 
     /// Get all headers of the request
-    const std::unordered_map<std::string, std::string> &getHeaders() const
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &getHeaders() const
     {
         return headers();
     }
 
     /// Get all cookies of the request
-    virtual const std::unordered_map<std::string, std::string> &cookies()
-        const = 0;
+    virtual const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &cookies() const = 0;
 
     /// Get all cookies of the request
-    const std::unordered_map<std::string, std::string> &getCookies() const
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &getCookies() const
     {
         return cookies();
     }
 
     /// Get the query string of the request.
@@ -265,15 +271,18 @@ class DROGON_EXPORT HttpRequest
     {
         return attributes();
     }
 
     /// Get parameters of the request.
-    virtual const std::unordered_map<std::string, std::string> &parameters()
-        const = 0;
+    virtual const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &parameters() const = 0;
 
     /// Get parameters of the request.
-    const std::unordered_map<std::string, std::string> &getParameters() const
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &getParameters() const
     {
         return parameters();
     }
 
     /// Get a parameter identified by the @param key
diff --git a/lib/inc/drogon/HttpResponse.h b/lib/inc/drogon/HttpResponse.h
index 38ea25ef..6d4ecccb 100644
--- a/lib/inc/drogon/HttpResponse.h
+++ b/lib/inc/drogon/HttpResponse.h
@@ -17,10 +17,11 @@
 #include <drogon/utils/string_view.h>
 #include <drogon/DrClassMap.h>
 #include <drogon/Cookie.h>
 #include <drogon/HttpTypes.h>
 #include <drogon/HttpViewData.h>
+#include <drogon/utils/Utilities.h>
 #include <json/json.h>
 #include <memory>
 #include <string>
 
 namespace drogon
@@ -188,15 +189,18 @@ class DROGON_EXPORT HttpResponse
      * @param key The key is case insensitive
      */
     virtual void removeHeader(std::string key) = 0;
 
     /// Get all headers of the response
-    virtual const std::unordered_map<std::string, std::string> &headers()
-        const = 0;
+    virtual const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &headers() const = 0;
 
     /// Get all headers of the response
-    const std::unordered_map<std::string, std::string> &getHeaders() const
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &getHeaders() const
     {
         return headers();
     }
 
     /**
@@ -220,14 +224,18 @@ class DROGON_EXPORT HttpResponse
     /// Get the cookie identified by the key parameter.
     /// If there is no the cookie, the empty cookie is retured.
     virtual const Cookie &getCookie(const std::string &key) const = 0;
 
     /// Get all cookies.
-    virtual const std::unordered_map<std::string, Cookie> &cookies() const = 0;
+    virtual const std::
+        unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
+            &cookies() const = 0;
 
     /// Get all cookies.
-    const std::unordered_map<std::string, Cookie> &getCookies() const
+    const std::
+        unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
+            &getCookies() const
     {
         return cookies();
     }
 
     /// Remove the cookie identified by the key parameter.
diff --git a/lib/inc/drogon/utils/Utilities.h b/lib/inc/drogon/utils/Utilities.h
index 9bbed1a6..6a31feda 100644
--- a/lib/inc/drogon/utils/Utilities.h
+++ b/lib/inc/drogon/utils/Utilities.h
@@ -406,7 +406,24 @@ inline bool fromString<bool>(const std::string &p) noexcept(false)
     {
         return false;
     }
     throw std::runtime_error("Can't convert from string '" + p + "' to bool");
 }
+
+namespace internal
+{
+DROGON_EXPORT extern const size_t fixedRandomNumber;
+struct SafeStringHash
+{
+    size_t operator()(const std::string &str) const
+    {
+        const size_t A = 6665339;
+        const size_t B = 2534641;
+        size_t h = fixedRandomNumber;
+        for (char ch : str)
+            h = (h * A) ^ (ch * B);
+        return h;
+    }
+};
+}  // namespace internal
 }  // namespace utils
 }  // namespace drogon
diff --git a/lib/src/HttpRequestImpl.h b/lib/src/HttpRequestImpl.h
index a29f6540..e054c28b 100644
--- a/lib/src/HttpRequestImpl.h
+++ b/lib/src/HttpRequestImpl.h
@@ -135,12 +135,13 @@ class HttpRequestImpl : public HttpRequest
     void setPathEncode(bool pathEncode) override
     {
         pathEncode_ = pathEncode;
     }
 
-    const std::unordered_map<std::string, std::string> &parameters()
-        const override
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &parameters() const override
     {
         parseParametersOnce();
         return parameters_;
     }
 
@@ -289,16 +290,20 @@ class HttpRequestImpl : public HttpRequest
             return it->second;
         }
         return defaultVal;
     }
 
-    const std::unordered_map<std::string, std::string> &headers() const override
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &headers() const override
     {
         return headers_;
     }
 
-    const std::unordered_map<std::string, std::string> &cookies() const override
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &cookies() const override
     {
         return cookies_;
     }
 
     void setParameter(const std::string &key, const std::string &value) override
@@ -539,13 +544,19 @@ class HttpRequestImpl : public HttpRequest
     Version version_{Version::kUnknown};
     std::string path_;
     bool pathEncode_{true};
     string_view matchedPathPattern_{""};
     std::string query_;
-    std::unordered_map<std::string, std::string> headers_;
-    std::unordered_map<std::string, std::string> cookies_;
-    mutable std::unordered_map<std::string, std::string> parameters_;
+    std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            headers_;
+    std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            cookies_;
+    mutable std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            parameters_;
     mutable std::shared_ptr<Json::Value> jsonPtr_;
     SessionPtr sessionPtr_;
     mutable AttributesPtr attributesPtr_;
     trantor::InetAddress peer_;
     trantor::InetAddress local_;
diff --git a/lib/src/HttpResponseImpl.h b/lib/src/HttpResponseImpl.h
index 8f32a6f5..870b2678 100644
--- a/lib/src/HttpResponseImpl.h
+++ b/lib/src/HttpResponseImpl.h
@@ -128,11 +128,13 @@ class DROGON_EXPORT HttpResponseImpl : public HttpResponse
             return tolower(c);
         });
         removeHeaderBy(key);
     }
 
-    const std::unordered_map<std::string, std::string> &headers() const override
+    const std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            &headers() const override
     {
         return headers_;
     }
 
     const std::string &getHeaderBy(const std::string &lowerKey) const
@@ -198,11 +200,13 @@ class DROGON_EXPORT HttpResponseImpl : public HttpResponse
             return it->second;
         }
         return defaultCookie;
     }
 
-    const std::unordered_map<std::string, Cookie> &cookies() const override
+    const std::
+        unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
+            &cookies() const override
     {
         return cookies_;
     }
 
     void removeCookie(const std::string &key) override
@@ -444,12 +448,15 @@ class DROGON_EXPORT HttpResponseImpl : public HttpResponse
         assert(code >= 0);
         customStatusCode_ = code;
         statusMessage_ = string_view{message, messageLength};
     }
 
-    std::unordered_map<std::string, std::string> headers_;
-    std::unordered_map<std::string, Cookie> cookies_;
+    std::
+        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
+            headers_;
+    std::unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
+        cookies_;
 
     int customStatusCode_{-1};
     HttpStatusCode statusCode_{kUnknown};
     string_view statusMessage_;
 
diff --git a/lib/src/Utilities.cc b/lib/src/Utilities.cc
index 5844e529..dc093463 100644
--- a/lib/src/Utilities.cc
+++ b/lib/src/Utilities.cc
@@ -1219,7 +1219,16 @@ bool secureRandomBytes(void *ptr, size_t size)
     if (systemRandomBytes(ptr, size))
         return true;
     return false;
 }
 
+namespace internal
+{
+DROGON_EXPORT const size_t fixedRandomNumber = []() {
+    size_t res;
+    utils::secureRandomBytes(&res, sizeof(res));
+    return res;
+}();
+}
+
 }  // namespace utils
 }  // namespace drogon

Vulnerable code:     virtual const std::unordered_map<std::string, std::string> &headers()
        const = 0;
    const std::unordered_map<std::string, std::string> &getHeaders() const
    virtual const std::unordered_map<std::string, std::string> &cookies()
        const = 0;
    const std::unordered_map<std::string, std::string> &getCookies() const
    virtual const std::unordered_map<std::string, std::string> &parameters()
        const = 0;
    const std::unordered_map<std::string, std::string> &getParameters() const
    virtual const std::unordered_map<std::string, std::string> &headers()
        const = 0;
    const std::unordered_map<std::string, std::string> &getHeaders() const
    virtual const std::unordered_map<std::string, Cookie> &cookies() const = 0;
    const std::unordered_map<std::string, Cookie> &getCookies() const
    const std::unordered_map<std::string, std::string> &parameters()
        const override
    const std::unordered_map<std::string, std::string> &headers() const override
    const std::unordered_map<std::string, std::string> &cookies() const override
    std::unordered_map<std::string, std::string> headers_;
    std::unordered_map<std::string, std::string> cookies_;
    mutable std::unordered_map<std::string, std::string> parameters_;
    const std::unordered_map<std::string, std::string> &headers() const override
    const std::unordered_map<std::string, Cookie> &cookies() const override
    std::unordered_map<std::string, std::string> headers_;
    std::unordered_map<std::string, Cookie> cookies_;
Fixed code:     virtual const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &headers() const = 0;
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &getHeaders() const
    virtual const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &cookies() const = 0;
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &getCookies() const
    virtual const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &parameters() const = 0;
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &getParameters() const
#include <drogon/utils/Utilities.h>
    virtual const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &headers() const = 0;
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &getHeaders() const
    virtual const std::
        unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
            &cookies() const = 0;
    const std::
        unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
            &getCookies() const

namespace internal
{
DROGON_EXPORT extern const size_t fixedRandomNumber;
struct SafeStringHash
{
    size_t operator()(const std::string &str) const
    {
        const size_t A = 6665339;
        const size_t B = 2534641;
        size_t h = fixedRandomNumber;
        for (char ch : str)
            h = (h * A) ^ (ch * B);
        return h;
    }
};
}  // namespace internal
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &parameters() const override
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &headers() const override
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &cookies() const override
    std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            headers_;
    std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            cookies_;
    mutable std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            parameters_;
    const std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            &headers() const override
    const std::
        unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
            &cookies() const override
    std::
        unordered_map<std::string, std::string, utils::internal::SafeStringHash>
            headers_;
    std::unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
        cookies_;
namespace internal
{
DROGON_EXPORT const size_t fixedRandomNumber = []() {
    size_t res;
    utils::secureRandomBytes(&res, sizeof(res));
    return res;
}();
}