33  IoT Security Hands-On Labs

33.1 Learning Objectives

By completing these labs, you will be able to:

  • Implement token-based authentication for IoT device access control on an ESP32
  • Apply rate limiting with exponential backoff to prevent brute force attacks
  • Validate and sanitize commands using whitelists to prevent injection attacks
  • Build anomaly detection systems using Z-score statistical methods
  • Construct security event logging pipelines for monitoring and forensic analysis
In 60 Seconds

IoT security hands-on labs provide the practical experience of implementing, attacking, and defending real IoT systems that is essential for developing genuine security competence — reading about TLS is not the same as configuring certificate pinning on an ESP32 and observing the difference in Wireshark. Each lab translates a security concept into observable evidence, building the intuition needed for real-world security decisions.

This hands-on chapter lets you practice IoT security concepts through exercises and scenarios. Think of it like a fire drill – practicing your response to threats in a safe environment prepares you to handle real incidents effectively. Working through these exercises builds the practical skills needed to secure real IoT deployments.

Key Concepts

  • Protocol analysis: Using packet capture tools (Wireshark, tcpdump) to inspect IoT network traffic, verifying encryption is active, credentials are not transmitted in cleartext, and protocol implementations conform to specifications.
  • Firmware extraction: Obtaining device firmware through official channels, manufacturer tools, or hardware interfaces (JTAG, SPI flash) for analysis of security controls, hardcoded credentials, and vulnerability assessment.
  • Fuzzing: Sending malformed or unexpected inputs to device interfaces (MQTT, HTTP APIs, physical ports) to discover input validation failures that cause crashes, unexpected behaviour, or security vulnerabilities.
  • Certificate pinning: Configuring an IoT device to only trust a specific server certificate (or its public key), preventing man-in-the-middle attacks even if a trusted CA is compromised.
  • Network isolation testing: Verifying that network segmentation controls actually prevent communication between IoT devices and enterprise systems by attempting connections that should be blocked.

33.2 Introduction

Security concepts are best learned by building and breaking things in a controlled environment. This chapter provides two progressive hands-on labs using Wokwi ESP32 simulators. Lab 1 focuses on preventive controls – authentication, rate limiting, and command validation – that block the majority of automated attacks. Lab 2 adds detective controls – anomaly detection and threat level management – that catch sophisticated attacks bypassing static rules. Together, these labs demonstrate the defense-in-depth principle central to IoT security.

33.3 Lab 1: Build a Secure IoT Device with Authentication

This lab demonstrates core IoT security principles using an ESP32 microcontroller. You will implement authentication, rate limiting, and secure command validation.

Rate Limiting Mathematics for IoT Authentication

When implementing rate limiting for IoT device authentication, understanding the quantitative trade-offs is essential:

\[\text{Brute Force Time} = \frac{N}{r}\]

where \(N\) is the keyspace size and \(r\) is the attempt rate (attempts/second).

Example: 4-digit PIN without rate limiting \[T = \frac{10{,}000 \text{ combinations}}{100 \text{ attempts/sec}} = 100 \text{ seconds} \approx 1.7 \text{ minutes}\]

With rate limiting (3 attempts per 30 seconds): \[T = \frac{10{,}000}{3 \text{ attempts} / 30\text{s}} = \frac{10{,}000}{0.1} = 100{,}000 \text{ seconds} \approx 27.8 \text{ hours}\]

Lockout duration calculation (exponential backoff): \[D = k \times 2^{(n-1)}\]

where \(D\) is lockout duration, \(k\) is base duration (30s), and \(n\) is the failure count. In practice, the backoff begins after exceeding a threshold (e.g., after 2 allowed attempts):

Progressive backoff example:

  • Attempts 1-2: No delay (within threshold)
  • Attempt 3: \(30 \times 2^{(3-1)} = 120\) seconds (2 min)
  • Attempt 4: \(30 \times 2^{(4-1)} = 240\) seconds (4 min)
  • Attempt 5: \(30 \times 2^{(5-1)} = 480\) seconds (8 min)

After just 5 failed attempts, the attacker faces an 8-minute delay – making automated brute force attacks economically infeasible.

