On-Chip-Debug- und Testschnittstelle mit unsachgemäßer Zugriffskontrolle

Beschreibung

On-Chip-Debug- und Testschnittstelle mit unsachgemäßer Zugriffskontrolle tritt auf, wenn Debug- und Testschnittstellen in Chips und SoCs ohne ordnungsgemäße Zugriffskontrolle implementiert werden. JTAG- und Debug-Schnittstellen sind für Entwicklung, Testen und Fertigung unerlässlich, müssen aber nach der Bereitstellung entweder gesperrt oder entfernt werden. Ohne ordnungsgemäße Kontrollen können Angreifer diese Schnittstellen ausnutzen, um Speicher zu lesen/schreiben, Secure Boot zu umgehen, kryptografische Schlüssel zu extrahieren oder bösartigen Code auszuführen.

Risiko

Ungeschützte Debug-Schnittstellen haben schwerwiegende Sicherheitsauswirkungen. Angreifer können gesamten Speicherinhalt lesen. Firmware kann modifiziert werden. Kryptografische Schlüssel können extrahiert werden. Secure Boot kann umgangen werden. Geistiges Eigentum kann offengelegt werden. Produktklonierung wird möglich. Alle Sicherheitskontrollen können umgangen werden. Persistenter Zugriff kann etabliert werden.

Lösung

Für Produktionsfreigabe: Sperren, Deaktivieren oder Entfernen von Debug-Ports. Implementierung von Authentifizierung für Debug-Zugriff. Verwendung von Sicherungen oder OTP, um Debug dauerhaft zu deaktivieren. Definition verschiedener Debug-Zugriffsebenen. Für Entwicklung: Beschränkung des Debug-Zugriffs auf autorisierte Entwickler. Schutz von Debug-Anmeldeinformationen. Protokollierung von Debug-Zugriffen. Sicherstellung, dass Debug-Schlüssel sich von Produktionsschlüsseln unterscheiden.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Speicher lesen - Debug-Schnittstellen können verwendet werden, um beliebigen Speicher einschließlich Geheimnisse zu lesen.
IntegritätBereich: Integrität

Speicher modifizieren - Debug kann Speicher und Code ändern.
AutorisierungBereich: Autorisierung

Privilegien erlangen - Debug gewährt oft höchste Systemrechte.

Beispielcode und Lösung

Verwundbarer Code

// Verwundbar: JTAG-Passwortauthentifizierung ohne Brute-Force-Schutz

module vulnerable_jtag_auth (
    input wire tck,
    input wire tms,
    input wire tdi,
    output reg tdo,
    input wire [31:0] password_input,
    output reg debug_enabled
);

    // Hartcodiertes Passwort - kann extrahiert werden
    parameter [31:0] DEBUG_PASSWORD = 32'hDEADBEEF;

    reg [31:0] entered_password;
    reg password_valid;

    always @(posedge tck) begin
        // VERWUNDBAR: Keine Sperre nach fehlgeschlagenen Versuchen
        // Angreifer kann alle 2^32 Kombinationen durchprobieren
        if (password_input == DEBUG_PASSWORD) begin
            debug_enabled <= 1'b1;
            password_valid <= 1'b1;
        end else begin
            password_valid <= 1'b0;
            // Kein Zähler für fehlgeschlagene Versuche!
            // Kein Sperrmechanismus!
        end
    end

endmodule

// Verwundbar: HMAC-Authentifizierung mit abgeschnittenem Geheimnis
module vulnerable_hmac_auth (
    input wire clk,
    input wire [511:0] secret_key,
    input wire [255:0] challenge,
    input wire [255:0] response,
    output reg authenticated
);

    wire [255:0] expected_response;

    // VERWUNDBAR: Nur 32 Bits des 512-Bit-Geheimnisses verwendet!
    // Reduziert Sicherheit drastisch von 2^512 auf 2^32
    hmac_sha256 hmac (
        .key(secret_key[31:0]),  // Nur 32 Bits verwendet!
        .message(challenge),
        .mac(expected_response)
    );

    always @(posedge clk) begin
        if (response == expected_response) begin
            authenticated <= 1'b1;
        end else begin
            authenticated <= 1'b0;
        end
    end

