fix: Harden command auth and prevent remote auth disable

- Flip cmd_requires_auth() from blacklist to whitelist: only read-only
  query commands are unauthenticated, all state-modifying commands now
  require HMAC (VULN-007)
- Block AUTH OFF command to prevent remote auth disable; secret rotation
  still allowed, full reset via FACTORY (VULN-005)
- Redact auth secret in boot log to first 4 chars only (VULN-003)
- Update HELP text to reflect AUTH change
This commit is contained in:
user
2026-02-14 20:01:19 +01:00
parent 476a9beb3b
commit bbe0e3fb21

View File

@@ -352,7 +352,7 @@ static void config_load_nvs(void)
}
s_auth_secret[32] = '\0';
config_save_str("auth_secret", s_auth_secret);
ESP_LOGW(TAG, "AUTH: generated secret: %s (note this for remote access)", s_auth_secret);
ESP_LOGW(TAG, "AUTH: secret generated (%.4s... — retrieve via serial or NVS)", s_auth_secret);
}
/* Boot counter — always increment, even on first boot */
@@ -1512,13 +1512,26 @@ static const char *auth_verify(const char *input, char *reply, size_t reply_size
static bool cmd_requires_auth(const char *cmd)
{
if (strncmp(cmd, "OTA ", 4) == 0) return true;
if (strcmp(cmd, "FACTORY") == 0) return true;
if (strcmp(cmd, "REBOOT") == 0) return true;
if (strncmp(cmd, "TARGET ", 7) == 0) return true;
if (strncmp(cmd, "AUTH ", 5) == 0) return true;
if (strncmp(cmd, "HOSTNAME ", 9) == 0) return true;
return false;
/* Whitelist: read-only / query commands that don't modify state */
if (strcmp(cmd, "STATUS") == 0) return false;
if (strcmp(cmd, "CONFIG") == 0) return false;
if (strcmp(cmd, "PROFILE") == 0) return false;
if (strcmp(cmd, "PING") == 0) return false;
if (strcmp(cmd, "HELP") == 0) return false;
if (strcmp(cmd, "HOSTNAME") == 0) return false;
if (strcmp(cmd, "CSI") == 0) return false;
if (strcmp(cmd, "CSIMODE") == 0) return false;
if (strcmp(cmd, "POWERSAVE") == 0) return false;
if (strcmp(cmd, "PRESENCE") == 0) return false;
if (strcmp(cmd, "CHANSCAN") == 0) return false;
if (strcmp(cmd, "FLOODTHRESH") == 0) return false;
if (strcmp(cmd, "AUTH") == 0) return false;
if (strcmp(cmd, "ALERT") == 0) return false;
if (strcmp(cmd, "LED") == 0) return false;
if (strcmp(cmd, "LOG") == 0) return false;
if (strcmp(cmd, "CALIBRATE STATUS") == 0) return false;
/* Everything else modifies state and requires auth */
return true;
}
/* --- Command handler --- */
@@ -2086,7 +2099,7 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
return strlen(reply);
}
/* AUTH [secret|OFF] */
/* AUTH [secret] — rotate secret (disable not allowed remotely) */
if (strcmp(cmd, "AUTH") == 0) {
snprintf(reply, reply_size, "OK AUTH %s", s_auth_secret[0] ? "on" : "off");
return strlen(reply);
@@ -2094,9 +2107,8 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
if (strncmp(cmd, "AUTH ", 5) == 0) {
const char *arg = cmd + 5;
if (strcmp(arg, "OFF") == 0) {
s_auth_secret[0] = '\0';
config_save_str("auth_secret", "");
snprintf(reply, reply_size, "OK AUTH off");
snprintf(reply, reply_size, "ERR AUTH cannot be disabled remotely (use FACTORY to reset)");
return strlen(reply);
} else {
size_t alen = strlen(arg);
if (alen < 8 || alen > 64) {
@@ -2407,7 +2419,7 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
"BLE [ON|OFF] SCANRATE <5-300> PROBERATE <1-300>\n"
"CALIBRATE [STATUS|CLEAR|N] PRESENCE [ON|OFF|THRESHOLD]\n"
"CHANSCAN [ON|OFF|NOW|INTERVAL] LED [QUIET|AUTO]\n"
"POWERSAVE [ON|OFF] AUTH [secret|OFF] FLOODTHRESH <n> [win]\n"
"POWERSAVE [ON|OFF] AUTH [secret] FLOODTHRESH <n> [win]\n"
"ALERT [TEMP <c>|HEAP <bytes>|OFF] LOG <level> RSSI RESET\n"
"OTA <url> POWERTEST [dwell] IDENTIFY REBOOT FACTORY");
return pos;