33.3.1 Lab Objectives

  • Implement token-based authentication
  • Apply rate limiting with exponential backoff
  • Validate commands using whitelists
  • Use visual feedback (LEDs) to indicate security states
  • Log security events for audit trails

33.3.2 Components

Component Purpose Wokwi Element
ESP32 DevKit Main controller with security logic esp32:devkit-v1
Green LED Indicates authenticated/authorized state led:green
Red LED Indicates unauthorized access attempt led:red
Yellow LED Indicates rate limit warning/lockout led:yellow
Push Button Trigger authentication attempt button
Resistors (3x 220 ohm) Current limiting for LEDs resistor

33.3.3 Interactive Wokwi Simulator

33.3.4 Circuit Connections

ESP32 Pin Connections:
---------------------
GPIO 2  --> Green LED (+) --> 220 ohm Resistor --> GND
GPIO 4  --> Red LED (+)   --> 220 ohm Resistor --> GND
GPIO 5  --> Yellow LED (+) --> 220 ohm Resistor --> GND
GPIO 15 --> Push Button --> GND (use internal pullup)

33.3.5 Security Implementation Code

/*
 * Secure IoT Device with Authentication
 * Demonstrates: Token auth, rate limiting, command validation
 */

#include <Arduino.h>

// PIN DEFINITIONS
const int LED_AUTH_OK = 2;      // Green LED - Authorized
const int LED_AUTH_FAIL = 4;    // Red LED - Unauthorized
const int LED_RATE_LIMIT = 5;   // Yellow LED - Rate limited
const int BUTTON_PIN = 15;      // Authentication trigger

// SECURITY CONFIGURATION
const char* VALID_TOKENS[] = {
    "IOT_SEC_TOKEN_2024_A1B2C3",
    "IOT_SEC_TOKEN_2024_D4E5F6",
    "ADMIN_MASTER_KEY_SECURED"
};
const int NUM_VALID_TOKENS = 3;

// Rate limiting configuration
const int MAX_FAILED_ATTEMPTS = 3;
const unsigned long LOCKOUT_DURATION = 30000;  // 30 seconds
const unsigned long MIN_ATTEMPT_INTERVAL = 1000; // 1 second

// Valid commands whitelist
const char* VALID_COMMANDS[] = {
    "STATUS", "LED_ON", "LED_OFF", "GET_TEMP", "RESET"
};
const int NUM_VALID_COMMANDS = 5;

// SECURITY STATE
struct SecurityState {
    int failedAttempts;
    unsigned long lockoutStartTime;
    unsigned long lastAttemptTime;
    bool isLocked;
    bool isAuthenticated;
    String currentToken;
    String sessionId;
} secState;

void setup() {
    Serial.begin(115200);

    pinMode(LED_AUTH_OK, OUTPUT);
    pinMode(LED_AUTH_FAIL, OUTPUT);
    pinMode(LED_RATE_LIMIT, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP);

    // Initialize security state
    secState.failedAttempts = 0;
    secState.isLocked = false;
    secState.isAuthenticated = false;

    Serial.println("Secure IoT Device Ready");
    Serial.println("Commands: AUTH <token>, CMD <command>, LOGOUT");
}

void loop() {
    // Handle button press for quick auth test
    static bool lastButtonState = HIGH;
    bool currentButtonState = digitalRead(BUTTON_PIN);

    if (lastButtonState == HIGH && currentButtonState == LOW) {
        authenticate("IOT_SEC_TOKEN_2024_A1B2C3");
    }
    lastButtonState = currentButtonState;

    // Handle Serial commands
    if (Serial.available()) {
        String input = Serial.readStringUntil('\n');
        input.trim();
        processCommand(input);
    }

    // Handle lockout expiration
    if (secState.isLocked) {
        unsigned long elapsed = millis() - secState.lockoutStartTime;
        if (elapsed >= LOCKOUT_DURATION) {
            secState.isLocked = false;
            secState.failedAttempts = 0;
            digitalWrite(LED_RATE_LIMIT, LOW);
            Serial.println("[SECURITY] Lockout expired");
        }
    }

    delay(50);
}

