Zugriffskontrollprüfung erfolgt nach dem Zugriff auf das Asset

Beschreibung

Zugriffskontrollprüfung erfolgt nach dem Zugriff auf das Asset tritt auf, wenn die hardware-basierte Zugriffskontrollprüfung eines Produkts erst stattfindet, nachdem bereits auf das Asset zugegriffen wurde. Die Schwachstelle entsteht, wenn die Zugriffskontrollverifizierung nicht-atomar erfolgt, wodurch ein Asset-Zugriff vor Abschluss der Autorisierung möglich wird und die Systemsicherheit potenziell kompromittiert wird. Dieses Timing-Problem bedeutet, dass zum Zeitpunkt der Zugriffsverweigerung die sensiblen Daten möglicherweise bereits gelesen oder modifiziert wurden.

Risiko

Post-Access-Autorisierung hat schwerwiegende Sicherheitsauswirkungen. Sensible Daten können vor der Verweigerung gelesen werden. Speicher kann vor der Ablehnung modifiziert werden. Privilegprüfungen werden wirkungslos. Race Conditions können ausgenutzt werden. Autorisierungsentscheidungen werden bedeutungslos. Seitenkanalinformationen können durchsickern. Angreifer können Daten Bit für Bit extrahieren. Sicherheitsgrenzen werden verletzt.

Lösung

Führen Sie die Zugriffskontrollprüfung zuerst durch und erlauben Sie den Asset-Zugriff nur, nachdem die Autorisierungsverifizierung erfolgreich war. Verwenden Sie atomare Operationen, die Prüfung und Zugriff kombinieren. Stellen Sie sicher, dass Datenpfade keine Werte weiterleiten, bevor die Zugriffskontrolle abgeschlossen ist. Entwerfen Sie Hardware so, dass Datenausgaben blockiert werden, bis die Autorisierung bestätigt ist. Verifizieren Sie die korrekte Reihenfolge durch Simulation und formale Verifikation.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Zugriffskontrolle ist wirkungslos.
VertraulichkeitBereich: Vertraulichkeit

Speicher lesen - Daten zugänglich vor Verweigerung.
IntegritätBereich: Integrität

Speicher modifizieren - Schreibvorgänge können vor Ablehnung abgeschlossen werden.
AutorisierungBereich: Autorisierung

Privilegien erlangen - Unautorisierter Zugriff gelingt teilweise.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Zugriffsprüfung nach Datenlesen

module vulnerable_register_access (
    input wire clk,
    input wire reset_n,
    input wire [7:0] addr,
    input wire read_enable,
    input wire write_enable,
    input wire [31:0] write_data,
    input wire [3:0] requester_id,
    output reg [31:0] read_data,
    output reg access_denied
);

    // Sensitiver Registerspeicher
    reg [31:0] registers [0:255];

    // Zugriffskontrollrichtlinie
    reg [15:0] read_policy [0:255];   // Bit pro Anforderer
    reg [15:0] write_policy [0:255];

    // VERWUNDBAR: Verwendung blockierender Zuweisungen in falscher Reihenfolge
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            read_data <= 32'h0;
            access_denied <= 1'b0;
        end
        else if (read_enable) begin
            // VERWUNDBAR: Datenlesen geschieht ZUERST
            read_data = registers[addr];  // Blockierende Zuweisung - sofort

            // Zugriffsprüfung geschieht DANACH
            if (!read_policy[addr][requester_id]) begin
                access_denied <= 1'b1;
                read_data <= 32'hDEADDEAD;  // Versuch zu maskieren - zu spät!
            end
            else begin
                access_denied <= 1'b0;
            end

            // Problem: read_data wurde bereits mit sensitivem Wert aktualisiert
            // Auch wenn access_denied, war der Wert kurzzeitig sichtbar
        end
    end

endmodule

