Unverifizierte Eigentümerschaft
Beschreibung
Unverifizierte Eigentümerschaft ist eine Schwachstelle, die auftritt, wenn ein Produkt nicht ordnungsgemäß verifiziert, dass eine kritische Ressource der richtigen Entität gehört, bevor Zugriff oder Manipulation erlaubt werden. Diese Schwäche stellt das Versäumnis dar zu bestätigen, dass der Akteur, der eine Operation anfordert, tatsächlich die Zielressource besitzt. Anders als allgemeine Autorisierungsprüfungen, die Berechtigungen verifizieren, bestätigt Eigentümerschaftsverifikation spezifisch, dass die Ressource der anfragenden Partei gehört. Dies ist kritisch für Operationen wie Prozessbeendigung, Dateilöschung oder Ressourcenfreigabe, bei denen das Einwirken auf Ressourcen einer anderen Entität erheblichen Schäden verursachen könnte.
Risiko
Das Versäumnis, Eigentümerschaft vor Ressourcenoperationen zu verifizieren, erzeugt erhebliche Privilegieneskalations- und Denial-of-Service-Risiken. Angreifer können Prozesse anderer Benutzer beenden, Dateien löschen, die ihnen nicht gehören, oder Ressourcen freigeben, die anderen Prinzipalen gehören. In Mehrbenutzersystemen ermöglicht dies Cross-User-Angriffe, bei denen ein Benutzer die Arbeit eines anderen storen oder kompromittieren kann. Unix-Socket-Operationen ohne Eigentümerschaftsverifikation können Passwörter offenlegen, die zwischen Prozessen übertragen werden. Zugriff auf spezielle Gerätedateien ohne Eigentümerschaftsprüfungen kann zu Root-Zugang führen. Das Risiko ist besonders schwerwiegend für destruktive Operationen, die nicht rückgängig gemacht werden können, und für gemeinsam genutzte Ressourcen, bei denen Eigentumskonfusion zu Privilegieneskalation führt.
Lösung
Verifizieren Sie immer die Ressourceneigentümerschaft, bevor Zugriffs- oder Modifikationsoperationen erlaubt werden. Bevor Sie einen Prozess beenden, verifizieren Sie, dass der anfragende Benutzer diesen Prozess besitzt. Bevor Sie Dateien modifizieren oder löschen, bestätigen Sie, dass die Dateieigentümerschaft mit dem anfragenden Benutzer übereinstimmt. Implementieren Sie Eigentümerschaftsprüfungen als obligatorischen Schritt in der Ressourcenzugangskontrolle, getrennt von, aber ergänzend zu Berechtigungsprüfungen. Verwenden Sie plattformbereitgestellte Eigentümerschaftsverifikationsmechanismen (stat() für Dateien, /proc für Prozesse) anstatt benutzerdefinierte Prüfungen zu implementieren. Für sensible Ressourcen wie Unix-Sockets verifizieren Sie, dass beide Endpunkte der Verbindung ihre jeweiligen Objekte besitzen. Wenden Sie das Prinzip der Privilegientrennung an, indem Sie mehrere Bedingungen erfordern, bevor Ressourcenzugriff erlaubt wird. Protokollieren Sie Eigentümerschaftsverifikationsfehler für Sicherheitsüberwachung.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Angreifer könnten unbefugten Zugang zu Systemressourcen erlangen, indem sie die Eigentümerschaftsverifikation umgehen. Dies ermöglicht Privilegieneskalation, Cross-User-Angriffe, Denial of Service durch Ressourcenzerstörung und unbefugten Zugang zu sensiblen Daten oder Funktionalität. |
Beispielcode
Anfälliger Code (Python)
Die folgenden Beispiele demonstrieren Schwachstellen durch unverifizierte Eigentümerschaft:
# Anfällig: Prozessbeendigung ohne Eigentümerschaftsprüfung
import os
import signal
def vulnerable_kill_process(process_id):
# Anfällig: Keine Verifikation der Prozesseigentümerschaft
os.kill(process_id, signal.SIGKILL)
# Jeder Benutzer kann jeden Prozess beenden!
def vulnerable_delete_file(file_path):
# Anfällig: Keine Eigentümerschaftsverifikation
if os.path.exists(file_path):
os.remove(file_path)
# Benutzer kann Dateien löschen, die ihm nicht gehören
class VulnerableSessionManager:
def terminate_session(self, session_id):
# Anfällig: Verifiziert keine Sitzungseigentümerschaft
session = self.sessions.get(session_id)
if session:
session.terminate()
# Jeder Benutzer kann jede Sitzung beenden
// Anfällig: C-Programm ohne Eigentümerschaftsverifikation
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
void vulnerable_kill(pid_t pid) {
// Anfällig: Keine Eigentümerschaftsprüfung
kill(pid, SIGKILL);
// Beendet jeden Prozess wenn Berechtigungen es erlauben
}
int vulnerable_socket_auth(int socket_fd) {
// Anfällig: Verifiziert Socket-Eigentümer nicht
char password[256];
read(socket_fd, password, sizeof(password));
// Angreifer könnte Socket erstellen und Passwort empfangen
// das für einen anderen Prozess bestimmt war
return verify_password(password);
}
int vulnerable_device_access(const char *device_path) {
// Anfällig: Keine Eigentümerschaftsverifikation für spezielles Gerät
int fd = open(device_path, O_RDWR);
if (fd >= 0) {
// Benutzer erhält Zugang zu Gerät, das ihm möglicherweise nicht gehört
ioctl(fd, PRIVILEGED_OPERATION, NULL);
close(fd);
}
return 0;
}
// Anfällig: Java ohne Eigentümerschaftsverifikation
public class VulnerableResourceManager {
public void terminateThread(long threadId) {
// Anfällig: Keine Eigentümerschaftsverifikation
Thread thread = findThread(threadId);
if (thread != null) {
thread.interrupt();
}
// Kann Threads unterbrechen, die anderen Komponenten gehören
}
public void deleteUserFile(String userId, String fileId) {
// Anfällig: Verwendet userId aus Anfrage ohne Verifikation
File file = fileRepository.findById(fileId);
fileRepository.delete(file);
// Verifiziert nicht, dass userId tatsächlich die Datei besitzt
}
}
Korrigierter Code (Python)
# Korrigiert: Prozessbeendigung mit Eigentümerschaftsverifikation
import os
import signal
def get_process_owner(pid):
"""Ermittelt die UID des Prozesseigentümers aus /proc"""
try:
with open(f'/proc/{pid}/status', 'r') as f:
for line in f:
if line.startswith('Uid:'):
return int(line.split()[1]) # Reale UID
except (FileNotFoundError, PermissionError, ValueError):
return None
return None
def secure_kill_process(process_id):
"""Beendet Prozess nur nach Verifikation der Eigentümerschaft"""
current_uid = os.getuid()
process_owner = get_process_owner(process_id)
if process_owner is None:
raise ProcessNotFoundError(f"Prozess {process_id} nicht gefunden")
# Korrigiert: Eigentümerschaft vor Beendigung verifizieren
if process_owner != current_uid:
raise PermissionError(
f"Kann Prozess {process_id} nicht beenden: "
f"gehört UID {process_owner}, nicht aktuellem Benutzer"
)
os.kill(process_id, signal.SIGKILL)
def secure_delete_file(file_path, requesting_uid):
"""Löscht Datei nur nach Verifikation der Eigentümerschaft"""
try:
stat_info = os.stat(file_path)
except FileNotFoundError:
raise FileNotFoundError(f"Datei nicht gefunden: {file_path}")
# Korrigiert: Eigentümerschaft verifizieren
if stat_info.st_uid != requesting_uid:
raise PermissionError(
f"Kann {file_path} nicht löschen: "
f"gehört UID {stat_info.st_uid}, nicht {requesting_uid}"
)
os.remove(file_path)
class SecureSessionManager:
def terminate_session(self, session_id, requesting_user):
"""Beendet Sitzung nur wenn im Besitz des anfragenden Benutzers"""
session = self.sessions.get(session_id)
if session is None:
raise SessionNotFoundError(session_id)
# Korrigiert: Sitzungseigentümerschaft verifizieren
if session.owner_id != requesting_user.id:
# Auf Admin-Überschreibung prüfen
if not requesting_user.has_role('admin'):
raise PermissionError(
f"Benutzer {requesting_user.id} besitzt Sitzung {session_id} nicht"
)
session.terminate()
// Korrigiert: C-Programm mit Eigentümerschaftsverifikation
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
uid_t get_process_owner(pid_t pid) {
char path[256];
struct stat st;
snprintf(path, sizeof(path), "/proc/%d", pid);
if (stat(path, &st) != 0) {
return (uid_t)-1;
}
return st.st_uid;
}
int secure_kill(pid_t pid) {
uid_t current_uid = getuid();
uid_t process_owner = get_process_owner(pid);
if (process_owner == (uid_t)-1) {
fprintf(stderr, "Prozess %d nicht gefunden\n", pid);
return -1;
}
// Korrigiert: Eigentümerschaft verifizieren
if (process_owner != current_uid) {
fprintf(stderr, "Kann Prozess %d nicht beenden: gehört UID %d\n",
pid, process_owner);
return -1;
}
return kill(pid, SIGKILL);
}
int secure_socket_auth(int socket_fd) {
struct stat socket_stat;
uid_t current_uid = getuid();
// Korrigiert: Socket-Eigentümerschaft vor Empfang sensibler Daten verifizieren
if (fstat(socket_fd, &socket_stat) != 0) {
return -1;
}
if (socket_stat.st_uid != current_uid) {
fprintf(stderr, "Socket gehört nicht dem aktuellen Benutzer\n");
return -1;
}
char password[256];
read(socket_fd, password, sizeof(password));
return verify_password(password);
}
int secure_device_access(const char *device_path, uid_t expected_owner) {
struct stat device_stat;
// Korrigiert: Geräteeigentümerschaft vor Zugriff verifizieren
if (stat(device_path, &device_stat) != 0) {
return -1;
}
if (device_stat.st_uid != expected_owner) {
fprintf(stderr, "Gerät gehört nicht dem erwarteten Benutzer\n");
return -1;
}
int fd = open(device_path, O_RDWR);
if (fd >= 0) {
ioctl(fd, PRIVILEGED_OPERATION, NULL);
close(fd);
}
return 0;
}
// Korrigiert: Java mit Eigentümerschaftsverifikation
public class SecureResourceManager {
public void terminateThread(User requester, long threadId) {
Thread thread = findThread(threadId);
if (thread == null) {
throw new ThreadNotFoundException(threadId);
}
// Korrigiert: Thread-Eigentümerschaft über Thread-Kontext verifizieren
ThreadOwnership ownership = threadRegistry.getOwnership(threadId);
if (!ownership.getOwnerId().equals(requester.getId())) {
throw new UnauthorizedAccessException(
"Benutzer " + requester.getId() + " besitzt Thread " + threadId + " nicht"
);
}
thread.interrupt();
}
public void deleteUserFile(User requester, String fileId) {
File file = fileRepository.findById(fileId)
.orElseThrow(() -> new FileNotFoundException(fileId));
// Korrigiert: Eigentümerschaft aus Dateidatensatz verifizieren, nicht aus Anfrage
if (!file.getOwnerId().equals(requester.getId())) {
auditLog.logUnauthorizedAccess(requester, "delete_file", fileId);
throw new UnauthorizedAccessException(
"Benutzer besitzt Datei " + fileId + " nicht"
);
}
fileRepository.delete(file);
auditLog.logFileDeletion(requester, fileId);
}
}
Die Korrektur verifiziert, dass der anfragende Benutzer die Zielressource besitzt, bevor die Operation erlaubt wird.
Ausgenutzt in der Praxis
Unix-Socket-Passwortoffenlegung (Unix-Systeme, Historisch)
CVE-2001-0178 dokumentierte UNIX-Socket-Kommunikationen, bei denen die Eigentümerschaft nicht verifiziert wurde, was Angreifern ermöglichte, für andere Prozesse bestimmte Passwörter zu empfangen, indem sie Sockets mit vorhersagbaren Namen erstellten.
Spezialgerätezugriff (Unix-Systeme, Historisch)
CVE-2004-2012 dokumentierte den Zugriff auf spezielle Gerätedateien ohne Eigentümerschaftsverifikation, was unbefugten Benutzern ermöglichte, durch unzureichend geschützte Geräteknoten Root-Zugang zu erlangen.
Prozessbeendigungsangriffe (Mehrbenutzersysteme, Fortlaufend)
Anwendungen, die Prozesse ohne Eigentümerschaftsverifikation beenden, wurden für Denial-of-Service-Angriffe ausgenutzt, bei denen Benutzer Prozesse anderer Benutzer oder Systemprozesse beenden.
Tools zum Testen/Ausnutzen
-
ps/pgrep — Prozesslistentools zur Identifikation der Prozesseigentümerschaft.
-
stat — Dateistatustool zur Prüfung der Ressourceneigentümerschaft.
-
lsof — Tool zum Auflisten offener Dateien und Sockets mit Eigentümerschaftsinformationen.
CVE-Beispiele
-
CVE-2001-0178 — UNIX-Socket-Eigentümer wurde für Passwortübertragung nicht verifiziert.
-
CVE-2004-2012 — Spezielles Gerät-Eigentümer nicht geprüft, was Root-Zugang ermöglichte.
Referenzen
-
MITRE Corporation. "CWE-283: Unverified Ownership." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/283.html
-
OWASP Foundation. "Broken Access Control." OWASP Top 10 2021. https://owasp.org/Top10/A01_2021-Broken_Access_Control/
-
CERT C Secure Coding Standard. "FIO16-C. Canonicalize path names before validating them." https://wiki.sei.cmu.edu/confluence/display/c/FIO16-C