bool authenticate(const String& token) {
    // Check if rate limited
    if (secState.isLocked) {
        Serial.println("[BLOCKED] Account locked");
        return false;
    }

    // Rate limiting check
    unsigned long now = millis();
    if (now - secState.lastAttemptTime < MIN_ATTEMPT_INTERVAL) {
        Serial.println("[RATE_LIMIT] Too fast!");
        return false;
    }
    secState.lastAttemptTime = now;

    // Validate token
    bool tokenValid = false;
    for (int i = 0; i < NUM_VALID_TOKENS; i++) {
        if (token == VALID_TOKENS[i]) {
            tokenValid = true;
            break;
        }
    }

    if (tokenValid) {
        secState.isAuthenticated = true;
        secState.failedAttempts = 0;
        digitalWrite(LED_AUTH_OK, HIGH);
        Serial.println("[SUCCESS] Authenticated");
        return true;
    } else {
        secState.failedAttempts++;
        Serial.print("[FAILED] Attempts: ");
        Serial.println(secState.failedAttempts);

        if (secState.failedAttempts >= MAX_FAILED_ATTEMPTS) {
            secState.isLocked = true;
            secState.lockoutStartTime = millis();
            digitalWrite(LED_RATE_LIMIT, HIGH);
            Serial.println("[LOCKED] 30 second lockout");
        }

        // Flash red LED
        digitalWrite(LED_AUTH_FAIL, HIGH);
        delay(200);
        digitalWrite(LED_AUTH_FAIL, LOW);
        return false;
    }
}

void processCommand(const String& input) {
    if (input.startsWith("AUTH ")) {
        authenticate(input.substring(5));
    }
    else if (input.startsWith("CMD ")) {
        if (!secState.isAuthenticated) {
            Serial.println("[ERROR] Not authenticated");
            return;
        }
        String cmd = input.substring(4);
        executeCommand(cmd);
    }
    else if (input == "LOGOUT") {
        secState.isAuthenticated = false;
        digitalWrite(LED_AUTH_OK, LOW);
        Serial.println("[AUTH] Logged out");
    }
}

void executeCommand(const String& command) {
    // Whitelist validation
    bool valid = false;
    for (int i = 0; i < NUM_VALID_COMMANDS; i++) {
        if (command == VALID_COMMANDS[i]) {
            valid = true;
            break;
        }
    }

    if (!valid) {
        Serial.println("[REJECTED] Invalid command");
        return;
    }

    // Execute validated command
    if (command == "STATUS") {
        Serial.println("Device: ONLINE");
    }
    else if (command == "LED_ON") {
        digitalWrite(LED_AUTH_OK, HIGH);
    }
    else if (command == "LED_OFF") {
        digitalWrite(LED_AUTH_OK, LOW);
    }
    else if (command == "GET_TEMP") {
        float temp = 22.5 + (random(0, 50) / 10.0);
        Serial.print("Temperature: ");
        Serial.println(temp);
    }
    else if (command == "RESET") {
        secState.failedAttempts = 0;
        secState.isLocked = false;
        secState.isAuthenticated = false;
        digitalWrite(LED_AUTH_OK, LOW);
        digitalWrite(LED_RATE_LIMIT, LOW);
        Serial.println("[RESET] Security state cleared");
    }
}

33.3.6 Lab Steps

  1. Build the circuit in Wokwi following the pin connections
  2. Copy the code into the Wokwi editor
  3. Start simulation and open the Serial Monitor
  4. Test authentication:
    • Try: AUTH wrong_password (should fail)
    • Try: AUTH IOT_SEC_TOKEN_2024_A1B2C3 (should succeed)
  5. Test rate limiting:
    • Enter 3 wrong passwords quickly
    • Observe 30-second lockout (yellow LED)
  6. Test command validation:
    • After authenticating, try: CMD STATUS
    • Try: CMD DROP_TABLE (should be rejected)

33.3.7 Security Concepts Demonstrated

Concept Implementation Real-World Application
Token Authentication Whitelist of valid tokens API keys, JWT tokens
Rate Limiting Max attempts with lockout Brute force prevention
Command Whitelisting Only approved commands execute Injection attack prevention
Visual Feedback LED indicators for states Security dashboards
Audit Logging Serial output of all events SIEM systems

