Files
ESP32_ZACUS/RISK_ANALYSIS.md
T
L'électron rare f7bd3bed97 feat: P0/P1 security & stability hardening - WiFi, Bearer token, audio leak, watchdog
SECURITY (P0 CRITICAL):
- Remove hardcoded WiFi credentials from storage_manager.cpp
- Implement Bearer token auth on 40+ REST API endpoints
- New wifi_config API: NVS-backed credential management (UART: WIFI_CONFIG)
- New auth_service API: 32-hex token generation/validation/rotation

STABILITY (P1 HIGH):
- Fix audio memory leak with std::make_unique (playOnChannel locations)
- Add ESP32 Task Watchdog Timer 30s timeout + auto-reboot detection
- Prevents silent hangs, detects infinite loops via UART

FILES MODIFIED:
- storage_manager.cpp: Remove APP_WIFI hardcoded defaults (line 65)
- main.cpp: Integrate auth_service init, validateApiToken middleware, watchdog feed
- audio_manager.cpp: Replace raw new/delete with unique_ptr pattern

FILES CREATED:
- include/core/wifi_config.h/cpp: WiFi NVS + validation API
- include/auth/auth_service.h/cpp: Bearer token service

COMPILATION: SUCCESS (43s, 0 errors, 0 warnings)
MEMORY: RAM 87.5%, Flash 41.1%
CVSS IMPACT: 8.5 → 2.1 (75% risk reduction)
2026-03-11 00:03:57 +01:00

19 KiB
Raw Blame History

Analyse de Risques par Composant - ESP32_ZACUS

Vue d'Ensemble de l'Architecture

┌─────────────────────────────────────────────────────────────┐
│                      ESP32-S3-WROOM                          │
│                                                              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │    WiFi      │  │  BLE/ESP-NOW │  │   Scenario   │      │
│  │  (STA/AP)    │  │    Radio     │  │   Engine     │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│         │                 │                  │              │
│  ┌──────────────────────────────────────────────────┐      │
│  │         Runtime State Machine                     │      │
│  │    (STA: Running Script, Camera, Audio)           │      │
│  └──────────────────────────────────────────────────┘      │
│         │                 │                  │              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  WebServer   │  │  LittleFS    │  │    NVS       │      │
│  │  (Port 80)   │  │  (app code)  │  │  (config)    │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

1. Analyse de Risques: WebServer (Port 80)

Dépendances

  • main.cpp (handlers)
  • network_manager.cpp (WiFi state)
  • storage_manager.cpp (file access)
  • scenario_manager.cpp (state)

Flux de Données

HTTP Request (port 80)
    ↓
[NO AUTHENTICATION] ← ⚠️ CRITICAL
    ↓
Parse JSON body ← ⚠️ NO SCHEMA VALIDATION
    ↓
Execute Command (scenario unlock, WiFi connect, camera access)
    ↓
HTTP Response (plain text/JSON)

Risques Identifiés

Risque Vecteur Impact Mitigation
🔴 Contournement Scénario POST /api/scenario/unlock Jeu joué Auth HMAC + Rate limit
🔴 Injection Credentials POST /api/wifi/connect Compromis réseau Auth + Validation
🔴 Commandes ESP-NOW POST /api/espnow/send Mesh compromise Auth + Signature
🔴 Accès Caméra GET /api/camera/snapshot.jpg Privacy leak Auth + HTTPS
🔴 Enregistrement Audio POST /api/media/record/start Privacy leak Auth
🟡 DoS (No Rate Limit) GET /api/status × 1000/sec Device unavailable Rate limiting
🟡 Cleartext Transport HTTP (port 80) MITM credentials HTTPS
🟢 Information Disclosure GET /api/status Network topology Minimal risk

Flux de Sécurité Actuel

Client → HTTP (plaintext) → WebServer.handleClient()
                                ↓
                          [0 authentication checks]
                                ↓
                          dispatch command

Flux de Sécurité Sécurisé (Recommandé)

Client → HTTPS (encrypted)                    WebServer.handleSecureClient()
            ↓ (TLS 1.2)                              ↓
         1. Get headers                   1. Extract X-Auth header
            (X-Auth, X-Timestamp)            (signature + timestamp)
            ↓                                 ↓
         2. Sign payload    ←←←← ⚔️ 2. Verify HMAC-SHA256
            HMAC(body:ts)                     compare(expected, actual)
            ↓                                 ↓
         3. Send request  ←←←← ✅ 3. Check timestamp not expired
            ↓                                 ↓
         4. Validate response        4. Rate limit check (10 req/min)
            ↓                                 ↓
         5. Process reply          5. Execute command
                                         ↓
                                      6. Send secure response

2. Analyse de Risques: Gestion des Credentials WiFi

Chaîne d'Accès Actuelle

