High severityNVD Advisory· Published Sep 28, 2021· Updated Aug 4, 2024
web_server allows OTA update without checking user defined basic auth username & password
CVE-2021-41104
Description
ESPHome is a system to control the ESP8266/ESP32. Anyone with web_server enabled and HTTP basic auth configured on version 2021.9.1 or older is vulnerable to an issue in which web_server allows over-the-air (OTA) updates without checking user defined basic auth username & password. This issue is patched in version 2021.9.2. As a workaround, one may disable or remove web_server.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
esphomePyPI | < 2021.9.2 | 2021.9.2 |
Affected products
1Patches
22234f6aacf8cFix lint issues in web_server_base (#2409)
1 file changed · +3 −2
esphome/components/web_server_base/web_server_base.h+3 −2 modified@@ -3,6 +3,7 @@ #ifdef USE_ARDUINO #include <memory> +#include <utility> #include "esphome/core/component.h" #include <ESPAsyncWebServer.h> @@ -96,8 +97,8 @@ class WebServerBase : public Component { std::shared_ptr<AsyncWebServer> get_server() const { return server_; } float get_setup_priority() const override; - void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } - void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } + void set_auth_username(std::string auth_username) { credentials_.username = std::move(auth_username); } + void set_auth_password(std::string auth_password) { credentials_.password = std::move(auth_password); } void add_handler(AsyncWebHandler *handler);
be965a60eba6Merge pull request from GHSA-48mj-p7x2-5jfm
5 files changed · +81 −25
esphome/components/web_server_base/web_server_base.cpp+11 −0 modified@@ -17,6 +17,17 @@ namespace web_server_base { static const char *const TAG = "web_server_base"; +void WebServerBase::add_handler(AsyncWebHandler *handler) { + // remove all handlers + + if (!credentials_.username.empty()) { + handler = new internal::AuthMiddlewareHandler(handler, &credentials_); + } + this->handlers_.push_back(handler); + if (this->server_ != nullptr) + this->server_->addHandler(handler); +} + void report_ota_error() { StreamString ss; Update.printError(ss);
esphome/components/web_server_base/web_server_base.h+66 −6 modified@@ -10,6 +10,68 @@ namespace esphome { namespace web_server_base { +namespace internal { + +class MiddlewareHandler : public AsyncWebHandler { + public: + MiddlewareHandler(AsyncWebHandler *next) : next_(next) {} + + bool canHandle(AsyncWebServerRequest *request) override { return next_->canHandle(request); } + void handleRequest(AsyncWebServerRequest *request) override { next_->handleRequest(request); } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + next_->handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + next_->handleBody(request, data, len, index, total); + } + bool isRequestHandlerTrivial() override { return next_->isRequestHandlerTrivial(); } + + protected: + AsyncWebHandler *next_; +}; + +struct Credentials { + std::string username; + std::string password; +}; + +class AuthMiddlewareHandler : public MiddlewareHandler { + public: + AuthMiddlewareHandler(AsyncWebHandler *next, Credentials *credentials) + : MiddlewareHandler(next), credentials_(credentials) {} + + bool check_auth(AsyncWebServerRequest *request) { + bool success = request->authenticate(credentials_->username.c_str(), credentials_->password.c_str()); + if (!success) { + request->requestAuthentication(); + } + return success; + } + + void handleRequest(AsyncWebServerRequest *request) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleRequest(request); + } + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleUpload(request, filename, index, data, len, final); + } + void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override { + if (!check_auth(request)) + return; + MiddlewareHandler::handleBody(request, data, len, index, total); + } + + protected: + Credentials *credentials_; +}; + +} // namespace internal + class WebServerBase : public Component { public: void init() { @@ -34,13 +96,10 @@ class WebServerBase : public Component { std::shared_ptr<AsyncWebServer> get_server() const { return server_; } float get_setup_priority() const override; - void add_handler(AsyncWebHandler *handler) { - // remove all handlers + void set_auth_username(std::string auth_username) { credentials_.username = auth_username; } + void set_auth_password(std::string auth_password) { credentials_.password = auth_password; } - this->handlers_.push_back(handler); - if (this->server_ != nullptr) - this->server_->addHandler(handler); - } + void add_handler(AsyncWebHandler *handler); void add_ota_handler(); @@ -54,6 +113,7 @@ class WebServerBase : public Component { uint16_t port_{80}; std::shared_ptr<AsyncWebServer> server_{nullptr}; std::vector<AsyncWebHandler *> handlers_; + internal::Credentials credentials_; }; class OTARequestHandler : public AsyncWebHandler {
esphome/components/web_server/__init__.py+4 −4 modified@@ -34,8 +34,8 @@ cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { - cv.Required(CONF_USERNAME): cv.string_strict, - cv.Required(CONF_PASSWORD): cv.string_strict, + cv.Required(CONF_USERNAME): cv.All(cv.string_strict, cv.Length(min=1)), + cv.Required(CONF_PASSWORD): cv.All(cv.string_strict, cv.Length(min=1)), } ), cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( @@ -57,8 +57,8 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) if CONF_AUTH in config: - cg.add(var.set_username(config[CONF_AUTH][CONF_USERNAME])) - cg.add(var.set_password(config[CONF_AUTH][CONF_PASSWORD])) + cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) + cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) if CONF_CSS_INCLUDE in config: cg.add_define("WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE])
esphome/components/web_server/web_server.cpp+0 −7 modified@@ -158,9 +158,6 @@ void WebServer::setup() { void WebServer::dump_config() { ESP_LOGCONFIG(TAG, "Web Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port()); - if (this->using_auth()) { - ESP_LOGCONFIG(TAG, " Basic authentication enabled"); - } } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } @@ -764,10 +761,6 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { - if (this->using_auth() && !request->authenticate(this->username_, this->password_)) { - return request->requestAuthentication(); - } - if (request->url() == "/") { this->handle_index_request(request); return;
esphome/components/web_server/web_server.h+0 −8 modified@@ -32,10 +32,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base) : base_(base) {} - void set_username(const char *username) { username_ = username; } - - void set_password(const char *password) { password_ = password; } - /** Set the URL to the CSS <link> that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -85,8 +81,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_js_request(AsyncWebServerRequest *request); #endif - bool using_auth() { return username_ != nullptr && password_ != nullptr; } - #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/<id>'. @@ -184,8 +178,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { protected: web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; - const char *username_{nullptr}; - const char *password_{nullptr}; const char *css_url_{nullptr}; const char *css_include_{nullptr}; const char *js_url_{nullptr};
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-48mj-p7x2-5jfmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-41104ghsaADVISORY
- github.com/esphome/esphome/commit/2234f6aacf8cc653307fed80f3750317a82c4f83ghsaWEB
- github.com/esphome/esphome/commit/be965a60eba6bb769e2a5afdbc8eed132f077a59ghsaWEB
- github.com/esphome/esphome/pull/2409ghsaWEB
- github.com/esphome/esphome/pull/2409/commits/207cde1667d8c799a197b78ca8a5a14de8d5ca1emitrex_refsource_MISC
- github.com/esphome/esphome/releases/tag/2021.9.2ghsax_refsource_MISCWEB
- github.com/esphome/esphome/security/advisories/GHSA-48mj-p7x2-5jfmghsax_refsource_CONFIRMWEB
- github.com/pypa/advisory-database/tree/main/vulns/esphome/PYSEC-2021-351.yamlghsaWEB
News mentions
0No linked articles in our index yet.