Knowledge Check: Authentication and Rate Limiting

Question: In the Lab 1 code, the lockout mechanism uses millis() to track timing. If an attacker power-cycles the ESP32 to reset the lockout timer, how would you make the rate limiting persistent across reboots?

Click to reveal answer

Answer: Store the failedAttempts count and lockoutStartTime in non-volatile storage (EEPROM or NVS on ESP32). On boot, read these values and check if the lockout period has elapsed. This prevents attackers from bypassing rate limiting by simply restarting the device. The ESP32’s Preferences library provides a simple API: preferences.putUInt("failedAttempts", secState.failedAttempts).

33.4 Lab 2: Build a Secure IoT Device with Threat Detection

This lab builds on the authentication lab to add anomaly detection and intrusion prevention capabilities. It reuses the same circuit as Lab 1 (three LEDs on GPIO 2, 4, and 5), with the LEDs repurposed to indicate threat levels instead of authentication states.

33.4.1 Lab Objectives

  • Implement statistical anomaly detection using Z-scores
  • Build DoS protection with request rate limiting
  • Simulate pattern-based attack detection (port scans, brute force)
  • Implement multi-level threat response with graduated alerts
  • Practice security event correlation

33.4.2 Threat Detection Features

  1. Anomaly Detection: Z-score analysis to detect unusual sensor readings
  2. DoS Protection: Request rate limiting with blocking
  3. Port Scan Simulation: Manual trigger to demonstrate threat escalation (in production, this would correlate multiple connection attempts across ports)
  4. Brute Force Simulation: Rapid request patterns that trigger rate limiting
  5. Threat Levels: Graduated response (NORMAL, LOW, MEDIUM, HIGH, CRITICAL) based on attack severity

33.4.3 Simplified Detection Code

/*
 * IoT Threat Detection System
 * Demonstrates: Anomaly detection, DoS protection, attack correlation
 */

#include <Arduino.h>
#include <math.h>

const int LED_NORMAL = 2;    // Green - Normal operations
const int LED_WARNING = 4;   // Yellow - Elevated threat
const int LED_CRITICAL = 5;  // Red - Critical threat

// Baseline for anomaly detection
float sensorBaseline = 25.0;
float sensorStdDev = 2.0;

// Rate limiting
const int MAX_REQUESTS_PER_SECOND = 10;
int requestCount = 0;
unsigned long lastSecond = 0;

// Threat level
enum ThreatLevel { NORMAL, LOW, MEDIUM, HIGH, CRITICAL };
ThreatLevel currentThreat = NORMAL;

void setup() {
    Serial.begin(115200);
    pinMode(LED_NORMAL, OUTPUT);
    pinMode(LED_WARNING, OUTPUT);
    pinMode(LED_CRITICAL, OUTPUT);

    digitalWrite(LED_NORMAL, HIGH);  // Normal state
    Serial.println("Threat Detection System Active");
    Serial.println("Commands: SENSOR <value>, DOS, SCAN, STATUS");
}

void loop() {
    // Reset rate limiter each second
    if (millis() - lastSecond >= 1000) {
        requestCount = 0;
        lastSecond = millis();
    }

    if (Serial.available()) {
        String input = Serial.readStringUntil('\n');
        input.trim();
        processInput(input);
    }

    updateThreatIndicators();
    delay(50);
}

void processInput(const String& input) {
    // Rate limit check
    requestCount++;
    if (requestCount > MAX_REQUESTS_PER_SECOND) {
        Serial.println("[DOS_BLOCKED] Rate limit exceeded");
        raiseThreatLevel(HIGH);
        return;
    }

    if (input.startsWith("SENSOR ")) {
        float value = input.substring(7).toFloat();
        checkAnomaly(value);
    }
    else if (input == "DOS") {
        Serial.println("[ATTACK] DoS simulation");
        raiseThreatLevel(HIGH);
    }
    else if (input == "SCAN") {
        Serial.println("[ATTACK] Port scan detected");
        raiseThreatLevel(MEDIUM);
    }
    else if (input == "STATUS") {
        printStatus();
    }
}

