fix: Address P2 security audit findings

- VULN-012: Split STATUS into minimal (unauthed: hostname, uptime,
  rssi, version, motion, presence) and full (authed: all internals,
  build info, target IP, heap, NVS stats)
- VULN-011: Remove mDNS service advertisement and hardcoded "ESP32 CSI
  Sensor" instance name; use hostname only
- VULN-021: Increase HMAC tag from 64 bits (16 hex) to 128 bits
  (32 hex) — BREAKING: client scripts must update HMAC computation
- VULN-023: Enable PMF (802.11w) in sdkconfig.defaults to prevent
  deauth attacks at protocol level
This commit is contained in:
user
2026-02-14 20:10:14 +01:00
parent ed8669c0af
commit 57927c7c22
2 changed files with 47 additions and 29 deletions

View File

@@ -1455,8 +1455,8 @@ static struct {
static int s_auth_nonce_idx = 0;
/**
* Verify HMAC-signed command. Format: "HMAC:<16hex>:<uptime_s>:<cmd>"
* HMAC = truncated SHA-256(secret, "<uptime_s>:<cmd>")
* Verify HMAC-signed command. Format: "HMAC:<32hex>:<uptime_s>:<cmd>"
* HMAC = first 16 bytes of SHA-256(secret, "<uptime_s>:<cmd>") as 32 hex chars
* Timestamp must be within 5s of device uptime (replay protection).
* Recently used timestamp+HMAC pairs are cached to reject exact replays.
* Returns pointer to actual command on success, or NULL on failure
@@ -1475,18 +1475,18 @@ static const char *auth_verify(const char *input, char *reply, size_t reply_size
return NULL;
}
/* Parse: HMAC:<16 hex chars>:<uptime_s>:<cmd> */
if (strlen(input) < 5 + 16 + 1) {
/* Parse: HMAC:<32 hex chars>:<uptime_s>:<cmd> */
if (strlen(input) < 5 + 32 + 1) {
snprintf(reply, reply_size, "ERR AUTH malformed");
return NULL;
}
if (input[5 + 16] != ':') {
if (input[5 + 32] != ':') {
snprintf(reply, reply_size, "ERR AUTH malformed");
return NULL;
}
/* payload = "<uptime_s>:<cmd>" */
const char *payload = input + 5 + 16 + 1;
const char *payload = input + 5 + 32 + 1;
const char *cmd_sep = strchr(payload, ':');
if (!cmd_sep) {
snprintf(reply, reply_size, "ERR AUTH malformed (need timestamp:cmd)");
@@ -1515,15 +1515,15 @@ static const char *auth_verify(const char *input, char *reply, size_t reply_size
mbedtls_md_hmac_finish(&ctx, hmac);
mbedtls_md_free(&ctx);
/* Format first 8 bytes as 16 hex chars */
char expected[17];
for (int i = 0; i < 8; i++) {
/* Format first 16 bytes as 32 hex chars (128-bit tag) */
char expected[33];
for (int i = 0; i < 16; i++) {
snprintf(expected + i * 2, 3, "%02x", hmac[i]);
}
/* Constant-time comparison (prevents timing side-channel) */
volatile uint8_t diff = 0;
for (int i = 0; i < 16; i++) {
for (int i = 0; i < 32; i++) {
diff |= (uint8_t)input[5 + i] ^ (uint8_t)expected[i];
}
if (diff != 0) {
@@ -1703,7 +1703,7 @@ static void powertest_task(void *arg)
vTaskDelete(NULL);
}
static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
static int cmd_handle(const char *cmd, char *reply, size_t reply_size, bool authed)
{
/* REBOOT */
if (strncmp(cmd, "REBOOT", 6) == 0) {
@@ -1744,13 +1744,21 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
return strlen(reply);
}
/* STATUS */
/* STATUS — minimal without auth, full with auth */
if (strncmp(cmd, "STATUS", 6) == 0) {
int64_t up = esp_timer_get_time() / 1000000LL;
int days = (int)(up / 86400);
int hours = (int)((up % 86400) / 3600);
int mins = (int)((up % 3600) / 60);
uint32_t heap = esp_get_free_heap_size();
char uptime_str[32];
if (days > 0) {
snprintf(uptime_str, sizeof(uptime_str), "%dd%dh%dm", days, hours, mins);
} else if (hours > 0) {
snprintf(uptime_str, sizeof(uptime_str), "%dh%dm", hours, mins);
} else {
snprintf(uptime_str, sizeof(uptime_str), "%dm", mins);
}
wifi_ap_record_t ap;
int rssi = 0;
@@ -1762,6 +1770,20 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
const esp_app_desc_t *app_desc = esp_app_get_description();
if (!authed) {
/* Minimal: no build info, no target, no internals */
snprintf(reply, reply_size,
"OK STATUS hostname=%s uptime=%s uptime_s=%lld rssi=%d channel=%d"
" version=%s motion=%d presence=%d",
s_hostname, uptime_str, (long long)up, rssi, channel,
app_desc->version, s_motion_detected ? 1 : 0,
s_presence_detected ? 1 : 0);
return strlen(reply);
}
/* Full status (authenticated) */
uint32_t heap = esp_get_free_heap_size();
float chip_temp = 0.0f;
#if SOC_TEMP_SENSOR_SUPPORTED
if (s_temp_handle) {
@@ -1771,15 +1793,6 @@ static int cmd_handle(const char *cmd, char *reply, size_t reply_size)
int actual_rate = (up > 0) ? (int)((uint64_t)s_csi_count / (uint64_t)up) : 0;
char uptime_str[32];
if (days > 0) {
snprintf(uptime_str, sizeof(uptime_str), "%dd%dh%dm", days, hours, mins);
} else if (hours > 0) {
snprintf(uptime_str, sizeof(uptime_str), "%dh%dm", hours, mins);
} else {
snprintf(uptime_str, sizeof(uptime_str), "%dm", mins);
}
const char *csi_mode_str = (s_csi_mode == CSI_MODE_COMPACT) ? "compact" :
(s_csi_mode == CSI_MODE_HYBRID) ? "hybrid" : "raw";
@@ -2596,8 +2609,8 @@ static void cmd_task(void *arg)
rx_buf[len] = '\0';
/* Log command (redact HMAC token) */
if (strncmp(rx_buf, "HMAC:", 5) == 0 && strlen(rx_buf) > 22) {
ESP_LOGI(TAG, "CMD rx: HMAC:****:%s", rx_buf + 22);
if (strncmp(rx_buf, "HMAC:", 5) == 0 && strlen(rx_buf) > 38) {
ESP_LOGI(TAG, "CMD rx: HMAC:****:%s", rx_buf + 38);
} else {
ESP_LOGI(TAG, "CMD rx: \"%s\"", rx_buf);
}
@@ -2622,7 +2635,7 @@ static void cmd_task(void *arg)
} else if (!authed && cmd_requires_auth(cmd)) {
reply_len = snprintf(reply_buf, sizeof(reply_buf), "ERR AUTH required");
} else {
reply_len = cmd_handle(cmd, reply_buf, sizeof(reply_buf));
reply_len = cmd_handle(cmd, reply_buf, sizeof(reply_buf), authed);
}
sendto(sock, reply_buf, reply_len, 0,
(struct sockaddr *)&src_addr, src_len);
@@ -2726,12 +2739,11 @@ void app_main()
}
#endif
/* mDNS: announce as <hostname>.local with _esp-csi._udp service */
/* mDNS: announce as <hostname>.local — generic service type to reduce fingerprinting */
ESP_ERROR_CHECK(mdns_init());
mdns_hostname_set(s_hostname);
mdns_instance_name_set("ESP32 CSI Sensor");
mdns_service_add(NULL, "_esp-csi", "_udp", s_target_port, NULL, 0);
ESP_LOGI(TAG, "mDNS hostname: %s.local (_esp-csi._udp:%d)", s_hostname, s_target_port);
mdns_instance_name_set(s_hostname);
ESP_LOGI(TAG, "mDNS hostname: %s.local", s_hostname);
/* Watchdog: 30s timeout, auto-reboot on hang */
esp_task_wdt_config_t wdt_cfg = {

View File

@@ -87,3 +87,9 @@ CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
# WiFi Authentication (reject open/WEP APs)
#
CONFIG_EXAMPLE_WIFI_AUTH_WPA2_WPA3_PSK=y
#
# Protected Management Frames (802.11w) — prevent deauth attacks
#
CONFIG_ESP_WIFI_PMF_ENABLED=y
CONFIG_ESP_WIFI_PMF_REQUIRED=y