endmodule

// Verwundbar: Debug-Schnittstelle ohne Authentifizierung
module vulnerable_debug_interface (
    input wire clk,
    input wire debug_request,
    input wire [7:0] debug_command,
    input wire [31:0] debug_address,
    output reg [31:0] debug_data,
    output reg debug_ack
);

    // ÜBERHAUPT KEINE AUTHENTIFIZIERUNG
    // Jeder mit physischem Zugang kann Debug-Schnittstelle verwenden

    always @(posedge clk) begin
        if (debug_request) begin
            case (debug_command)
                8'h01: debug_data <= read_memory(debug_address);
                8'h02: write_memory(debug_address, debug_data);
                8'h03: debug_data <= read_secure_fuses();  // Legt Geheimnisse offen!
                8'h04: bypass_secure_boot();  // Deaktiviert Sicherheit!
                default: debug_data <= 32'h0;
            endcase
            debug_ack <= 1'b1;
        end
    end

endmodule
// Verwundbar: Firmware mit schwacher Debug-Authentifizierung

#define DEBUG_PASSWORD "debug123"  // Schwaches, hartcodiertes Passwort

bool authenticate_debug_access(const char* provided_password) {
    // VERWUNDBAR: Einfacher String-Vergleich
    // Timing-Angriff möglich
    // Keine Sperre nach fehlgeschlagenen Versuchen
    return strcmp(provided_password, DEBUG_PASSWORD) == 0;
}

void handle_debug_command(uint8_t command, uint32_t param) {
    // Keine Authentifizierungsprüfung!
    switch (command) {
        case CMD_READ_MEMORY:
            debug_output(read_memory(param));
            break;
        case CMD_WRITE_MEMORY:
            write_memory(param, debug_input());
            break;
        case CMD_DUMP_KEYS:
            debug_output(get_encryption_keys());  // Legt Geheimnisse offen!
            break;
        case CMD_DISABLE_SECURITY:
            security_enabled = false;  // Deaktiviert Sicherheit!
            break;
    }
}

Sichere Lösung

// Sicher: JTAG-Authentifizierung mit Brute-Force-Schutz

module secure_jtag_auth (
    input wire tck,
    input wire tms,
    input wire tdi,
    output reg tdo,
    input wire [255:0] auth_response,  // Kryptographische Antwort
    input wire auth_attempt,
    output reg debug_enabled,
    output reg permanently_locked
);

    // Sicherheitsparameter
    parameter MAX_FAILED_ATTEMPTS = 3;
    parameter LOCKOUT_CYCLES = 32'd1000000;  // ~1 Sekunde bei 1MHz

    reg [3:0] failed_attempts;
    reg [31:0] lockout_counter;
    reg in_lockout;

    // Challenge-Response-Authentifizierung
    reg [255:0] current_challenge;
    wire [255:0] expected_response;
    wire response_valid;

    // Kryptographische Verifikation
    crypto_verify verifier (
        .challenge(current_challenge),
        .response(auth_response),
        .valid(response_valid)
    );

    always @(posedge tck) begin
        if (permanently_locked) begin
            // Gerät permanent gesperrt - kein Debug-Zugriff mehr
            debug_enabled <= 1'b0;
        end
        else if (in_lockout) begin
            // Temporäre Sperre aktiv
            if (lockout_counter > 0) begin
                lockout_counter <= lockout_counter - 1;
            end else begin
                in_lockout <= 1'b0;
            end
            debug_enabled <= 1'b0;
        end
        else if (auth_attempt) begin
            if (response_valid) begin
                // Erfolgreiche Authentifizierung
                debug_enabled <= 1'b1;
                failed_attempts <= 4'b0;
                // Neue Challenge für nächstes Mal generieren
                current_challenge <= generate_new_challenge();
            end else begin
                // Fehlgeschlagene Authentifizierung
                failed_attempts <= failed_attempts + 1;

                if (failed_attempts >= MAX_FAILED_ATTEMPTS - 1) begin
                    // Zu viele Fehlversuche - permanente Sperre
                    permanently_locked <= 1'b1;
                end else begin
                    // Temporäre Sperre mit exponentiellem Backoff
                    in_lockout <= 1'b1;
                    lockout_counter <= LOCKOUT_CYCLES << failed_attempts;
                end
                debug_enabled <= 1'b0;
            end
        end
    end

