Browse Source

Merge pull request #9859 from Chocobo1/host

Add option for WebUI Host header validation
adaptive-webui-19844
Mike Tzou 6 years ago committed by GitHub
parent
commit
d05897c89a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      src/base/preferences.cpp
  2. 2
      src/base/preferences.h
  3. 3
      src/gui/optionsdialog.cpp
  4. 65
      src/gui/optionsdialog.ui
  5. 3
      src/webui/api/appcontroller.cpp
  6. 3
      src/webui/webapplication.cpp
  7. 1
      src/webui/webapplication.h
  8. 23
      src/webui/www/private/preferences_content.html

10
src/base/preferences.cpp

@ -626,6 +626,16 @@ void Preferences::setWebUiCSRFProtectionEnabled(bool enabled)
setValue("Preferences/WebUI/CSRFProtection", enabled); setValue("Preferences/WebUI/CSRFProtection", enabled);
} }
bool Preferences::isWebUIHostHeaderValidationEnabled() const
{
return value("Preferences/WebUI/HostHeaderValidation", true).toBool();
}
void Preferences::setWebUIHostHeaderValidationEnabled(const bool enabled)
{
setValue("Preferences/WebUI/HostHeaderValidation", enabled);
}
bool Preferences::isWebUiHttpsEnabled() const bool Preferences::isWebUiHttpsEnabled() const
{ {
return value("Preferences/WebUI/HTTPS/Enabled", false).toBool(); return value("Preferences/WebUI/HTTPS/Enabled", false).toBool();

2
src/base/preferences.h

@ -201,6 +201,8 @@ public:
void setWebUiClickjackingProtectionEnabled(bool enabled); void setWebUiClickjackingProtectionEnabled(bool enabled);
bool isWebUiCSRFProtectionEnabled() const; bool isWebUiCSRFProtectionEnabled() const;
void setWebUiCSRFProtectionEnabled(bool enabled); void setWebUiCSRFProtectionEnabled(bool enabled);
bool isWebUIHostHeaderValidationEnabled() const;
void setWebUIHostHeaderValidationEnabled(bool enabled);
// HTTPS // HTTPS
bool isWebUiHttpsEnabled() const; bool isWebUiHttpsEnabled() const;

3
src/gui/optionsdialog.cpp

@ -390,6 +390,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, m_ui->IPSubnetWhitelistButton, &QPushButton::setEnabled); connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, m_ui->IPSubnetWhitelistButton, &QPushButton::setEnabled);
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->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkDynDNS, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkDynDNS, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->comboDNSService, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->comboDNSService, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
connect(m_ui->domainNameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->domainNameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
@ -719,6 +720,7 @@ void OptionsDialog::saveOptions()
// Security // Security
pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked()); pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
pref->setWebUiCSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked()); pref->setWebUiCSRFProtectionEnabled(m_ui->checkCSRFProtection->isChecked());
pref->setWebUIHostHeaderValidationEnabled(m_ui->groupHostHeaderValidation->isChecked());
// DynDNS // DynDNS
pref->setDynDNSEnabled(m_ui->checkDynDNS->isChecked()); pref->setDynDNSEnabled(m_ui->checkDynDNS->isChecked());
pref->setDynDNSService(m_ui->comboDNSService->currentIndex()); pref->setDynDNSService(m_ui->comboDNSService->currentIndex());
@ -1082,6 +1084,7 @@ void OptionsDialog::loadOptions()
// 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->groupHostHeaderValidation->setChecked(pref->isWebUIHostHeaderValidationEnabled());
m_ui->checkDynDNS->setChecked(pref->isDynDNSEnabled()); m_ui->checkDynDNS->setChecked(pref->isDynDNSEnabled());
m_ui->comboDNSService->setCurrentIndex(static_cast<int>(pref->getDynDNSService())); m_ui->comboDNSService->setCurrentIndex(static_cast<int>(pref->getDynDNSService()));

65
src/gui/optionsdialog.ui

