diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 076a73773..0f6a458de 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -576,6 +576,16 @@ void Preferences::setWebUiPassword(const QString &new_password) setValue("Preferences/WebUI/Password_ha1", md5.result().toHex()); } +bool Preferences::isWebUiClickjackingProtectionEnabled() const +{ + return value("Preferences/WebUI/ClickjackingProtection", true).toBool(); +} + +void Preferences::setWebUiClickjackingProtectionEnabled(bool enabled) +{ + setValue("Preferences/WebUI/ClickjackingProtection", enabled); +} + bool Preferences::isWebUiHttpsEnabled() const { return value("Preferences/WebUI/HTTPS/Enabled", false).toBool(); diff --git a/src/base/preferences.h b/src/base/preferences.h index c9a0188d2..46616591d 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -194,6 +194,10 @@ public: QString getWebUiPassword() const; void setWebUiPassword(const QString &new_password); + // WebUI security + bool isWebUiClickjackingProtectionEnabled() const; + void setWebUiClickjackingProtectionEnabled(bool enabled); + // HTTPS bool isWebUiHttpsEnabled() const; void setWebUiHttpsEnabled(bool enabled); diff --git a/src/gui/optionsdlg.cpp b/src/gui/optionsdlg.cpp index dac450aaf..23af0a055 100644 --- a/src/gui/optionsdlg.cpp +++ b/src/gui/optionsdlg.cpp @@ -378,6 +378,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, m_ui->IPSubnetWhitelistButton, &QPushButton::setEnabled); + connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkDynDNS, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->comboDNSService, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->domainNameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); @@ -694,6 +695,8 @@ void OptionsDialog::saveOptions() pref->setWebUiPassword(webUiPassword()); pref->setWebUiLocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked()); pref->setWebUiAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked()); + // Security + pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked()); // DynDNS pref->setDynDNSEnabled(m_ui->checkDynDNS->isChecked()); pref->setDynDNSService(m_ui->comboDNSService->currentIndex()); @@ -1096,6 +1099,9 @@ void OptionsDialog::loadOptions() m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled()); m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked()); + // Security + m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled()); + m_ui->checkDynDNS->setChecked(pref->isDynDNSEnabled()); m_ui->comboDNSService->setCurrentIndex(static_cast(pref->getDynDNSService())); m_ui->domainNameTxt->setText(pref->getDynDomainName()); diff --git a/src/gui/optionsdlg.ui b/src/gui/optionsdlg.ui index 0a9afb932..f298fbb62 100644 --- a/src/gui/optionsdlg.ui +++ b/src/gui/optionsdlg.ui @@ -3168,6 +3168,13 @@ Use ';' to split multiple entries. Can use wildcard '*'. + + + + Enable clickjacking protection + + + diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 26775b203..a970cd06d 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -205,6 +205,8 @@ void AppController::preferencesAction() for (const Utils::Net::Subnet &subnet : copyAsConst(pref->getWebUiAuthSubnetWhitelist())) authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet); data["bypass_auth_subnet_whitelist"] = authSubnetWhitelistStringList.join("\n"); + // Security + data["web_ui_clickjacking_protection_enabled"] = pref->isWebUiClickjackingProtectionEnabled(); // Update my dynamic domain name data["dyndns_enabled"] = pref->isDynDNSEnabled(); data["dyndns_service"] = pref->getDynDNSService(); @@ -479,6 +481,9 @@ void AppController::setPreferencesAction() // recognize new lines and commas as delimiters pref->setWebUiAuthSubnetWhitelist(m["bypass_auth_subnet_whitelist"].toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts)); } + // Security + if (m.contains("web_ui_clickjacking_protection_enabled")) + pref->setWebUiClickjackingProtectionEnabled(m["web_ui_clickjacking_protection_enabled"].toBool()); // Update my dynamic domain name if (m.contains("dyndns_enabled")) pref->setDynDNSEnabled(m["dyndns_enabled"].toBool()); diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index 35e6d2fa9..3ce44da84 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -428,6 +428,8 @@ void WebApplication::configure() m_currentLocale = newLocale; m_translatedFiles.clear(); } + + m_isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled(); } void WebApplication::registerAPIController(const QString &scope, APIController *controller) @@ -525,11 +527,13 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons print(error.message(), Http::CONTENT_TYPE_TXT); } - // avoid clickjacking attacks - header(Http::HEADER_X_FRAME_OPTIONS, "SAMEORIGIN"); header(Http::HEADER_X_XSS_PROTECTION, "1; mode=block"); header(Http::HEADER_X_CONTENT_TYPE_OPTIONS, "nosniff"); - header(Http::HEADER_CONTENT_SECURITY_POLICY, "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none';"); + + if (m_isClickjackingProtectionEnabled) { + header(Http::HEADER_X_FRAME_OPTIONS, "SAMEORIGIN"); + header(Http::HEADER_CONTENT_SECURITY_POLICY, "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none';"); + } return response(); } diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 509927993..e96b25300 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -142,4 +142,7 @@ private: }; QMap m_translatedFiles; QString m_currentLocale; + + // security related + bool m_isClickjackingProtectionEnabled; }; diff --git a/src/webui/www/private/preferences_content.html b/src/webui/www/private/preferences_content.html index 5020fd938..d828275cb 100644 --- a/src/webui/www/private/preferences_content.html +++ b/src/webui/www/private/preferences_content.html @@ -458,6 +458,11 @@ + +
+ + +
@@ -1022,6 +1027,9 @@ $('bypass_auth_subnet_whitelist_textarea').setProperty('value', pref.bypass_auth_subnet_whitelist); updateBypasssAuthSettings(); + // Security + $('clickjacking_protection_checkbox').setProperty('checked', pref.web_ui_clickjacking_protection_enabled); + // Update my dynamic domain name $('use_dyndns_checkbox').setProperty('checked', pref.dyndns_enabled); $('dyndns_select').setProperty('value', pref.dyndns_service); @@ -1313,6 +1321,8 @@ settings.set('bypass_auth_subnet_whitelist_enabled', $('bypass_auth_subnet_whitelist_checkbox').getProperty('checked')); settings.set('bypass_auth_subnet_whitelist', $('bypass_auth_subnet_whitelist_textarea').getProperty('value')); + settings.set('web_ui_clickjacking_protection_enabled', $('clickjacking_protection_checkbox').getProperty('checked')); + // Update my dynamic domain name settings.set('dyndns_enabled', $('use_dyndns_checkbox').getProperty('checked')); settings.set('dyndns_service', $('dyndns_select').getProperty('value'));