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
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Schutzmechanismus umgehen - Zugriffskontrolle ist wirkungslos. |
| Vertraulichkeit | Bereich: Vertraulichkeit Speicher lesen - Daten zugänglich vor Verweigerung. |
| Integrität | Bereich: Integrität Speicher modifizieren - Schreibvorgänge können vor Ablehnung abgeschlossen werden. |
| Autorisierung | Bereich: 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
- MITRE Corporation. "CWE-1280: Access Control Check Implemented After Asset is Accessed." https://cwe.mitre.org/data/definitions/1280.html
- ARM. "AMBA AXI Protocol Specification"
- IEEE. "Hardware Security Best Practices"