Executed non-invasive pentest against amber-maple (v1.12-dev): - Phase 1: mDNS, port scan, binary analysis, eFuse readout - Phase 2: HMAC timing, command injection (27 tests), replay (6 tests) - Phase 3: NVS analysis, CVE check (12 CVEs), binary structure All network-facing tests PASS. Physical security gaps documented.
7.9 KiB
7.9 KiB
Pentest Results — ESP32 CSI Sensor Firmware
Date: 2026-02-14 Target: amber-maple (192.168.129.30), ESP32-D0WD-V3 rev 3.1 Firmware: v1.11.0-11-ga4bd2a6-dirty (v1.12-dev security hardening) ESP-IDF: v5.5.2 Tester: Raspberry Pi 5 (192.168.129.11), same LAN
Results Matrix
Phase 1: Passive Reconnaissance
| # | Test | Status | Notes |
|---|---|---|---|
| 1a | mDNS service discovery | PASS | No _esp-csi._udp service advertised. Only hostname.local resolves. |
| 1b | Port scan (UDP) | PASS | Only 5353/udp (mDNS) and 5501/udp (cmd) open. 5500/udp closed (outbound-only). |
| 1b | Port scan (TCP) | PASS | All 1000 TCP ports closed. Zero TCP attack surface. |
| 1c | Firmware strings | PASS | No hardcoded secrets, passwords, or embedded private keys. |
| 1c | Firmware strings | WARN | Hardcoded default IP 192.168.129.11 and hostname amber-maple in binary. |
| 1c | Entropy analysis | INFO | Firmware not flash-encrypted (low entropy). Expected for dev boards. |
| 1c | PEM markers | INFO | mbedTLS parser constants only, not actual embedded keys. |
| 1d | eFuse: JTAG | WARN | JTAG_DISABLE = False — JTAG debug accessible via GPIO 12-15. |
| 1d | eFuse: Secure Boot | WARN | ABS_DONE_0 = False, ABS_DONE_1 = False — no secure boot. |
| 1d | eFuse: Flash Encryption | WARN | FLASH_CRYPT_CNT = 0 — flash encryption disabled. |
| 1d | eFuse: Download Mode | WARN | DISABLE_DL_ENCRYPT/DECRYPT/CACHE = False — UART download mode open. |
| 1d | eFuse: Coding Scheme | INFO | CODING_SCHEME = NONE — full 256-bit key space available. |
Phase 2: Network Protocol Analysis
| # | Test | Status | Notes |
|---|---|---|---|
| 2a | Unauth STATUS info leak | PASS | Minimal response (hostname, uptime, rssi, channel, version). No secrets. |
| 2a | CONFIG info disclosure | PASS | Auth secret not exposed in unauthed CONFIG response. |
| 2a | HMAC on wire | INFO | HMAC tags visible in plaintext UDP (expected — no DTLS). |
| 2a | HELP disclosure | INFO | Command list visible without auth (by design). |
| 2b | HMAC timing oracle | PASS | Median diff 144us between test cases — within WiFi jitter. Constant-time comparison effective. |
| 2c | Null byte injection | PASS | All 17 unauthed injection payloads handled safely. |
| 2c | Format string injection | PASS | %x%x%n payloads rejected; no stack leak. |
| 2c | Newline/CRLF injection | PASS | Rejected by auth requirement. |
| 2c | Oversized packets | PASS | MTU-sized and 191-byte packets rejected gracefully. |
| 2c | Binary garbage | PASS | Random byte payloads handled without crash. |
| 2c | HOSTNAME format string | PASS | Authed: format strings rejected (timeout, likely sanitized). |
| 2c | HOSTNAME oversized | PASS | ERR HOSTNAME length 1-31 — proper validation. |
| 2c | HOSTNAME bad chars | PASS | ERR HOSTNAME chars: a-z 0-9 - — strict allowlist. |
| 2c | TARGET format string | PASS | ERR TARGET invalid IP — proper validation. |
| 2c | RATE boundary values | PASS | All out-of-range values rejected: ERR RATE range 10-100. |
| 2d | Valid HMAC command | PASS | Authed STATUS returns full response. |
| 2d | Immediate replay | PASS | ERR AUTH replay rejected — nonce dedup works. |
| 2d | Expired timestamp (-10s) | PASS | ERR AUTH expired (drift=10s) — window enforced. |
| 2d | Future timestamp (+10s) | PASS | ERR AUTH expired (drift=-10s) — window enforced. |
| 2d | Wrong secret | PASS | ERR AUTH failed — incorrect HMAC rejected. |
| 2d | Nonce cache overflow (9 cmds) | PASS | Replay still rejected after flooding cache with 9 unique commands. |
Phase 3: Static Analysis
| # | Test | Status | Notes |
|---|---|---|---|
| 3a | NVS auth_secret storage | FAIL | Stored as plaintext string: 86bd963b07858d5b10db839d55b409df at offset 0x1ba8. |
| 3a | NVS WiFi credentials | PASS | No WiFi SSID/password found in NVS dump (compiled-in via sdkconfig). |
| 3a | NVS integrity | WARN | 2 of 6 pages have invalid CRC (uninitialized, not corruption). |
| 3a | Boot history | INFO | boot_count = 25 visible — leaks reboot frequency. |
| 3b | CVE exposure (12 checked) | PASS | 8 CVEs not applicable (unused components). 4 LOW risk (BLE scan-only mitigates). |
| 3b | CVE-2025-27840 (HCI cmds) | LOW | Hidden HCI commands; mitigated by scan-only BLE mode. |
| 3b | Unused #include "esp_now.h" |
INFO | Dead include — remove to avoid link to CVE-2025-52471. |
| 3c | Stack canaries | FAIL | CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y — no stack overflow protection. |
| 3c | Heap poisoning | WARN | CONFIG_HEAP_POISONING_DISABLED=y — no heap corruption detection. |
| 3c | PMF configuration | PASS | CONFIG_ESP_WIFI_PMF_REQUIRED=y — 802.11w enforced. |
| 3c | Debug symbols in ELF | INFO | ELF has full debug info (11.9 MB). .bin for OTA is stripped. |
| 3c | cmd_task size | INFO | 9,877 bytes compiled — large function handling untrusted input. |
| 3c | Watchdog | PASS | Bootloader, interrupt, and task WDTs all enabled. |
Risk Assessment
Critical (requires physical access)
| Risk | Impact | Mitigation |
|---|---|---|
| No flash encryption | Auth secret + WiFi creds readable from flash | Enable CONFIG_FLASH_ENCRYPTION_ENABLED (irreversible eFuse) |
| No secure boot | Arbitrary firmware flashable via UART/OTA | Enable Secure Boot V2 (irreversible eFuse) |
| JTAG enabled | Live memory inspection, breakpoints | Burn JTAG_DISABLE eFuse (irreversible) |
| NVS plaintext | auth_secret in cleartext NVS |
Flash encryption covers this |
Medium (network-accessible)
| Risk | Impact | Mitigation |
|---|---|---|
| No DTLS | HMAC tokens visible to LAN sniffers | Implement DTLS for command channel |
| No stack canaries | Buffer overflow in cmd_task could be exploitable | Enable CONFIG_COMPILER_STACK_CHECK_MODE_NORM |
| OTA over HTTP | MITM firmware injection on LAN | Embed CA cert, enforce HTTPS OTA |
Low / Informational
| Risk | Impact | Mitigation |
|---|---|---|
| Hardcoded default IP | Network topology leak in binary | Move to Kconfig or NVS-only default |
| Version string leaks git hash | Aids targeted attacks | Use clean tag-only version strings |
| HELP visible without auth | Command enumeration | By design — acceptable |
| Uptime in unauthed STATUS | Aids HMAC timestamp prediction | Already in minimal STATUS by design |
Security Hardening Scorecard
| Category | Score | Notes |
|---|---|---|
| Authentication | 9/10 | HMAC-SHA256, replay protection, nonce cache, rate limiting. Only gap: no mutual auth. |
| Input Validation | 10/10 | All 27 injection tests passed. Strict allowlists on HOSTNAME, RATE, TARGET, POWER. |
| Network Exposure | 8/10 | Minimal ports, PMF required, service ads removed. Gap: plaintext UDP. |
| Physical Security | 2/10 | No secure boot, no flash encryption, JTAG open, UART download mode open. |
| Binary Security | 4/10 | No stack canaries, no heap poisoning. WDTs present. |
| CVE Exposure | 9/10 | Minimal attack surface; unused components disabled; v5.5.2 patches applied. |
Overall: Strong network security, weak physical security. The firmware is well-hardened against remote/network attacks. Physical access remains the primary threat vector.
Recommendations (Priority Order)
- P1 — Enable stack canaries:
CONFIG_COMPILER_STACK_CHECK_MODE_NORM=y - P1 — Enable heap poisoning:
CONFIG_HEAP_POISONING_LIGHT=y - P2 — Enable WDT panic:
CONFIG_ESP_TASK_WDT_PANIC=y - P2 — Remove unused
#include "esp_now.h" - P2 — Remove hardcoded default IP from binary
- P3 — Flash encryption (requires eFuse planning)
- P3 — Secure Boot V2 (requires eFuse planning)
- P3 — DTLS for command channel (significant effort)
- P3 — OTA certificate pinning