void checkAnomaly(float value) {
    // Z-score calculation
    float zScore = fabsf(value - sensorBaseline) / sensorStdDev;

    Serial.print("Value: ");
    Serial.print(value);
    Serial.print(" | Z-score: ");
    Serial.println(zScore);

    if (zScore > 3.0) {
        Serial.println("[ANOMALY] Suspicious reading detected");
        raiseThreatLevel(MEDIUM);
    } else if (zScore > 2.0) {
        Serial.println("[WARNING] Elevated reading");
        raiseThreatLevel(LOW);
    } else {
        Serial.println("[NORMAL] Reading within baseline");
    }
}

void raiseThreatLevel(ThreatLevel level) {
    if (level > currentThreat) {
        currentThreat = level;
        Serial.print("[THREAT] Level raised to: ");
        Serial.println(level);
    }
}

void updateThreatIndicators() {
    digitalWrite(LED_NORMAL, currentThreat == NORMAL);
    digitalWrite(LED_WARNING, currentThreat >= LOW && currentThreat < HIGH);
    digitalWrite(LED_CRITICAL, currentThreat >= HIGH);
}

void printStatus() {
    Serial.println("=== SYSTEM STATUS ===");
    Serial.print("Threat Level: ");
    Serial.println(currentThreat);
    Serial.print("Requests this second: ");
    Serial.println(requestCount);
    Serial.print("Baseline: ");
    Serial.println(sensorBaseline);
}

33.4.4 Testing the Threat Detection Lab

  1. Normal operation: SENSOR 24.5 (within baseline, Z-score = 0.25)
  2. Warning threshold: SENSOR 30.0 (Z-score = 2.5, triggers LOW threat)
  3. Anomaly detection: SENSOR 100.0 (Z-score = 37.5, triggers MEDIUM threat)
  4. DoS simulation: Enter DOS to simulate attack (triggers HIGH threat)
  5. Rate limiting: Enter many commands quickly to trigger the 10-requests-per-second limit

33.5 Brute Force Time Calculator

Use this interactive calculator to explore how rate limiting parameters affect brute force attack feasibility.

33.6 Z-Score Anomaly Detection Explorer

Use this calculator to understand how Z-score thresholds affect anomaly detection sensitivity.

33.7 Lab Challenges

Challenge 1: Add Token Expiration

Modify the authentication system so tokens expire after 5 minutes of inactivity. Implement automatic logout.

Hint: Track lastActivityTime in the SecurityState struct and check it in loop(). Reset the timer on each valid command.

Challenge 2: Implement Two-Factor Authentication

Add a second factor: after token authentication, require the user to press the button within 10 seconds to complete login.

Hint: Add a pendingAuth state and use millis() to track the 10-second window. The green LED should blink during the waiting period.

Challenge 3: Add Encryption for Commands

Implement simple XOR encryption for commands sent over serial. The key should be established during authentication.

Hint: XOR each character of the command with a key byte. The same operation encrypts and decrypts. Use a shared key derived from the authentication token.

Challenge 4: Create a Honeypot Token

Add a “trap” token that, when used, immediately locks the device and logs the attempt with high severity.

Hint: Add a special token like "HONEY_TRAP_TOKEN" to your token list, but instead of granting access, trigger a lockout with an extended duration and log the source.

Challenge 5: Build Alert Correlation Engine

Detect attack chains: scan followed by brute force followed by anomalous commands should trigger critical alert.

Hint: Track the sequence of event types in a small circular buffer. When the buffer contains SCAN + AUTH_FAIL + ANOMALY in order, escalate to CRITICAL.

33.8 From Lab to Production

The lab implementations demonstrate real security principles using simplified components. Here is how each lab concept maps to production-grade equivalents:

Lab Feature Builds Upon Enables Production Equivalent
Serial commands Direct I/O Protocol testing MQTT/CoAP over TLS
Hardcoded tokens Static credential store Authentication flow Certificate-based auth, JWT
LED indicators GPIO state output Visual alerting Security dashboards, SIEM
Serial logging Event recording Audit trail Centralized log aggregation
Simple rate limiting Attack attempt tracking Brute force prevention Distributed rate limiting (Redis)
Z-score anomaly Statistical baselines Behavioral detection ML models (Isolation Forest, autoencoders)
Threat levels Multi-metric correlation Graduated incident response SIEM severity scoring