// VERWUNDBAR: Schreiben vor Zugriffsprüfung
module vulnerable_memory_write (
    input wire clk,
    input wire reset_n,
    input wire [15:0] addr,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire [3:0] master_id,
    output reg write_complete,
    output reg access_denied
);

    reg [31:0] memory [0:65535];

    // MPU-Konfiguration
    reg [31:0] mpu_start [0:7];
    reg [31:0] mpu_end [0:7];
    reg [15:0] mpu_allowed_masters [0:7];

    function automatic is_access_allowed;
        input [15:0] address;
        input [3:0] master;
        integer i;
        begin
            is_access_allowed = 1'b0;
            for (i = 0; i < 8; i = i + 1) begin
                if (address >= mpu_start[i] && address <= mpu_end[i]) begin
                    if (mpu_allowed_masters[i][master]) begin
                        is_access_allowed = 1'b1;
                    end
                end
            end
        end
    endfunction

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            write_complete <= 1'b0;
            access_denied <= 1'b0;
        end
        else if (write_enable) begin
            // VERWUNDBAR: Schreiben geschieht sofort
            memory[addr] = write_data;  // Blockierend - sofortige Wirkung

            // Prüfung geschieht nach dem Schreiben
            if (!is_access_allowed(addr, master_id)) begin
                access_denied <= 1'b1;
                // VERWUNDBAR: Daten wurden bereits geschrieben!
                // Versuch rückgängig zu machen ist zu spät
                memory[addr] = 32'h0;  // Versuch zu löschen
            end
            else begin
                access_denied <= 1'b0;
            end

            write_complete <= 1'b1;
        end
    end

endmodule
// VERWUNDBAR: Software-Zugriffsprüfung nach dem Lesen

#include <stdint.h>

typedef struct {
    uint32_t data[256];
    uint8_t access_policy[256];  // Bit 0 = Lesen, Bit 1 = Schreiben
} protected_region_t;

// VERWUNDBAR: Lesen dann prüfen
uint32_t vulnerable_read(protected_region_t* region, uint8_t index,
                         uint8_t requester_privilege) {
    // VERWUNDBAR: Daten zuerst lesen
    uint32_t value = region->data[index];

    // Prüfung geschieht danach
    if (!(region->access_policy[index] & 0x01)) {
        // Zugriff verweigert - aber Wert bereits in 'value' Variable
        // Könnte durch Timing, Cache usw. durchsickern
        return 0xFFFFFFFF;  // Fehlerwert zurückgeben
    }

    // Auch Privileg prüfen (nach dem Lesen)
    if (requester_privilege < get_required_privilege(index)) {
        // Verweigert - aber Wert wurde bereits gelesen
        return 0xFFFFFFFF;
    }

    return value;

    // Angriff: Auch bei Verweigerung wurde Wert in Register geladen
    // Seitenkanale können den tatsächlichen Wert durchsickern lassen
}

// VERWUNDBAR: Schreiben dann prüfen
void vulnerable_write(protected_region_t* region, uint8_t index,
                      uint32_t value, uint8_t requester_privilege) {
    // VERWUNDBAR: Zuerst schreiben
    region->data[index] = value;

    // Berechtigungen nach dem Schreiben prüfen
    if (!(region->access_policy[index] & 0x02)) {
        // Hoppla! Bereits geschrieben - versuchen rückgängig zu machen
        region->data[index] = 0;  // Aber Schäden könnte bereits angerichtet sein
    }
}

Sichere Lösung

// SICHER: Zugriffsprüfung vor Datenlesen

module secure_register_access (
    input wire clk,
    input wire reset_n,
    input wire [7:0] addr,
    input wire read_enable,
    input wire write_enable,
    input wire [31:0] write_data,
    input wire [3:0] requester_id,
    output reg [31:0] read_data,
    output reg access_denied,
    output reg operation_complete
);

    // Sensitiver Registerspeicher
    reg [31:0] registers [0:255];

    // Zugriffskontrollrichtlinie
    reg [15:0] read_policy [0:255];
    reg [15:0] write_policy [0:255];

    // SICHER: Zugriffsberechtigung kombinatorisch prüfen
    wire read_allowed = read_policy[addr][requester_id];
    wire write_allowed = write_policy[addr][requester_id];

    // SICHER: Zustandsmaschine für kontrollierten Zugriff
    reg [1:0] state;
    parameter IDLE = 2'd0;
    parameter CHECK = 2'd1;
    parameter ACCESS = 2'd2;
    parameter COMPLETE = 2'd3;

    reg pending_read;
    reg pending_write;
    reg [7:0] pending_addr;
    reg [31:0] pending_data;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            read_data <= 32'h0;
            access_denied <= 1'b0;
            operation_complete <= 1'b0;
            state <= IDLE;
            pending_read <= 1'b0;
            pending_write <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    access_denied <= 1'b0;
                    operation_complete <= 1'b0;

                    if (read_enable || write_enable) begin
                        pending_read <= read_enable;
                        pending_write <= write_enable;
                        pending_addr <= addr;
                        pending_data <= write_data;
                        state <= CHECK;
                    end
                end

                CHECK: begin
                    // SICHER: Zugriff ZUERST prüfen, bevor Datenbewegung
                    if (pending_read && !read_allowed) begin
                        access_denied <= 1'b1;
                        read_data <= 32'h0;  // Keine echten Daten jemals geladen
                        state <= COMPLETE;
                    end
                    else if (pending_write && !write_allowed) begin
                        access_denied <= 1'b1;
                        state <= COMPLETE;
                    end
                    else begin
                        state <= ACCESS;  // Berechtigung erteilt
                    end
                end

                ACCESS: begin
                    // SICHER: Nur auf Daten zugreifen, nachdem Berechtigung verifiziert
                    if (pending_read) begin
                        read_data <= registers[pending_addr];
                    end
                    else if (pending_write) begin
                        registers[pending_addr] <= pending_data;
                    end
                    state <= COMPLETE;
                end

                COMPLETE: begin
                    operation_complete <= 1'b1;
                    pending_read <= 1'b0;
                    pending_write <= 1'b0;
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule

