From ba08249eef39393f9d9439c5f469e8c12260edbf Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 6 Apr 2026 21:16:23 -0500 Subject: [PATCH 1/4] prevent /upload from overwriting wsec.json --- wled00/wled_server.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index f7aac7fa50..1f303119c0 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -204,6 +204,11 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, finalname = '/' + finalname; // prepend slash if missing } + if (finalname.indexOf(FPSTR(s_wsec)) >= 0) { + request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json + return; + } + request->_tempFile = WLED_FS.open(finalname, "w"); DEBUG_PRINTF_P(PSTR("Uploading %s\n"), finalname.c_str()); if (finalname.equals(FPSTR(getPresetsFileName()))) presetsModifiedTime = toki.second(); From 7d9bae9d4317a3ae2effc8f9a4aaab5c5bbf4dc3 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 6 Apr 2026 22:10:55 -0500 Subject: [PATCH 2/4] move wsec check outside of if index logic. --- wled00/wled_server.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 1f303119c0..916b990a11 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -198,17 +198,18 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg)); return; } + + if (filename.indexOf(FPSTR(s_wsec)) >= 0) { + request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json + return; + } + if (!index) { String finalname = filename; if (finalname.charAt(0) != '/') { finalname = '/' + finalname; // prepend slash if missing } - if (finalname.indexOf(FPSTR(s_wsec)) >= 0) { - request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json - return; - } - request->_tempFile = WLED_FS.open(finalname, "w"); DEBUG_PRINTF_P(PSTR("Uploading %s\n"), finalname.c_str()); if (finalname.equals(FPSTR(getPresetsFileName()))) presetsModifiedTime = toki.second(); From 98efc038325d950cd2f72423f739dbc23fb17ba6 Mon Sep 17 00:00:00 2001 From: foamyguy Date: Mon, 6 Apr 2026 22:24:52 -0500 Subject: [PATCH 3/4] use isFinal check --- wled00/wled_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 916b990a11..cfcfcf7711 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -200,7 +200,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, } if (filename.indexOf(FPSTR(s_wsec)) >= 0) { - request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // skip wsec.json + if (isFinal) request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // block wsec.json return; } From 25c1d67bca975f55d8017029bfa7c7353616696e Mon Sep 17 00:00:00 2001 From: foamyguy Date: Tue, 7 Apr 2026 19:40:01 -0500 Subject: [PATCH 4/4] user control for disable wsec.json upload --- wled00/cfg.cpp | 4 ++++ wled00/data/settings_sec.htm | 3 ++- wled00/set.cpp | 1 + wled00/wled.h | 1 + wled00/wled_server.cpp | 2 +- wled00/xml.cpp | 1 + 6 files changed, 10 insertions(+), 2 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d01f83ad54..408708d99a 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -723,6 +723,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (pwdCorrect) { //only accept these values from cfg.json if ota is unlocked (else from wsec.json) CJSON(otaLock, ota[F("lock")]); CJSON(wifiLock, ota[F("lock-wifi")]); + CJSON(denyWsecUpload, ota[F("deny-wsec")]); #ifndef WLED_DISABLE_OTA CJSON(aOtaEnabled, ota[F("aota")]); #endif @@ -1228,6 +1229,7 @@ void serializeConfig(JsonObject root) { JsonObject ota = root.createNestedObject("ota"); ota[F("lock")] = otaLock; ota[F("lock-wifi")] = wifiLock; + ota[F("deny-wsec")] = denyWsecUpload; ota[F("pskl")] = strlen(otaPass); #ifndef WLED_DISABLE_OTA ota[F("aota")] = aOtaEnabled; @@ -1303,6 +1305,7 @@ bool deserializeConfigSec() { getStringFromJson(otaPass, ota[F("pwd")], 33); CJSON(otaLock, ota[F("lock")]); CJSON(wifiLock, ota[F("lock-wifi")]); + CJSON(denyWsecUpload, ota[F("deny-wsec")]); #ifndef WLED_DISABLE_OTA CJSON(aOtaEnabled, ota[F("aota")]); #endif @@ -1345,6 +1348,7 @@ void serializeConfigSec() { ota[F("pwd")] = otaPass; ota[F("lock")] = otaLock; ota[F("lock-wifi")] = wifiLock; + ota[F("deny-wsec")] = denyWsecUpload; #ifndef WLED_DISABLE_OTA ota[F("aota")] = aOtaEnabled; #endif diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index b52e9a15e0..428e678380 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -55,7 +55,8 @@

Security & Update Setup

The password should be changed when OTA is enabled.
Disable OTA when not in use, otherwise an attacker can reflash device software!
Settings on this page are only changeable if OTA lock is disabled!
- Deny access to WiFi settings if locked:

+ Deny access to WiFi settings if locked:
+ Deny uploading wsec.json file:

Factory reset:
All settings and presets will be erased.

⚠ Unencrypted transmission. An attacker on the same network can intercept form data!
diff --git a/wled00/set.cpp b/wled00/set.cpp index 925f273aee..02d98ab687 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -663,6 +663,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) { otaLock = request->hasArg(F("NO")); wifiLock = request->hasArg(F("OW")); + denyWsecUpload = request->hasArg(F("OU")); #ifndef WLED_DISABLE_OTA aOtaEnabled = request->hasArg(F("AO")); #endif diff --git a/wled00/wled.h b/wled00/wled.h index adc5c3d4e7..1632d33f06 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -576,6 +576,7 @@ WLED_GLOBAL bool otaLock _INIT(true); // prevents OTA firmware update WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks #endif WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled +WLED_GLOBAL bool denyWsecUpload _INIT(false); // when true, POST /upload refuses to overwrite wsec.json #ifdef WLED_ENABLE_AOTA WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on #else diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index cfcfcf7711..47163ee9ee 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -199,7 +199,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, return; } - if (filename.indexOf(FPSTR(s_wsec)) >= 0) { + if (denyWsecUpload && filename.indexOf(FPSTR(s_wsec)) >= 0) { if (isFinal) request->send(403, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_accessdenied)); // block wsec.json return; } diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 0d3468f9ea..c8635dae16 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -641,6 +641,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("PIN"),fpass); printSetFormCheckbox(settingsScript,PSTR("NO"),otaLock); printSetFormCheckbox(settingsScript,PSTR("OW"),wifiLock); + printSetFormCheckbox(settingsScript,PSTR("OU"),denyWsecUpload); printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled); printSetFormCheckbox(settingsScript,PSTR("SU"),otaSameSubnet); char tmp_buf[128];