mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-19 21:03:30 -07:00
Revise cookie 'secure flag' enable condition
The localhost is 'potentially trustworthy' and RFC 6265 allows setting secure flag in this case. Also check `X-Forwarded-Proto` header value to support reverse proxy usage. Note: for reverse proxy users, now the `X-Forwarded-Proto` header is expected to be sent to qbt otherwise the `secure` flag might be set erroneously. https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.5 https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy Closes #21250. PR #21260.
This commit is contained in:
parent
d9bc7935eb
commit
130c0d8487
6 changed files with 27 additions and 6 deletions
|
@ -57,6 +57,7 @@ namespace Http
|
||||||
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
|
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
|
||||||
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
|
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
|
||||||
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
|
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
|
||||||
|
inline const QString HEADER_X_FORWARDED_PROTO = u"X-forwarded-proto"_s;
|
||||||
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
|
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
|
||||||
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
|
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
|
||||||
|
|
||||||
|
|
|
@ -1273,7 +1273,6 @@ void OptionsDialog::loadWebUITabOptions()
|
||||||
// Security
|
// Security
|
||||||
m_ui->checkClickjacking->setChecked(pref->isWebUIClickjackingProtectionEnabled());
|
m_ui->checkClickjacking->setChecked(pref->isWebUIClickjackingProtectionEnabled());
|
||||||
m_ui->checkCSRFProtection->setChecked(pref->isWebUICSRFProtectionEnabled());
|
m_ui->checkCSRFProtection->setChecked(pref->isWebUICSRFProtectionEnabled());
|
||||||
m_ui->checkSecureCookie->setEnabled(pref->isWebUIHttpsEnabled());
|
|
||||||
m_ui->checkSecureCookie->setChecked(pref->isWebUISecureCookieEnabled());
|
m_ui->checkSecureCookie->setChecked(pref->isWebUISecureCookieEnabled());
|
||||||
m_ui->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled());
|
m_ui->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled());
|
||||||
m_ui->textServerDomains->setText(pref->getServerDomains());
|
m_ui->textServerDomains->setText(pref->getServerDomains());
|
||||||
|
@ -1315,7 +1314,6 @@ void OptionsDialog::loadWebUITabOptions()
|
||||||
|
|
||||||
connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->checkWebUIHttps, &QGroupBox::toggled, m_ui->checkSecureCookie, &QWidget::setEnabled);
|
|
||||||
connect(m_ui->checkSecureCookie, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
connect(m_ui->checkSecureCookie, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
connect(m_ui->textServerDomains, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
|
||||||
|
|
|
@ -3675,7 +3675,7 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkSecureCookie">
|
<widget class="QCheckBox" name="checkSecureCookie">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enable cookie Secure flag (requires HTTPS)</string>
|
<string>Enable cookie Secure flag (requires HTTPS or localhost connection)</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -744,7 +744,7 @@ void WebApplication::sessionStart()
|
||||||
|
|
||||||
QNetworkCookie cookie {m_sessionCookieName.toLatin1(), m_currentSession->id().toLatin1()};
|
QNetworkCookie cookie {m_sessionCookieName.toLatin1(), m_currentSession->id().toLatin1()};
|
||||||
cookie.setHttpOnly(true);
|
cookie.setHttpOnly(true);
|
||||||
cookie.setSecure(m_isSecureCookieEnabled && m_isHttpsEnabled);
|
cookie.setSecure(m_isSecureCookieEnabled && isOriginTrustworthy()); // [rfc6265] 4.1.2.5. The Secure Attribute
|
||||||
cookie.setPath(u"/"_s);
|
cookie.setPath(u"/"_s);
|
||||||
if (m_isCSRFProtectionEnabled)
|
if (m_isCSRFProtectionEnabled)
|
||||||
cookie.setSameSitePolicy(QNetworkCookie::SameSite::Strict);
|
cookie.setSameSitePolicy(QNetworkCookie::SameSite::Strict);
|
||||||
|
@ -767,6 +767,27 @@ void WebApplication::sessionEnd()
|
||||||
setHeader({Http::HEADER_SET_COOKIE, QString::fromLatin1(cookie.toRawForm())});
|
setHeader({Http::HEADER_SET_COOKIE, QString::fromLatin1(cookie.toRawForm())});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WebApplication::isOriginTrustworthy() const
|
||||||
|
{
|
||||||
|
// https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
|
||||||
|
|
||||||
|
if (m_isReverseProxySupportEnabled)
|
||||||
|
{
|
||||||
|
const QString forwardedProto = request().headers.value(Http::HEADER_X_FORWARDED_PROTO);
|
||||||
|
if (forwardedProto.compare(u"https", Qt::CaseInsensitive) == 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_isHttpsEnabled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// client is on localhost
|
||||||
|
if (env().clientAddress.isLoopback())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool WebApplication::isCrossSiteRequest(const Http::Request &request) const
|
bool WebApplication::isCrossSiteRequest(const Http::Request &request) const
|
||||||
{
|
{
|
||||||
// https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers
|
// https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers
|
||||||
|
|
|
@ -128,9 +128,11 @@ private:
|
||||||
bool isAuthNeeded();
|
bool isAuthNeeded();
|
||||||
bool isPublicAPI(const QString &scope, const QString &action) const;
|
bool isPublicAPI(const QString &scope, const QString &action) const;
|
||||||
|
|
||||||
|
bool isOriginTrustworthy() const;
|
||||||
bool isCrossSiteRequest(const Http::Request &request) const;
|
bool isCrossSiteRequest(const Http::Request &request) const;
|
||||||
bool validateHostHeader(const QStringList &domains) const;
|
bool validateHostHeader(const QStringList &domains) const;
|
||||||
|
|
||||||
|
// reverse proxy
|
||||||
QHostAddress resolveClientAddress() const;
|
QHostAddress resolveClientAddress() const;
|
||||||
|
|
||||||
// Persistent data
|
// Persistent data
|
||||||
|
|
|
@ -988,7 +988,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="formRow">
|
<div class="formRow">
|
||||||
<input type="checkbox" id="secureCookieCheckbox">
|
<input type="checkbox" id="secureCookieCheckbox">
|
||||||
<label for="secureCookieCheckbox">QBT_TR(Enable cookie Secure flag (requires HTTPS))QBT_TR[CONTEXT=OptionsDialog]</label>
|
<label for="secureCookieCheckbox">QBT_TR(Enable cookie Secure flag (requires HTTPS or localhost connection))QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset class="settings">
|
<fieldset class="settings">
|
||||||
|
@ -1965,7 +1965,6 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
||||||
const isUseHttpsEnabled = $("use_https_checkbox").checked;
|
const isUseHttpsEnabled = $("use_https_checkbox").checked;
|
||||||
$("ssl_cert_text").disabled = !isUseHttpsEnabled;
|
$("ssl_cert_text").disabled = !isUseHttpsEnabled;
|
||||||
$("ssl_key_text").disabled = !isUseHttpsEnabled;
|
$("ssl_key_text").disabled = !isUseHttpsEnabled;
|
||||||
$("secureCookieCheckbox").disabled = !isUseHttpsEnabled;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBypasssAuthSettings = function() {
|
const updateBypasssAuthSettings = function() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue