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:
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user