Operation auf Ressource in falscher Lebenszyklusphase
Beschreibung
Operation auf Ressource in falscher Lebenszyklusphase tritt auf, wenn Code Operationen auf einer Ressource durchführt, die für den aktuellen Zustand der Ressource in ihrem Lebenszyklus ungeeignet sind. Dies umfasst die Verwendung einer Ressource bevor sie bereit ist, nachdem sie geschlossen wurde, während der Initialisierung oder in einem ungültigen Zustand. Häufige Beispiele sind das Lesen aus nicht geöffneten Dateien, Schreiben auf geschlossene Verbindungen oder das Aufrufen von Methoden auf teilweise konstruierten Objekten.
Risiko
Undefiniertes Verhalten durch Verwendung von Ressourcen in ungültigen Zuständen. Datenkorruption durch Schreiben auf nicht ordnungsgemäß vorbereitete Ressourcen. Sicherheitslücken durch Umgehung von Initialisierungsroutinen. Abstürze durch Operationen auf freigegebenen Ressourcen. Informationsoffenlegung durch Zugriff auf Ressourcen vor Bereinigung. Authentifizierungsumgehung durch Verwendung von Sessions vor Validierung.
Lösung
Implementieren Sie explizite Zustandsverfolgung für Ressourcen. Verwenden Sie Zustandsmaschinen für komplexe Ressourcen-Lebenszyklen. Validieren Sie den Ressourcenzustand vor Operationen. Verwenden Sie Factory-Muster, um ordnungsgemäße Initialisierung sicherzustellen. Implementieren Sie ordnungsgemäße Aufräumsequenzen. Verwenden Sie Sprachkonstrukte, die den Lebenszyklus erzwingen (RAII, try-with-resources).
Häufige Konsequenzen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Datenkorruption Operationen auf nicht vorbereiteten Ressourcen beschädigen Daten. |
| Verfügbarkeit | Bereich: Abstürze Ungültige Zustandsoperationen verursachen Fehler. |
| Sicherheit | Bereich: Umgehung Verwendung von Ressourcen bevor Sicherheitsprüfungen abgeschlossen sind. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: Operationen auf Ressourcen in falscher Lebenszyklusphase
#include <stdio.h>
#include <stdlib.h>
// VERWUNDBAR: Verwendung vor Öffnen
FILE* global_log = NULL;
void log_event_vulnerable(const char* message) {
// VERWUNDBAR: global_log könnte noch nicht geöffnet sein
fprintf(global_log, "%s\n", message); // Absturz wenn NULL
}
void init_logging() {
global_log = fopen("app.log", "a");
}
// VERWUNDBAR: Verwendung nach Schließen
void process_file_vulnerable(const char* filename) {
FILE* file = fopen(filename, "r");
char buffer[1024];
fread(buffer, 1, sizeof(buffer), file);
fclose(file);
// VERWUNDBAR: Verwendung von file nach Schließen
fseek(file, 0, SEEK_SET); // Undefiniertes Verhalten
fread(buffer, 1, sizeof(buffer), file);
}
// VERWUNDBAR: Verwendung teilweise initialisierter Struktur
typedef struct {
int (*process)(void* data);
void* data;
int size;
} Handler;
void use_handler_vulnerable(Handler* h) {
// VERWUNDBAR: process könnte noch nicht gesetzt sein
h->process(h->data); // Absturz wenn process NULL
}
// VERWUNDBAR: Socket-Verwendung vor Verbindung
int socket_vulnerable() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
// VERWUNDBAR: Senden vor Verbindung
char msg[] = "Hello";
send(sock, msg, sizeof(msg), 0); // Fehlschlag - nicht verbunden
// Später: connect(sock, ...);
return sock;
}
// VERWUNDBAR: Speicherverwendung während Freigabe
void double_use_vulnerable() {
char* buffer = malloc(100);
strcpy(buffer, "data");
free(buffer);
// VERWUNDBAR: Speicher wird freigegeben, nicht sicher
// Selbst innerhalb derselben Funktion nach free
memset(buffer, 0, 100); // Use after free
}
# VERWUNDBAR: Python-Ressourcen-Lebenszyklus-Probleme
class VulnerableConnection:
def __init__(self):
self.socket = None
self.connected = False
def send(self, data):
# VERWUNDBAR: Könnte nicht verbunden sein
self.socket.send(data) # AttributeError oder Socket-Fehler
def connect(self, host, port):
self.socket = socket.socket()
self.socket.connect((host, port))
self.connected = True
# VERWUNDBAR: Datenbank vor Verbindung verwenden
class VulnerableDatabase:
def __init__(self):
self.connection = None
self.cursor = None
def query(self, sql):
# VERWUNDBAR: cursor existiert möglicherweise nicht
self.cursor.execute(sql) # AttributeError
return self.cursor.fetchall()
def connect(self, connstr):
self.connection = psycopg2.connect(connstr)
self.cursor = self.connection.cursor()
# VERWUNDBAR: Geschlossene Ressource verwenden
def process_file_vulnerable(filename):
f = open(filename, 'r')
content = f.read()
f.close()
# VERWUNDBAR: Datei ist geschlossen
f.seek(0) # ValueError: I/O operation on closed file
more_content = f.read()
# VERWUNDBAR: Session vor Authentifizierung verwenden
class VulnerableSession:
def __init__(self):
self.user = None
self.permissions = None
def get_user_data(self):
# VERWUNDBAR: Könnte vor authenticate() aufgerufen werden
return {
'username': self.user.username, # AttributeError
'permissions': self.permissions
}
def authenticate(self, username, password):
if verify_credentials(username, password):
self.user = load_user(username)
self.permissions = load_permissions(username)
// VERWUNDBAR: Java-Ressourcen-Lebenszyklus-Probleme
public class VulnerableLifecycle {
private Connection connection;
private PreparedStatement statement;
// VERWUNDBAR: Verwendung vor Initialisierung
public ResultSet query(String sql) throws SQLException {
// VERWUNDBAR: connection könnte null sein
statement = connection.prepareStatement(sql);
return statement.executeQuery();
}
public void connect(String url) throws SQLException {
connection = DriverManager.getConnection(url);
}
// VERWUNDBAR: Verwendung nach Schließen
public void processAndClose() throws SQLException {
ResultSet rs = statement.executeQuery();
connection.close();
// VERWUNDBAR: Verwendung von connection nach Schließen
while (rs.next()) { // Könnte fehlschlagen - Verbindung geschlossen
process(rs);
}
}
// VERWUNDBAR: Builder vor Fertigstellung verwendet
public static class VulnerableBuilder {
private String host;
private int port;
private Config config;
public void connect() {
// VERWUNDBAR: config könnte nicht gesetzt sein
Socket socket = new Socket(host, port);
socket.setSoTimeout(config.getTimeout()); // NPE
}
public VulnerableBuilder host(String h) {
this.host = h;
return this;
}
public VulnerableBuilder config(Config c) {
this.config = c;
return this;
}
}
}
// VERWUNDBAR: Objekt während Konstruktion verwenden
public class VulnerableDuringConstruction {
private final List<String> items = new ArrayList<>();
private Thread workerThread;
public VulnerableDuringConstruction() {
// VERWUNDBAR: Thread starten der 'this' verwendet bevor Konstruktion abgeschlossen
workerThread = new Thread(() -> {
// 'this' könnte nicht vollständig konstruiert sein
processItems();
});
workerThread.start();
}
public void processItems() {
for (String item : items) { // items könnte null sein
process(item);
}
}
}
// VERWUNDBAR: JavaScript-Ressourcen-Lebenszyklus-Probleme
class VulnerableService {
constructor() {
this.db = null;
this.initialized = false;
}
// VERWUNDBAR: Verwendung vor Initialisierung
async query(sql) {
// VERWUNDBAR: db könnte null sein
return await this.db.query(sql);
}
async initialize() {
this.db = await connectDatabase();
this.initialized = true;
}
// VERWUNDBAR: Verwendung nach Entsorgung
async dispose() {
await this.db.close();
this.db = null;
}
async fetchData() {
// Aufgerufen nach dispose
// VERWUNDBAR: db ist null
return await this.db.query('SELECT * FROM data');
}
}
// VERWUNDBAR: WebSocket in falschem Zustand verwendet
class VulnerableWebSocket {
constructor(url) {
this.ws = new WebSocket(url);
}
// VERWUNDBAR: Senden bevor Verbindung offen
send(message) {
// VERWUNDBAR: WebSocket könnte noch nicht OPEN sein
this.ws.send(JSON.stringify(message));
// Fehler: Noch im CONNECTING-Zustand
}
}
// VERWUNDBAR: Promise-basierte Initialisierung nicht abgewartet
class VulnerableAsync {
constructor() {
this.data = null;
this.init(); // VERWUNDBAR: Nicht abgewartet
}
async init() {
this.data = await fetchData(); // Braucht Zeit
}
process() {
// VERWUNDBAR: Könnte aufgerufen werden bevor init abgeschlossen
return this.data.map(item => item.value); // data ist null
}
}
Lösungscode
// SICHER: Ordnungsgemäßes Lebenszyklus-Management in C
#include <stdio.h>
#include <stdlib.h>
// SICHER: Zustandsverfolgung für Ressourcen
typedef enum {
LOG_UNINITIALIZED,
LOG_OPEN,
LOG_CLOSED
} LogState;
FILE* global_log = NULL;
LogState log_state = LOG_UNINITIALIZED;
void init_logging() {
global_log = fopen("app.log", "a");
if (global_log) {
log_state = LOG_OPEN;
}
}
void log_event_safe(const char* message) {
// SICHER: Zustand vor Verwendung prüfen
if (log_state != LOG_OPEN || global_log == NULL) {
return; // Oder initialisieren, oder Fehler
}
fprintf(global_log, "%s\n", message);
}
// SICHER: Ordnungsgemäßer Ressourcen-Lebenszyklus
void process_file_safe(const char* filename) {
FILE* file = fopen(filename, "r");
if (!file) return;
char buffer[1024];
fread(buffer, 1, sizeof(buffer), file);
// Verarbeiten während Datei offen
process_data(buffer);
// SICHER: Schließen wenn fertig, nicht danach verwenden
fclose(file);
file = NULL;
}
// SICHER: Handler-Zustand validieren
typedef struct {
int (*process)(void* data);
void* data;
int size;
int initialized;
} SafeHandler;
int use_handler_safe(SafeHandler* h) {
// SICHER: Vor Verwendung validieren
if (!h || !h->initialized || !h->process) {
return -1;
}
return h->process(h->data);
}
SafeHandler* create_handler(int (*proc)(void*), void* data, int size) {
SafeHandler* h = calloc(1, sizeof(SafeHandler));
if (!h) return NULL;
h->process = proc;
h->data = data;
h->size = size;
h->initialized = 1; // Als bereit markieren
return h;
}
// SICHER: Ordnungsgemäßer Socket-Lebenszyklus
int socket_safe() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) return -1;
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
// SICHER: Verbinden vor Senden
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock);
return -1;
}
// SICHER: Socket ist verbunden
char msg[] = "Hello";
send(sock, msg, sizeof(msg), 0);
return sock;
}
# SICHER: Python mit ordnungsgemäßem Lebenszyklus-Management
from enum import Enum, auto
import contextlib
class ConnectionState(Enum):
DISCONNECTED = auto()
CONNECTING = auto()
CONNECTED = auto()
CLOSED = auto()
class SafeConnection:
def __init__(self):
self.socket = None
self.state = ConnectionState.DISCONNECTED
def send(self, data):
# SICHER: Zustand validieren
if self.state != ConnectionState.CONNECTED:
raise RuntimeError(f"Kann nicht senden im Zustand {self.state}")
self.socket.send(data)
def connect(self, host, port):
if self.state != ConnectionState.DISCONNECTED:
raise RuntimeError("Bereits verbunden oder am Verbinden")
self.state = ConnectionState.CONNECTING
try:
self.socket = socket.socket()
self.socket.connect((host, port))
self.state = ConnectionState.CONNECTED
except Exception:
self.state = ConnectionState.DISCONNECTED
raise
def close(self):
if self.socket:
self.socket.close()
self.state = ConnectionState.CLOSED
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
# SICHER: Context-Manager für ordnungsgemäßen Lebenszyklus
class SafeDatabase:
def __init__(self, connstr):
self.connstr = connstr
self.connection = None
self.cursor = None
def connect(self):
self.connection = psycopg2.connect(self.connstr)
self.cursor = self.connection.cursor()
return self
def query(self, sql, params=None):
if not self.cursor:
raise RuntimeError("Nicht verbunden - connect() zuerst aufrufen")
self.cursor.execute(sql, params)
return self.cursor.fetchall()
def close(self):
if self.cursor:
self.cursor.close()
if self.connection:
self.connection.close()
def __enter__(self):
return self.connect()
def __exit__(self, *args):
self.close()
# SICHER: Context-Manager verwenden
def process_database():
with SafeDatabase("host=localhost") as db:
results = db.query("SELECT * FROM users")
# db automatisch geschlossen nach Block
# SICHER: Session mit Zustandsvalidierung
class SafeSession:
def __init__(self):
self.user = None
self.permissions = None
self.authenticated = False
def authenticate(self, username, password):
if verify_credentials(username, password):
self.user = load_user(username)
self.permissions = load_permissions(username)
self.authenticated = True
return True
return False
def get_user_data(self):
# SICHER: Zustand validieren
if not self.authenticated:
raise RuntimeError("Session nicht authentifiziert")
return {
'username': self.user.username,
'permissions': self.permissions
}
// SICHER: Java mit ordnungsgemäßem Lebenszyklus-Management
public class SafeLifecycle implements AutoCloseable {
private Connection connection;
private PreparedStatement statement;
private boolean connected = false;
public void connect(String url) throws SQLException {
connection = DriverManager.getConnection(url);
connected = true;
}
public ResultSet query(String sql) throws SQLException {
// SICHER: Zustand validieren
if (!connected || connection == null) {
throw new IllegalStateException("Nicht verbunden");
}
statement = connection.prepareStatement(sql);
return statement.executeQuery();
}
// SICHER: Vor Schließen verarbeiten
public void processAndClose() throws SQLException {
try {
ResultSet rs = statement.executeQuery();
while (rs.next()) {
process(rs);
}
} finally {
close();
}
}
@Override
public void close() throws SQLException {
connected = false;
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
}
// SICHER: Builder mit Validierung
public static class SafeBuilder {
private String host;
private int port;
private Config config;
private boolean hostSet = false;
private boolean portSet = false;
public SafeBuilder host(String h) {
this.host = h;
this.hostSet = true;
return this;
}
public SafeBuilder port(int p) {
this.port = p;
this.portSet = true;
return this;
}
public SafeBuilder config(Config c) {
this.config = c;
return this;
}
public Connection build() throws IllegalStateException {
// SICHER: Validieren dass alle erforderlichen Felder gesetzt sind
if (!hostSet || !portSet) {
throw new IllegalStateException("Host und Port erforderlich");
}
Config c = config != null ? config : Config.defaults();
return new Connection(host, port, c);
}
}
// SICHER: 'this' während Konstruktion nicht veröffentlichen
public class SafeDuringConstruction {
private final List<String> items = new ArrayList<>();
public SafeDuringConstruction() {
// Keine Threads im Konstruktor starten
}
// Factory-Methode für ordnungsgemäße Initialisierung
public static SafeDuringConstruction create() {
SafeDuringConstruction instance = new SafeDuringConstruction();
instance.startWorker(); // Nach Konstruktion abgeschlossen
return instance;
}
private void startWorker() {
Thread workerThread = new Thread(this::processItems);
workerThread.start();
}
}
// SICHER: JavaScript mit ordnungsgemäßem Lebenszyklus
class SafeService {
constructor() {
this.db = null;
this.state = 'uninitialized';
}
async initialize() {
this.state = 'initializing';
this.db = await connectDatabase();
this.state = 'ready';
}
async query(sql) {
// SICHER: Zustand validieren
if (this.state !== 'ready') {
throw new Error(`Service nicht bereit: ${this.state}`);
}
return await this.db.query(sql);
}
async dispose() {
if (this.db) {
await this.db.close();
this.db = null;
}
this.state = 'disposed';
}
// Factory-Methode stellt Initialisierung sicher
static async create() {
const service = new SafeService();
await service.initialize();
return service;
}
}
// SICHER: WebSocket mit Zustandsbehandlung
class SafeWebSocket {
constructor(url) {
this.ws = new WebSocket(url);
this.ready = false;
this.queue = [];
this.ws.onopen = () => {
this.ready = true;
// Warteschlangennachrichten senden
this.queue.forEach(msg => this.ws.send(msg));
this.queue = [];
};
}
send(message) {
const data = JSON.stringify(message);
if (this.ready && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
} else {
// Für wenn bereit in Warteschlange stellen
this.queue.push(data);
}
}
}
// SICHER: Async-Initialisierung mit Factory
class SafeAsync {
constructor() {
this.data = null;
}
async init() {
this.data = await fetchData();
}
process() {
if (!this.data) {
throw new Error('Nicht initialisiert');
}
return this.data.map(item => item.value);
}
static async create() {
const instance = new SafeAsync();
await instance.init();
return instance;
}
}
Ausgenutzt in der Praxis
Use-After-Close-Exploits
Datenbankverbindungen nach Schließen verwendet, was Datenkorruption verursacht.
Pre-Initialisierungs-Umgehung
Sicherheitsprüfungen umgangen durch Verwendung von Ressourcen vor Initialisierung.
Double-Free via Lebenszyklus
Speicherkorruption durch fehlerhaftes Lebenszyklus-Management.
Werkzeuge zum Testen/Ausnutzen
-
Zustandsmaschinen-Testframeworks.
-
Statische Analyse für Lebenszyklus-Verletzungen.
-
Fuzzing mit Lebenszyklus-bewussten Orakeln.
CVE-Beispiele
-
CVE-2019-11477: TCP-Ressourcen-Lebenszyklus-Probleme (SACK-Panik).
-
CVE-2020-12654: Verwendung nicht initialisierter Wireless-Treiber-Strukturen.
Referenzen
-
MITRE. "CWE-666: Operation on Resource in Wrong Phase of Lifetime." https://cwe.mitre.org/data/definitions/666.html
-
Zustandsmaschinen-Designmuster.