VYPR
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.

PackageAffected versionsPatched versions
esphomePyPI
< 2021.9.22021.9.2

Affected products

1

Patches

2
2234f6aacf8c

Fix lint issues in web_server_base (#2409)

https://github.com/esphome/esphomeJesse HillsSep 28, 2021via ghsa
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);
     
    
be965a60eba6

Merge pull request from GHSA-48mj-p7x2-5jfm

https://github.com/esphome/esphomeOtto WinterSep 28, 2021via ghsa
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

News mentions

0

No linked articles in our index yet.