@ -2944,28 +2944,6 @@ Specify an IPv4 or IPv6 address. You can specify &quot;0.0.0.0&quot; for any IPv
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="labelServerDomains">
<property name="text">
<string>Server domains:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="textServerDomains">
<property name="toolTip">
<string>Whitelist for filtering HTTP Host header values.
In order to defend against DNS rebinding attack,
you should put in domain names used by WebUI server.
Use ';' to split multiple entries. Can use wildcard '*'.</string>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<widget class="QCheckBox" name="checkWebUIUPnP"> <widget class="QCheckBox" name="checkWebUIUPnP">
<property name="text"> <property name="text">
@ -3176,6 +3154,12 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Security</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_33">
<item> <item>
<widget class="QCheckBox" name="checkClickjacking"> <widget class="QCheckBox" name="checkClickjacking">
<property name="text"> <property name="text">
@ -3190,6 +3174,43 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupHostHeaderValidation">
<property name="title">
<string>Enable Host header validation</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_32">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="labelServerDomains">
<property name="text">
<string>Server domains:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="textServerDomains">
<property name="toolTip">
<string>Whitelist for filtering HTTP Host header values.
In order to defend against DNS rebinding attack,
you should put in domain names used by WebUI server.
Use ';' to split multiple entries. Can use wildcard '*'.</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="checkDynDNS"> <widget class="QGroupBox" name="checkDynDNS">
<property name="title"> <property name="title">

3
src/webui/api/appcontroller.cpp

@ -208,6 +208,7 @@ void AppController::preferencesAction()
// Security // Security
data["web_ui_clickjacking_protection_enabled"] = pref->isWebUiClickjackingProtectionEnabled(); data["web_ui_clickjacking_protection_enabled"] = pref->isWebUiClickjackingProtectionEnabled();
data["web_ui_csrf_protection_enabled"] = pref->isWebUiCSRFProtectionEnabled(); data["web_ui_csrf_protection_enabled"] = pref->isWebUiCSRFProtectionEnabled();
data["web_ui_host_header_validation_enabled"] = pref->isWebUIHostHeaderValidationEnabled();
// Update my dynamic domain name // Update my dynamic domain name
data["dyndns_enabled"] = pref->isDynDNSEnabled(); data["dyndns_enabled"] = pref->isDynDNSEnabled();
data["dyndns_service"] = pref->getDynDNSService(); data["dyndns_service"] = pref->getDynDNSService();
@ -487,6 +488,8 @@ void AppController::setPreferencesAction()
pref->setWebUiClickjackingProtectionEnabled(m["web_ui_clickjacking_protection_enabled"].toBool()); pref->setWebUiClickjackingProtectionEnabled(m["web_ui_clickjacking_protection_enabled"].toBool());
if (m.contains("web_ui_csrf_protection_enabled")) if (m.contains("web_ui_csrf_protection_enabled"))
pref->setWebUiCSRFProtectionEnabled(m["web_ui_csrf_protection_enabled"].toBool()); pref->setWebUiCSRFProtectionEnabled(m["web_ui_csrf_protection_enabled"].toBool());
if (m.contains("web_ui_host_header_validation_enabled"))
pref->setWebUIHostHeaderValidationEnabled(m["web_ui_host_header_validation_enabled"].toBool());
// Update my dynamic domain name // Update my dynamic domain name
if (m.contains("dyndns_enabled")) if (m.contains("dyndns_enabled"))
pref->setDynDNSEnabled(m["dyndns_enabled"].toBool()); pref->setDynDNSEnabled(m["dyndns_enabled"].toBool());

3
src/webui/webapplication.cpp