Lab Progression: Lab 1 (authentication + rate limiting) prevents the majority of automated attacks through preventive controls. Lab 2 (anomaly detection + threat levels) catches sophisticated attacks that bypass static rules through detective controls. Together, they demonstrate defense-in-depth.

Scenario: A smart building company deploys 250 smart locks across an office complex. Each lock accepts authentication attempts via BLE from employee smartphones. They must decide whether to implement rate limiting to prevent brute force attacks.

Given:

  • Device: ESP32-based smart lock with BLE interface
  • Current authentication: 4-digit PIN (10,000 possible combinations)
  • Attack scenario: Attacker with $80 Bluetooth sniffer attempts brute force
  • Without rate limiting: Can try 100 PINs/minute
  • Proposed rate limiting: Max 3 failed attempts, then 5-minute lockout

Cost-Benefit Calculation:

Time to Brute Force Without Rate Limiting:
10,000 combinations / 100 attempts/min = 100 minutes = 1.7 hours
Attack feasible: YES (attacker can break in during lunch)

Time to Brute Force With Rate Limiting (3 attempts per 5 min):
Effective rate: 3 attempts / 5 min = 0.6 attempts/min
10,000 / 0.6 = 16,667 minutes = 278 hours = 11.6 days
Attack feasible: NO (impractical for opportunistic attacker)

Implementation Cost:
- Development time: 4 hours x $150/hr = $600
- Testing time: 2 hours x $150/hr = $300
- Code size: 80 lines (negligible flash/RAM impact)
- Runtime overhead: <1% CPU per authentication attempt
Total cost: $900 one-time

Risk Without Rate Limiting:
- Physical security breach probability: 15% per year (based on industry data)
- Average cost per breach: $25,000 (theft, remediation, investigation)
- Expected annual loss: 0.15 x $25,000 = $3,750

Risk With Rate Limiting:
- Breach probability reduced to: 1% per year
- Expected annual loss: 0.01 x $25,000 = $250
Annual savings: $3,750 - $250 = $3,500
ROI: $3,500 / $900 = 389% in year one
Payback period: $900 / $3,500 = 0.26 years = 3.1 months

Decision: Implement rate limiting. ROI is 389% in year one, and it protects against the most common attack vector (brute force). The CPU overhead is negligible for authentication operations that occur fewer than 10 times per day per lock.

Key Insight: Rate limiting is one of the highest-ROI security controls for authentication systems. The implementation cost is minimal (a few hours of development), but it increases brute force attack time from hours to days, making attacks economically infeasible for most threat actors.

Criterion Token/API Key Certificate-Based (mTLS) Biometric Multi-Factor (2FA)
Implementation Complexity Low (1-2 days) High (1-2 weeks) Very High (hardware + AI) Medium (3-5 days)
Cost per Device $0 (software only) $0.50-3 (secure element) $5-50 (fingerprint sensor) $0 (software + user phone)
User Friction None (transparent) None (transparent) Medium (enrollment + scan) High (second device needed)
Security Strength Medium (key theft risk) Very High (hardware-backed) High (spoofing possible) Very High (two independent factors)
Offline Operation Yes (if pre-validated) Yes (cert on device) Yes No (requires network for OTP)
Revocation Speed Instant (server-side) Slow (CRL/OCSP check) Impossible (biometric permanent) Instant (disable OTP)
Best For Low-security sensors, API access High-security devices, long lifespan Consumer devices with local access Critical operations, high-value assets
Avoid When Regulatory compliance (medical, financial) Cost-sensitive consumer products Shared devices (family thermostats) Elderly users, no smartphone access

Decision Tree:

  1. Is device safety-critical or handles PHI/PII? – Use Certificate-Based or Multi-Factor
  2. Is cost <$10 per device? – Avoid Biometric, consider Token or Certificate
  3. Must work offline? – Avoid Multi-Factor, use Certificate or Token
  4. Target users are non-technical (elderly, children)? – Avoid Multi-Factor and Biometric
  5. Need to revoke access within 1 hour? – Use Token or Multi-Factor (not Certificate alone)