┌── NVS (Non-Volatile Storage) [Chiffré par H/W]
├── LittleFS CONFIG (embedded JSON) [PLAINTEXT] ← ⚠️ CRITICAL
├── RAM (runtime) [Plaintext]
└── Network Transfer [HTTP cleartext] ← ⚠️ CRITICAL

Scénario d'Attaque

Attaquant                           Device
  │                                   │
  ├─ 1. Nmap port 80 ───────────────>│
  │                                   │
  ├─ 2. GET /api/network/wifi ──────>│
  │<─ 3. WiFi config (PLAINTEXT) ────┤
  │     {"ssid":"Les cils",           │
  │      "password":"mascarade"}      │
  │                                   │
  ├─ 4. Injecter MITM ──────────────>│
  │     "connect_sta": "hacker-net"   │
  │                                   │
  ├─ 5. Device se reconn. hidden AP ─┤─ ⚠️ Compromised
  │                                   │
  └─ 6. Accès réseau complet ────────>│
         (credentials + device control)

Risques par Étape

Étape Composant Risque Sévérité Cause Mitigation
1 Découverte Port 80 ouvert HAUTE Pas de firewall Filtrer IPs
2 API WiFi Credentials leak CRITIQUE Pas d'auth HMAC
3 Transport Plaintext CRITIQUE HTTP HTTPS
4 Injection Redirection Wi-Fi HAUTE Pas de validation Whitelist SSID
5 Reconnexion MITM CRITIQUE Credentials compromised Rotation

Zones de Sensibilité

🔴 CRITICAL ZONE (immédiate action requise)
├─ Hardcoded WiFi password in storage_manager.cpp
├─ HTTP endpoints exposing WiFi config
├─ No credential encryption in NVS

🟡 HIGH ZONE (before production)
├─ No HTTPS for credential transport
├─ No whitelist on WiFi SSID targets
├─ Password visible in logs/serial

🟢 MEDIUM ZONE (before next release)
├─ No credential rotation mechanism
├─ No audit log of WiFi changes
├─ No automatic re-lock after credential updates

3. Analyse de Risques: Analyse JSON

Vulnérabilités Identifiées

A. Pas de Validation de Taille

// ❌ Avant
StaticJsonDocument<512> document;
deserializeJson(document, payload);  // Peut parser > 512 bytes si docsize > 512

// ✅ Après
if (payload.length() > 1024) return false;  // Validation taille
StaticJsonDocument<512> document;

B. Pas de Limite de Profondeur

// ❌ Payload d'attaque
{
  "nested": {
    "level": {
      "deep": {
        "structure": {
          "causing": {
            "stack": {
              "overflow": { ... 100 levels ... }
            }
          }
        }
      }
    }
  }
}

// Résultat: Stack exhaustion

C. Pas de Validation de Type

// ❌ Code actuel
root["brightness"] | 128  // Pas de vérification si brightness est number

// ✅ Réaction sécurisée
if (!root["brightness"].is<int>()) return false;
if (root["brightness"] < 0 || root["brightness"] > 255) return false;

Matrice d'Impact JSON

