From 24cad4483b16c9903aab6149a5f6fc1884de7d93 Mon Sep 17 00:00:00 2001 From: sledgehammer999 Date: Mon, 20 Mar 2023 17:01:09 +0200 Subject: [PATCH] Deny remote user login with default credentials Apparently there are users exposing the webui client to the internet without changing the default credentials. And apparently there are attackers out there scanning for exposed clients and then logging in with the default credentials and running code (crypto miners). Closes #13833 Closes #16529 Closes #18731 --- src/webui/api/authcontroller.cpp | 14 ++++++++++++++ src/webui/api/isessionmanager.h | 1 + src/webui/webapplication.cpp | 7 ++++++- src/webui/webapplication.h | 1 + src/webui/www/public/scripts/login.js | 17 +++++++++++++---- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/webui/api/authcontroller.cpp b/src/webui/api/authcontroller.cpp index 8900a738f3c..25c3ebbb7ec 100644 --- a/src/webui/api/authcontroller.cpp +++ b/src/webui/api/authcontroller.cpp @@ -68,6 +68,20 @@ void AuthController::loginAction() const QString username {pref->getWebUiUsername()}; const QByteArray secret {pref->getWebUIPassword()}; + if (!m_sessionManager->isLocalClient() + && (username == u"admin"_qs) + && (secret == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ=="))) + { + if (Preferences::instance()->getWebUIMaxAuthFailCount() > 0) + increaseFailedAttempts(); + LogMsg(tr("WebAPI login failure. Reason: Remote connection with the default credentials is prohibited.") + , Log::WARNING); + throw APIError(APIErrorType::AccessDenied + , tr("Remote connection with the default credentials is prohibited. Change the default credentials by connecting from %1." + , "Remote connection with the default credentials is prohibited. Change the default credentials by connecting from localhost.") + .arg(u"localhost"_qs)); + } + const bool usernameEqual = Utils::Password::slowEquals(usernameFromWeb.toUtf8(), username.toUtf8()); const bool passwordEqual = Utils::Password::PBKDF2::verify(secret, passwordFromWeb); diff --git a/src/webui/api/isessionmanager.h b/src/webui/api/isessionmanager.h index 64396a7da9d..022c8bc55a2 100644 --- a/src/webui/api/isessionmanager.h +++ b/src/webui/api/isessionmanager.h @@ -42,6 +42,7 @@ struct ISessionManager { virtual ~ISessionManager() = default; virtual QString clientId() const = 0; + virtual bool isLocalClient() const = 0; virtual ISession *session() = 0; virtual void sessionStart() = 0; virtual void sessionEnd() = 0; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index ba24b9049dc..2d886e9b14e 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -586,6 +586,11 @@ QString WebApplication::clientId() const return m_clientAddress.toString(); } +bool WebApplication::isLocalClient() const +{ + return Utils::Net::isLoopbackAddress(m_clientAddress); +} + void WebApplication::sessionInitialize() { Q_ASSERT(!m_currentSession); @@ -638,7 +643,7 @@ QString WebApplication::generateSid() const bool WebApplication::isAuthNeeded() { - if (!m_isLocalAuthEnabled && Utils::Net::isLoopbackAddress(m_clientAddress)) + if (!m_isLocalAuthEnabled && isLocalClient()) return false; if (m_isAuthSubnetWhitelistEnabled && Utils::Net::isIPInSubnets(m_clientAddress, m_authSubnetWhitelist)) return false; diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 79e0740f9c3..13b7ce0e573 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -98,6 +98,7 @@ class WebApplication final Http::Response processRequest(const Http::Request &request, const Http::Environment &env) override; QString clientId() const override; + bool isLocalClient() const override; WebSession *session() override; void sessionStart() override; void sessionEnd() override; diff --git a/src/webui/www/public/scripts/login.js b/src/webui/www/public/scripts/login.js index 21ccc6ffa2d..2a4dd776bbb 100644 --- a/src/webui/www/public/scripts/login.js +++ b/src/webui/www/public/scripts/login.js @@ -39,16 +39,25 @@ document.addEventListener('DOMContentLoaded', function() { function submitLoginForm() { const errorMsgElement = document.getElementById('error_msg'); + errorMsgElement.textContent = ""; const xhr = new XMLHttpRequest(); xhr.open('POST', 'api/v2/auth/login', true); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=UTF-8'); xhr.addEventListener('readystatechange', function() { if (xhr.readyState === 4) { // DONE state - if ((xhr.status === 200) && (xhr.responseText === "Ok.")) - location.reload(true); - else - errorMsgElement.textContent = 'QBT_TR(Invalid Username or Password.)QBT_TR[CONTEXT=HttpServer]'; + if (xhr.status === 200) { + if (xhr.responseText === "Ok.") + location.reload(true); + else + errorMsgElement.textContent = 'QBT_TR(Invalid Username or Password.)QBT_TR[CONTEXT=HttpServer]'; + } + else if ((xhr.status === 403) && xhr.responseText) { + errorMsgElement.textContent = xhr.responseText; + } + else { + errorMsgElement.textContent = 'QBT_TR(Generic login error.)QBT_TR[CONTEXT=HttpServer]'; + } } }); xhr.addEventListener('error', function() {