// SICHER: Atomare Zugriffskontrolle mit gesteuerter Ausgabe
module secure_gated_access (
    input wire clk,
    input wire reset_n,
    input wire [7:0] addr,
    input wire read_enable,
    input wire [3:0] requester_id,
    output wire [31:0] read_data,
    output wire access_denied
);

    reg [31:0] registers [0:255];
    reg [15:0] read_policy [0:255];

    // SICHER: Kombinatorische Zugriffsprüfung
    wire allowed = read_policy[addr][requester_id];

    // SICHER: Datenausgabe basierend auf Zugriffsberechtigung steuern
    // Datenpfad ist blockiert, bis Berechtigung verifiziert
    assign read_data = (read_enable && allowed) ? registers[addr] : 32'h0;
    assign access_denied = read_enable && !allowed;

    // Kein Timing-Lücke - Prüfung und Steuerung sind kombinatorisch

endmodule

// SICHER: Speicherschutz mit Prüfung-vor-Zugriff
module secure_memory_protection (
    input wire clk,
    input wire reset_n,
    input wire [15:0] addr,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire read_enable,
    input wire [3:0] master_id,
    output reg [31:0] read_data,
    output reg operation_complete,
    output reg access_denied
);

    reg [31:0] memory [0:65535];

    // MPU-Konfiguration
    reg [15:0] region_start [0:7];
    reg [15:0] region_end [0:7];
    reg [15:0] region_read_mask [0:7];   // Master die lesen dürfen
    reg [15:0] region_write_mask [0:7];  // Master die schreiben dürfen

    // SICHER: Zugriffsberechtigung vorab berechnen
    function automatic check_permission;
        input [15:0] address;
        input [3:0] master;
        input is_write;
        integer i;
        begin
            check_permission = 1'b0;
            for (i = 0; i < 8; i = i + 1) begin
                if (address >= region_start[i] && address <= region_end[i]) begin
                    if (is_write) begin
                        check_permission = region_write_mask[i][master];
                    end
                    else begin
                        check_permission = region_read_mask[i][master];
                    end
                end
            end
        end
    endfunction

    // Zustandsmaschine
    reg [1:0] state;
    parameter IDLE = 2'd0;
    parameter CHECKING = 2'd1;
    parameter ACCESSING = 2'd2;

    reg [15:0] latched_addr;
    reg [31:0] latched_data;
    reg latched_write;
    reg access_allowed;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            state <= IDLE;
            read_data <= 32'h0;
            operation_complete <= 1'b0;
            access_denied <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    operation_complete <= 1'b0;
                    access_denied <= 1'b0;

                    if (write_enable || read_enable) begin
                        latched_addr <= addr;
                        latched_data <= write_data;
                        latched_write <= write_enable;
                        state <= CHECKING;
                    end
                end

                CHECKING: begin
                    // SICHER: Berechtigung VOR Speicherzugriff evaluieren
                    access_allowed <= check_permission(latched_addr, master_id,
                                                       latched_write);

                    if (!check_permission(latched_addr, master_id, latched_write)) begin
                        // SICHER: Vor jedem Zugriff verweigern
                        access_denied <= 1'b1;
                        operation_complete <= 1'b1;
                        state <= IDLE;
                    end
                    else begin
                        state <= ACCESSING;
                    end
                end

                ACCESSING: begin
                    // SICHER: Nur hierher gelangen, wenn Zugriff erlaubt war
                    if (latched_write) begin
                        memory[latched_addr] <= latched_data;
                    end
                    else begin
                        read_data <= memory[latched_addr];
                    end

                    operation_complete <= 1'b1;
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule
// SICHER: Software-Zugriffsprüfung vor dem Lesen

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

typedef struct {
    uint32_t data[256];
    uint8_t access_policy[256];
} protected_region_t;

// SICHER: Prüfen dann lesen
bool secure_read(protected_region_t* region, uint8_t index,
                 uint8_t requester_privilege, uint32_t* value_out) {

    // SICHER: Berechtigungen ZUERST prüfen
    if (!(region->access_policy[index] & 0x01)) {
        *value_out = 0;
        return false;  // Zugriff verweigert - kein Lesen durchgeführt
    }

    // SICHER: Privileg VOR dem Lesen prüfen
    if (requester_privilege < get_required_privilege(index)) {
        *value_out = 0;
        return false;  // Privileg verweigert - kein Lesen durchgeführt
    }

    // SICHER: Erst jetzt das Lesen durchführen
    *value_out = region->data[index];
    return true;
}

// SICHER: Prüfen dann schreiben
bool secure_write(protected_region_t* region, uint8_t index,
                  uint32_t value, uint8_t requester_privilege) {

    // SICHER: Schreibberechtigung ZUERST prüfen
    if (!(region->access_policy[index] & 0x02)) {
        return false;  // Zugriff verweigert - kein Schreiben durchgeführt
    }

    // SICHER: Privileg VOR dem Schreiben prüfen
    if (requester_privilege < get_required_privilege(index)) {
        return false;  // Privileg verweigert - kein Schreiben durchgeführt
    }

    // SICHER: Erst jetzt das Schreiben durchführen
    region->data[index] = value;
    return true;
}

// SICHER: Atomares Prüfen-und-Zugreifen für Hardware-Register
uint32_t secure_hw_read(uint32_t addr, uint8_t requester) {
    // SICHER: Hardware bitten, atomares Prüfen-und-Lesen durchzuführen
    // Hardware stellt sicher, dass Daten nicht sichtbar sind, bis Prüfung besteht

    // Anfrage einrichten
    write_hw_reg(ACCESS_CTRL_ADDR, addr);
    write_hw_reg(ACCESS_CTRL_REQUESTER, requester);
    write_hw_reg(ACCESS_CTRL_CMD, CMD_READ);

    // Auf Abschluss warten
    while (!(read_hw_reg(ACCESS_CTRL_STATUS) & STATUS_COMPLETE)) {
        // Warten
    }

    // Ergebnis prüfen
    if (read_hw_reg(ACCESS_CTRL_STATUS) & STATUS_DENIED) {
        return 0;  // Zugriff wurde verweigert
    }

    // Zugriff war erlaubt - Ergebnis lesen
    return read_hw_reg(ACCESS_CTRL_DATA);
}

CVE-Beispiele

Zugriffskontroll-Timing-Schwachstellen wurden in verschiedenen Hardware-Designs gefunden, bei denen Daten exponiert wurden, bevor Berechtigungsprüfungen abgeschlossen waren, was Informationsoffenlegung durch Seitenkanale oder direkte Beobachtung ermöglichte.


Verwandte CWEs

  • CWE-284: Unsachgemäße Zugriffskontrolle (Eltern)
  • CWE-696: Falsche Verhaltensreihenfolge (Eltern)
  • CWE-1198: Privilegientrennung und Zugriffskontrollprobleme (Kategorie)
  • CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition (verwandt)
  • CAPEC-180: Ausnutzung falsch konfigurierter Zugriffskontrollsicherheitsstufen (Angriffsmuster)

Referenzen

  1. MITRE Corporation. "CWE-1280: Access Control Check Implemented After Asset is Accessed." https://cwe.mitre.org/data/definitions/1280.html
  2. ARM. "AMBA AXI Protocol Specification"
  3. IEEE. "Hardware Security Best Practices"