@ -452,6 +452,7 @@ void WebApplication::configure()
m_isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled(); m_isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled();
m_isCSRFProtectionEnabled = pref->isWebUiCSRFProtectionEnabled(); m_isCSRFProtectionEnabled = pref->isWebUiCSRFProtectionEnabled();
m_isHostHeaderValidationEnabled = pref->isWebUIHostHeaderValidationEnabled();
m_isHttpsEnabled = pref->isWebUiHttpsEnabled(); m_isHttpsEnabled = pref->isWebUiHttpsEnabled();
} }
@ -542,7 +543,7 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons
try { try {
// block suspicious requests // block suspicious requests
if ((m_isCSRFProtectionEnabled && isCrossSiteRequest(m_request)) if ((m_isCSRFProtectionEnabled && isCrossSiteRequest(m_request))
|| !validateHostHeader(m_domainList)) { || (m_isHostHeaderValidationEnabled && !validateHostHeader(m_domainList))) {
throw UnauthorizedHTTPError(); throw UnauthorizedHTTPError();
} }

1
src/webui/webapplication.h

@ -155,5 +155,6 @@ private:
QStringList m_domainList; QStringList m_domainList;
bool m_isClickjackingProtectionEnabled; bool m_isClickjackingProtectionEnabled;
bool m_isCSRFProtectionEnabled; bool m_isCSRFProtectionEnabled;
bool m_isHostHeaderValidationEnabled;
bool m_isHttpsEnabled; bool m_isHttpsEnabled;
}; };

23
src/webui/www/private/preferences_content.html