endmodule

// Sicher: HMAC-Authentifizierung mit vollständigem Geheimnis
module secure_hmac_auth (
    input wire clk,
    input wire [511:0] secret_key,
    input wire [255:0] challenge,
    input wire [255:0] response,
    input wire verify_request,
    output reg authenticated,
    output reg auth_complete
);

    wire [255:0] expected_response;

    // Vollständigen 512-Bit-Geheimschlüssel verwenden
    hmac_sha256 hmac (
        .key(secret_key),  // Volle 512 Bits
        .message(challenge),
        .mac(expected_response)
    );

    // Konstantzeitvergleich zur Verhinderung von Timing-Angriffen
    wire [255:0] diff;
    assign diff = response ^ expected_response;

    always @(posedge clk) begin
        if (verify_request) begin
            // Konstantzeit: prüfen ob alle Bits null sind
            authenticated <= (diff == 256'b0);
            auth_complete <= 1'b1;
        end else begin
            auth_complete <= 1'b0;
        end
    end

endmodule

// Sicher: Debug-Schnittstelle mit korrekter Zugriffskontrolle
module secure_debug_interface (
    input wire clk,
    input wire reset_n,
    input wire debug_request,
    input wire [7:0] debug_command,
    input wire [31:0] debug_address,
    input wire debug_authenticated,
    input wire [2:0] debug_access_level,  // Gestufte Zugriffskontrolle
    input wire production_mode,
    output reg [31:0] debug_data,
    output reg debug_ack,
    output reg debug_error
);

    // Zugriffsebenen-Definitionen
    parameter ACCESS_NONE = 3'b000;
    parameter ACCESS_BASIC = 3'b001;
    parameter ACCESS_ADVANCED = 3'b010;
    parameter ACCESS_FULL = 3'b111;

    // Befehlszugriffsanforderungen
    function [2:0] required_access_level;
        input [7:0] cmd;
        begin
            case (cmd)
                8'h01: required_access_level = ACCESS_BASIC;    // Speicher lesen
                8'h02: required_access_level = ACCESS_ADVANCED; // Speicher schreiben
                8'h03: required_access_level = ACCESS_FULL;     // Fuses lesen
                8'h04: required_access_level = ACCESS_FULL;     // Sicherheitsoperationen
                default: required_access_level = ACCESS_NONE;
            endcase
        end
    endfunction

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            debug_data <= 32'b0;
            debug_ack <= 1'b0;
            debug_error <= 1'b0;
        end
        else if (debug_request) begin
            // Prüfen ob im Produktionsmodus - meiste Debug-Funktionen deaktiviert
            if (production_mode && debug_command != 8'h01) begin
                debug_error <= 1'b1;
                debug_ack <= 1'b0;
            end
            // Authentifizierung prüfen
            else if (!debug_authenticated) begin
                debug_error <= 1'b1;
                debug_ack <= 1'b0;
            end
            // Zugriffsebene prüfen
            else if (debug_access_level < required_access_level(debug_command)) begin
                debug_error <= 1'b1;
                debug_ack <= 1'b0;
            end
            else begin
                // Autorisiert - Befehl ausführen
                case (debug_command)
                    8'h01: debug_data <= read_memory(debug_address);
                    8'h02: write_memory(debug_address, debug_data);
                    8'h03: debug_data <= read_secure_fuses();
                    default: debug_data <= 32'b0;
                endcase
                debug_ack <= 1'b1;
                debug_error <= 1'b0;
            end
        end
    end

endmodule
// Sicher: Firmware mit sicherer Debug-Authentifizierung

#include <stdint.h>
#include <stdbool.h>
#include "crypto.h"

#define MAX_AUTH_ATTEMPTS 3
#define LOCKOUT_DURATION_MS 30000

static uint8_t failed_attempts = 0;
static uint32_t lockout_until = 0;
static bool permanently_locked = false;
static uint8_t current_challenge[32];

// Kryptographische Challenge generieren
void generate_challenge(void) {
    secure_random(current_challenge, sizeof(current_challenge));
}

// Kryptographische Antwort verifizieren
bool verify_response(const uint8_t* response, size_t response_len) {
    uint8_t expected[32];

    // Erwartete Antwort mit Gerätegeheimnis berechnen
    hmac_sha256(get_device_secret(), 32, current_challenge, 32, expected);

    // Konstantzeitvergleich
    return secure_compare(response, expected, 32);
}

// Debug-Zugriff mit Sperrschutz authentifizieren
bool authenticate_debug_access(const uint8_t* response, size_t len) {
    // Permanente Sperre prüfen
    if (permanently_locked) {
        return false;
    }

    // Temporäre Sperre prüfen
    if (get_current_time_ms() < lockout_until) {
        return false;
    }

    // Kryptographische Antwort verifizieren
    if (verify_response(response, len)) {
        failed_attempts = 0;
        generate_challenge();  // Neue Challenge für nächstes Mal
        return true;
    }

    // Authentifizierung fehlgeschlagen
    failed_attempts++;

    if (failed_attempts >= MAX_AUTH_ATTEMPTS) {
        // Permanente Sperre
        permanently_locked = true;
        blow_lockout_fuse();  // Hardware-Durchsetzung
    } else {
        // Exponentieller Backoff bei Sperre
        lockout_until = get_current_time_ms() +
                        (LOCKOUT_DURATION_MS << failed_attempts);
    }

    generate_challenge();  // Neue Challenge
    return false;
}

// Debug-Befehlshandler mit Zugriffskontrolle
void handle_debug_command(uint8_t command, uint32_t param, uint8_t access_level) {
    // Produktionsmodus-Prüfung
    if (is_production_device() && command != CMD_READ_BASIC) {
        debug_error(ERR_PRODUCTION_LOCKED);
        return;
    }

    // Zugriffsebenen-Prüfung
    if (access_level < get_required_access(command)) {
        debug_error(ERR_INSUFFICIENT_ACCESS);
        return;
    }

    // Autorisierten Befehl ausführen
    switch (command) {
        case CMD_READ_BASIC:
            debug_output(read_memory_safe(param));
            break;
        case CMD_READ_ADVANCED:
            debug_output(read_memory(param));
            break;
        case CMD_WRITE_MEMORY:
            write_memory_checked(param, debug_input());
            break;
        default:
            debug_error(ERR_UNKNOWN_COMMAND);
    }
}

Ausgenutzt in der Praxis

Xbox 360 JTAG-Hack (2007)

Hacker nutzten ungeschützte JTAG-Schnittstellen auf der Xbox 360 aus, um das Sicherheitssystem der Konsole zu umgehen, was das Ausführen von nicht signiertem Code und raubkopierter Software ermöglichte.

Router-Debug-Port-Ausnutzungen (verschiedene)

Zahlreiche Router wurden mit aktivierten Debug-Ports ausgeliefert, die Angreifern volle Kontrolle über die Geräte ermöglichten.


Tools zum Testen und Ausnutzen


CVE-Beispiele

  • CVE-2019-18827 — JTAG-Zugriff vor ROM-Code-Ausführung auf Barco ClickShare möglich.

  • CVE-2021-26312 — Debug-Schnittstelle ermöglicht unbefugten Speicherzugriff.


Referenzen

  1. MITRE Corporation. "CWE-1191: On-Chip Debug and Test Interface With Improper Access Control." https://cwe.mitre.org/data/definitions/1191.html

  2. NIST. "Guidelines for Securing JTAG Interfaces."

  3. ARM. "Debug Authentication Architecture."