Example Application:

  • Smart thermostat (low-risk, consumer): Token-based (simple, low-cost)
  • Medical infusion pump (life-critical): Certificate-based (hardware root of trust)
  • Smart door lock (physical security): Biometric + PIN fallback (convenience + security)
  • Industrial PLC (critical infrastructure): Multi-Factor (operator badge + OTP)
Common Mistake: Implementing Rate Limiting with Time Delays Instead of Lockouts

The Mistake: Many developers implement rate limiting by adding delays (e.g., delay(5000) after failed attempt) instead of using lockout counters.

Example of Flawed Code:

if (!authenticate(password)) {
    failedAttempts++;
    delay(5000 * failedAttempts);  // Wait longer after each failure
}

Why This Fails:

  1. DoS vulnerability: Attacker can lock device in infinite delays by triggering failures
  2. Not a brute-force deterrent: Attacker can simply restart device to reset counter
  3. Blocks legitimate users: Single typo causes 5-second wait
  4. Wastes power: delay() keeps CPU active during wait (battery drain on battery-powered devices)

Correct Implementation:

unsigned long lockoutUntil = 0;
int failedAttempts = 0;

bool authenticate(String password) {
    // Check if currently locked out
    if (millis() < lockoutUntil) {
        unsigned long remaining = (lockoutUntil - millis()) / 1000;
        Serial.printf("[LOCKED] %lu seconds remaining\n", remaining);
        return false;
    }

    if (verifyPassword(password)) {
        failedAttempts = 0;  // Reset on success
        return true;
    }

    failedAttempts++;
    if (failedAttempts >= 3) {
        lockoutUntil = millis() + 300000;  // 5-minute lockout
        // In production: store lockoutUntil in EEPROM/NVS
        // sendAlertEmail("Brute force attempt detected");
        // logSecurityEvent("LOCKOUT", sourceIP);
    }
    return false;
}

Why This Works:

  • Persistent lockout: Survives device restarts if stored in EEPROM/NVS
  • Non-blocking: Device can perform other tasks during lockout
  • Exponential backoff: Can increase lockout duration with repeated attacks
  • Alerting: Security team notified of attack attempts
  • Power-efficient: No active delay consuming power

Real-World Impact: A smart lock manufacturer deployed delay-based rate limiting. Attackers discovered they could force 10-minute delays by submitting wrong codes, effectively creating a denial-of-service that locked out legitimate users. After switching to lockout-based rate limiting with EEPROM persistence, brute force attacks became infeasible (11.5 days per 4-digit PIN vs. 1.7 hours before), and legitimate users experienced no delays.

33.9 See Also

Security Theory:

Implementation Guides:

Related Labs:

Common Pitfalls

IoT security labs — especially fuzzing, packet injection, and firmware modification — must be performed on isolated lab systems. Running these activities on production hardware risks disrupting legitimate operations and damaging equipment.

The value of a hands-on lab is not completing the steps but understanding what each step reveals. After every lab activity, stop to analyse the results: what security property was demonstrated? What would an attacker learn from this?

Security lab skills are built through reflection on documented findings, not just through completing exercises. Write up each lab with observed results, security implications, and how the demonstrated vulnerability would be fixed in production.

Lab conditions (controlled environment, known hardware, isolated network) differ from production. Lab findings indicate potential vulnerabilities that require additional analysis to confirm exploitability in the specific production deployment context.

33.10 Summary

These labs demonstrated practical IoT security:

  • Authentication: Token-based access control with secure validation
  • Rate Limiting: Preventing brute force with lockout mechanisms
  • Command Validation: Whitelist-based input sanitization
  • Anomaly Detection: Statistical Z-score methods to detect unusual behavior
  • Threat Response: Multi-level alerts based on attack severity

33.11 Knowledge Check

33.12 What’s Next

The final chapter provides Visual Resources including diagrams for encryption, attack scenarios, and security architectures.

Continue to Visual Resources


← Intrusion Detection Visual Resources →