Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deny remote user login with default credentials #18735

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/app/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
#include "gui/uithememanager.h"
#include "gui/utils.h"
#include "gui/windowstate.h"
#else
#include "base/utils/password.h"
#endif // DISABLE_GUI

#ifndef DISABLE_WEBUI
Expand Down Expand Up @@ -898,7 +900,7 @@ try
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url);
printf("%s\n", qUtf8Printable(mesg));

if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ=="))
if (pref->getWebUIPassword() == Utils::Password::defaultPassword)
{
const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n'
+ tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_qs) + u'\n'
Expand Down
5 changes: 2 additions & 3 deletions src/base/preferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "profile.h"
#include "settingsstorage.h"
#include "utils/fs.h"
#include "utils/password.h"

namespace
{
Expand Down Expand Up @@ -613,9 +614,7 @@ void Preferences::setWebUiUsername(const QString &username)

QByteArray Preferences::getWebUIPassword() const
{
// default: adminadmin
const auto defaultValue = QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==");
return value(u"Preferences/WebUI/Password_PBKDF2"_qs, defaultValue);
return value(u"Preferences/WebUI/Password_PBKDF2"_qs, Utils::Password::defaultPassword);
}

void Preferences::setWebUIPassword(const QByteArray &password)
Expand Down
4 changes: 3 additions & 1 deletion src/base/utils/password.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@

#pragma once

class QByteArray;
#include <QByteArray>

class QString;

namespace Utils::Password
{
inline const QByteArray defaultPassword = QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, QByteArray can't be constexpr.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think constexpr is important in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to obsess with constexpr when it is already compile-time:
https://doc.qt.io/qt-6/qbytearray.html#QByteArrayLiteral

The macro generates the data for a QByteArray out of the string literal ba at compile time. Creating a QByteArray from it is free in this case, and the generated byte array data is stored in the read-only segment of the compiled object file.

// Implements constant-time comparison to protect against timing attacks
// Taken from https://crackstation.net/hashing-security.htm
bool slowEquals(const QByteArray &a, const QByteArray &b);
Expand Down
14 changes: 14 additions & 0 deletions src/webui/api/authcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 == Utils::Password::defaultPassword))
{
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);

Expand Down
1 change: 1 addition & 0 deletions src/webui/api/isessionmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/webui/webapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/webui/webapplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/webui/www/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ <h1>qBittorrent QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]</h1>
<img src="images/qbittorrent-tray.svg" alt="qBittorrent logo" />
</div>
<div id="formplace" class="col">
<form id="loginform" method="post" onsubmit="submitLoginForm();">
<form id="loginform" method="post" onsubmit="submitLoginForm(event);">
<div class="row">
<label for="username">QBT_TR(Username)QBT_TR[CONTEXT=HttpServer]</label><br />
<input type="text" id="username" name="username" autocomplete="username" />
<input type="text" id="username" name="username" autocomplete="username" required />
</div>
<div class="row">
<label for="password">QBT_TR(Password)QBT_TR[CONTEXT=HttpServer]</label><br />
<input type="password" id="password" name="password" autocomplete="current-password" />
<input type="password" id="password" name="password" autocomplete="current-password" required />
</div>
<div class="row">
<input type="submit" id="login" value="QBT_TR(Login)QBT_TR[CONTEXT=HttpServer]" />
Expand Down
24 changes: 15 additions & 9 deletions src/webui/www/public/scripts/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,30 @@
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('username').focus();
document.getElementById('username').select();

document.getElementById('loginform').addEventListener('submit', function(e) {
e.preventDefault();
});
});

function submitLoginForm() {
function submitLoginForm(event) {
event.preventDefault();
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() {
Expand Down