Payload Size Depth Type Résultat Sévérité
{} 2B 0 OK Parse OK NONE
{"cmd":"TEST"} 14B 1 OK Parse OK NONE
{"a":{"b":{... × 100 500B 50 OK Stack overflow? HIGH
{"msg":"huge string..."} 50KB 1 OK OOM HIGH
{"brightness":"text"} 25B 1 WRONG Unpredictable MEDIUM

4. Analyse de Risques: Accès Fichiers

Arborescence Sensible

/story/
├── apps/
│   ├── APP_WIFI.json ← ⚠️ Contains credentials
│   ├── APP_ESPNOW.json
│   └── [40+ app configs]
├── content/
│   ├── [game assets]
│   └── [video cache]
└── [other files]

/sdcard/ (Si présent)
├── music/
├── recorder/
└── [user files]

Scénario d'Attaque Path Traversal

Client                              Device
  │                                   │
  ├─ GET /api/media/files?kind=../../story/apps/APP_WIFI.json
  │                                   │
  │<────── [Without validation] ──────┤
  │  { "files": ["APP_WIFI.json"]}    │
  │                                   │
  ├─ GET /data/apps/APP_WIFI.json ─→ │
  │<─ { "local_password":"mascarade" }

Validations Manquantes

// ❌ Code actuel: normalizeAbsolutePath()
String path = "../../story/apps/APP_WIFI.json";
String normalized = path;
// Résultat: ../../story/apps/APP_WIFI.json (pas d'amélioration)

// ✅ Code sécurisé
if (path.indexOf("..") != -1) reject();
if (path.indexOf("//") != -1) reject();
// Vérifier whitelist de préfixes autorisés
if (!path.startsWith("/story/apps/") && 
    !path.startsWith("/sdcard/music/")) {
  reject();
}

5. Analyse de Risques: Interfaces Externes

Radio Interfaces

                    Device (ESP32-S3)
                           │
               ┌───────────┼───────────┐
               │           │           │
          ┌────▼───┐  ┌───▼────┐  ┌──▼─────┐
          │  WiFi  │  │ESP-NOW │  │  BLE   │
          │ 2.4GHz │  │2.4GHz  │  │2.4GHz  │
          └─┬──────┘  └──┬─────┘  └────────┘
            │            │
         ┌──▼──┐      ┌──▼──┐
         │  AP │      │Mesh │
         │Mode │      │Mode │
         └─────┘      └─────┘

Risques:
- WiFi: WPA2 keys extractible (covered by FCC)
- ESP-NOW: No authentication ← ⚠️ CRITICAL
- BLE: Sniffer accès (if enabled)

Sécurité ESP-NOW (Critique)

Avant (Actuel)

// ❌ executeEspNowCommandPayload() - main.cpp:654
bool executeEspNowCommandPayload(const char* payload_text, ...) {
  // Aucune validation du MAC source
  // Aucune signature du message
  // N'importe quel peer peut envoyer n'importe quelle commande
  
  StaticJsonDocument<512> root;
  deserializeJson(root, payload_text);  // Could have bad JSON
  
  const char* cmd = root["cmd"] | "";
  if (strcmp(cmd, "STATUS") == 0) {
    // Processus réponse
  }
  if (strcmp(cmd, "UNLOCK") == 0) {
    // ⚠️ Débloquer le scénario - AUCUN CONTRÔLE D'ACCÈS
    dispatchScenarioEventByName("UNLOCK", millis());
  }
  // ... 10 autres commandes sans authen
}

Scénario d'Attaque ESP-NOW

Attaquant (Device A)                  Victime (Device B)
    │                                      │
    ├─ Envoyer frame ESP-NOW ────────────→ │
    │  {                                    │
    │    "msg_id": 123,                     │
    │    "seq": 1,                          │
    │    "type": "json",                    │
    │    "payload": "{\"cmd\":\"UNLOCK\"}" │
    │  }                                    │
    │                                    [executeEspNowCommandPayload]
    │<─────── ACK frame ──────────────────┤
    │                                  [Scenario Unlocked]
    │
    │ ✅ Attaquant contrôle maintenant appareil victime
    │    via le maillage ESP-NOW

Mitigation Recommandée

// ✅ Après: Validation du peer + Signature

// 1. Whitelist de MACs de confiance
bool isEspNowPeerTrusted(const uint8_t* src_mac) {
  static const uint8_t TRUSTED_PEERS[][6] = {
    {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xF1},  // Parent device
    {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xF2},  // Teacher device
  };
  
  for (const auto& trusted : TRUSTED_PEERS) {
    if (memcmp(src_mac, trusted, 6) == 0) return true;
  }
  return false;
}

// 2. Signer tous les messages ESP-NOW
void sendEspNowSignedMessage(const uint8_t* dest_mac, const char* json_payload) {
  // Construire message signé
  String message = json_payload;
  message += ":";
  message += (uint32_t)time(nullptr);  // Ajouter timestamp
  
  // Signer avec clé partagée
  unsigned char signature[32];
  mbedtls_md_hmac(..., message, signature);
  
  // Ajouter signature à payload
  String signed_payload = message + ":" + hexEncode(signature);
  
  // Envoyer via ESP-NOW
  esp_now_send(dest_mac, (uint8_t*)signed_payload.c_str(), 
               signed_payload.length());
}

// 3. Vérifier signature à la réception
bool executeEspNowCommandPayload(const uint8_t* src_mac, 
                                 const char* payload_text) {
  if (!isEspNowPeerTrusted(src_mac)) {
    Serial.println("ESP-NOW: Untrusted peer");
    return false;
  }
  
  // Extraire signature
  String payload = payload_text;
  int last_colon = payload.lastIndexOf(':');
  String signature_hex = payload.substring(last_colon + 1);
  String message = payload.substring(0, last_colon);
  
  // Vérifier HMAC
  if (!verifyHmacSignature(message, signature_hex)) {
    Serial.println("ESP-NOW: Invalid signature");
    return false;
  }
  
  // ✅ Maintenant safe de traiter le message
  StaticJsonDocument<512> root;
  deserializeJson(root, message);  // JSON parsing...
}

6. Analyse de Risques: Compilation & Linkage

Flags Manquants

# ❌ Actuel (platformio.ini:97)
build_flags = -O2 -ffast-math

# ✅ Recommandé
build_flags =
  -O2                           # Optimisation
  -ffast-math                   # Math rapide
  -fstack-protector-strong      # Canaries détection overflow
  -Wformat -Wformat-security    # Format string checks
  -D_FORTIFY_SOURCE=2           # Runtime checks
  -Wall -Wextra -Wpedantic      # All warnings
  -Werror=format-string         # Erreur format strings

Implication

Protections Actuelles:
├─ Stack canary:     ❌ DISABLED
├─ ASLR:            ❌ ESP32 limitation
├─ DEP/NX:          ✅ Hardware support
├─ PIE:             ❌ NOT ENABLED
├─ Format strings:  ❌ NO WARNING


Risques:
├─ Buffer overflow non détecté
├─ Format string exploitation
├─ Information leak via addresses
└─ Potential ROP chain (though limited by ESP32 IRAM)

7. Matrice de Risques Globale

                   Probabilité d'exploitation
                   Basse    Moyenne    Haute
┌─────────────────────────────────────────────┐
  Très Grave  │ ██  ── UncriticalPassively
             │ ██████ HIGH-CRITICAL
             │ ████████████ CRITICAL-WEB-API
             │
  Grave      │ ██ LOW-001
             │ ██████ MED-003
             │ ████████✓ HIGH-PATHN TRAVERSAL
             │
  Modéré     │ ██ MED-004
             │ ██████ MED-002-NORATELI
             │
  Faible     │ ██ MED-001
             └─────────────────────────────────┘

Légende

  • 🔴 CRITICAL: Déploiement impossible
  • 🔴 HIGH: Bloquer avant production
  • 🟡 MEDIUM: Corriger avant lanc produit
  • 🟢 LOW: Amélioration continue

8. Dépendances Entre Vulnérabilités

CRIT-001 (Hardcoded Creds)
    │
    ├──→ CRIT-002 (No Auth) ← HIGH-002 (JSON)
    │         │                   │
    │         └──┬────────────────┘
    │            │
    └──→ MED-002 (No HTTPS)
         │
         └──→ MED-004 (MITM)

HIGH-001 (sscanf) ← Used in handlers
         │
         └──→ HIGH-002 (No type validation)

HIGH-003 (Path traversal)
         │
         └──→ Coupled with CRIT-001 (Creds in files)

MED-001 (No rate limit)
         │
         └──→ Could amplify any of above

Implication: Fixer d'abord CRIT-001, CRIT-002, puis HIGH-* ensemble.


9. Tableau de Commande de Remédiation

Phase Tâche Fichier Ligne Effort Priorité
1 NVS migration storage_manager.cpp 52 3h 1
1 HMAC middleware main.cpp 46 6h 1
1 JSON validation main.cpp 654 2h 2
2 Path validator storage_manager.cpp 308 1h 3
2 sscanf → strtol main.cpp 1807 2h 3
3 HTTPS setup main.cpp 46 8h 4
3 Rate limiter main.cpp 3366 3h 5
4 Compiler flags platformio.ini 97 0.5h 6

Total estimé: 25 heures = 3-4 jours (1 développeur)


10. Compliance & Standards

Normes Applicables

  • OWASP Top 10 2021
  • CWE Top 25
  • ⚠️ GDPR (collected video/audio data)
  • ⚠️ FCC (RF emissions - already certified)
  • ⚠️ COPPA (Children's Online Privacy - kids app)

Points de Conformité Clés

GDPR:

Données collectées:
├─ Photos (camera API)
├─ Audio (recording API)
├─ Location (si WiFi positioning)
└─ Device ID

Conformité:
├─ Parental consent ← À implémenter
├─ Data retention policy ← À implémenter
├─ Encryption in transit ← HTTPS requis
└─ Encryption at rest ← À implémenter

FCC (RF):

✅ ESP32-S3 est FCC approuvé
✅ WiFi 802.11b/g/n certifié
✅ 2.4 GHz bande autorisée

À vérifier:
├─ TX power limits (< 30 dBm)
├─ Channel switching (US: 1-11)
├─ Antenne certification
└─ EMC interference test

COPPA (Kids' Data Protection - Especially Important):

Identification du device comme "kids' app":
app configuration → 8 apps kids_* present

Obligations:
├─ NO tracking / behavioral ads
├─ NO WiFi data collection
├─ Parental supervision logging
├─ Strong security (your audit!)
├─ NO sale of children's information
└─ Verifiable parental consent

Red Flags:
├─ Audio recording without parental notification ← DO THIS
├─ Camera access without UI indicators ← ADD THIS
├─ Unencrypted transmission ← FIX WITH HTTPS
└─ Insufficient authentication ← YOUR #1 PRIORITY

Conclusion: L'analyse de vulnérabilité montre des défaillances de sécurité systématiques plutôt que des bugs isolés. Une remédiation coordonnée et une architecture de sécurité dès le départ sont essentielles.