@ -411,7 +411,6 @@
<legend>QBT_TR(Web User Interface (Remote control))QBT_TR[CONTEXT=OptionsDialog]</legend> <legend>QBT_TR(Web User Interface (Remote control))QBT_TR[CONTEXT=OptionsDialog]</legend>
<label class="leftLabelMedium" for="webui_address_value">QBT_TR(IP address:)QBT_TR[CONTEXT=OptionsDialog]</label><input type="text" id="webui_address_value" /> <label class="leftLabelMedium" for="webui_address_value">QBT_TR(IP address:)QBT_TR[CONTEXT=OptionsDialog]</label><input type="text" id="webui_address_value" />
<label for="webui_port_value" style="margin-left: 10px;">QBT_TR(Port:)QBT_TR[CONTEXT=OptionsDialog]</label><input type="text" id="webui_port_value" style="width: 4em;" /><br/> <label for="webui_port_value" style="margin-left: 10px;">QBT_TR(Port:)QBT_TR[CONTEXT=OptionsDialog]</label><input type="text" id="webui_port_value" style="width: 4em;" /><br/>
<label class="leftLabelMedium" for="webui_domain_textarea">QBT_TR(Server domains:)QBT_TR[CONTEXT=OptionsDialog]</label><textarea id="webui_domain_textarea" rows="1" cols="70"></textarea><br/>
<input type="checkbox" id="webui_upnp_checkbox" /> <input type="checkbox" id="webui_upnp_checkbox" />
<label for="webui_upnp_checkbox">QBT_TR(Use UPnP / NAT-PMP to forward the port from my router)QBT_TR[CONTEXT=OptionsDialog]</label><br/> <label for="webui_upnp_checkbox">QBT_TR(Use UPnP / NAT-PMP to forward the port from my router)QBT_TR[CONTEXT=OptionsDialog]</label><br/>
<fieldset class="settings"> <fieldset class="settings">
@ -449,6 +448,8 @@
</div> </div>
</fieldset> </fieldset>
<fieldset class="settings">
<legend>QBT_TR(Security)QBT_TR[CONTEXT=OptionsDialog]</legend>
<div class="formRow"> <div class="formRow">
<input type="checkbox" id="clickjacking_protection_checkbox" /> <input type="checkbox" id="clickjacking_protection_checkbox" />
<label for="clickjacking_protection_checkbox">QBT_TR(Enable clickjacking protection)QBT_TR[CONTEXT=OptionsDialog]</label> <label for="clickjacking_protection_checkbox">QBT_TR(Enable clickjacking protection)QBT_TR[CONTEXT=OptionsDialog]</label>
@ -457,6 +458,18 @@
<input type="checkbox" id="csrf_protection_checkbox" /> <input type="checkbox" id="csrf_protection_checkbox" />
<label for="csrf_protection_checkbox">QBT_TR(Enable Cross-Site Request Forgery (CSRF) protection)QBT_TR[CONTEXT=OptionsDialog]</label> <label for="csrf_protection_checkbox">QBT_TR(Enable Cross-Site Request Forgery (CSRF) protection)QBT_TR[CONTEXT=OptionsDialog]</label>
</div> </div>
<fieldset class="settings">
<legend>
<input type="checkbox" id="host_header_validation_checkbox" onclick="updateHostHeaderValidationSettings();" />
<label for="host_header_validation_checkbox">QBT_TR(Enable Host header validation)QBT_TR[CONTEXT=OptionsDialog]</label>
</legend>
<div class="formRow">
<label class="leftLabelMedium" for="webui_domain_textarea">QBT_TR(Server domains:)QBT_TR[CONTEXT=OptionsDialog]</label>
<textarea id="webui_domain_textarea" rows="1" cols="60"></textarea>
</div>
</fieldset>
</fieldset>
</fieldset> </fieldset>
<fieldset class="settings"> <fieldset class="settings">
@ -712,6 +725,11 @@
$('bypass_auth_subnet_whitelist_textarea').setProperty('disabled', !isBypassAuthSubnetWhitelistEnabled); $('bypass_auth_subnet_whitelist_textarea').setProperty('disabled', !isBypassAuthSubnetWhitelistEnabled);
}; };
updateHostHeaderValidationSettings = function() {
var isHostHeaderValidationEnabled = $('host_header_validation_checkbox').getProperty('checked');
$('webui_domain_textarea').setProperty('disabled', !isHostHeaderValidationEnabled);
};
updateDynDnsSettings = function() { updateDynDnsSettings = function() {
var isDynDnsEnabled = $('use_dyndns_checkbox').getProperty('checked'); var isDynDnsEnabled = $('use_dyndns_checkbox').getProperty('checked');
$('dyndns_select').setProperty('disabled', !isDynDnsEnabled); $('dyndns_select').setProperty('disabled', !isDynDnsEnabled);
@ -971,6 +989,8 @@
// Security // Security
$('clickjacking_protection_checkbox').setProperty('checked', pref.web_ui_clickjacking_protection_enabled); $('clickjacking_protection_checkbox').setProperty('checked', pref.web_ui_clickjacking_protection_enabled);
$('csrf_protection_checkbox').setProperty('checked', pref.web_ui_csrf_protection_enabled); $('csrf_protection_checkbox').setProperty('checked', pref.web_ui_csrf_protection_enabled);
$('host_header_validation_checkbox').setProperty('checked', pref.web_ui_host_header_validation_enabled);
updateHostHeaderValidationSettings();
// Update my dynamic domain name // Update my dynamic domain name
$('use_dyndns_checkbox').setProperty('checked', pref.dyndns_enabled); $('use_dyndns_checkbox').setProperty('checked', pref.dyndns_enabled);
@ -1256,6 +1276,7 @@
settings.set('web_ui_clickjacking_protection_enabled', $('clickjacking_protection_checkbox').getProperty('checked')); settings.set('web_ui_clickjacking_protection_enabled', $('clickjacking_protection_checkbox').getProperty('checked'));
settings.set('web_ui_csrf_protection_enabled', $('csrf_protection_checkbox').getProperty('checked')); settings.set('web_ui_csrf_protection_enabled', $('csrf_protection_checkbox').getProperty('checked'));
settings.set('web_ui_host_header_validation_enabled', $('host_header_validation_checkbox').getProperty('checked'));
// Update my dynamic domain name // Update my dynamic domain name
settings.set('dyndns_enabled', $('use_dyndns_checkbox').getProperty('checked')); settings.set('dyndns_enabled', $('use_dyndns_checkbox').getProperty('checked'));

Loading…
Cancel
Save