Unsachgemäße Prüfung auf abgelegte Privilegien
Beschreibung
Unsachgemäße Prüfung auf abgelegte Privilegien ist eine Schwachstelle, die auftritt, wenn ein Produkt versucht, Privilegien abzulegen, aber nicht prüft oder falsch prüft, ob das Ablegen erfolgreich war. Wenn Privilegienablegungsoperationen stillschweigend fehlschlagen, läuft die Anwendung weiterhin mit erhöhten Berechtigungen, was sensible Operationen möglicherweise unbefugtem Zugriff aussetzt. Diese Schwäche ist besonders gefährlich, weil der Code ordnungsgemäße Sicherheitspraktiken durch den Versuch, Privilegien abzulegen, zu implementieren scheint, aber das Versäumnis, den Erfolg zu verifizieren, bedeutet, dass die Sicherheitsmaßnahme unwirksam ist.
Risiko
Das Versäumnis, Privilegienablegungen zu verifizieren, erzeugt ein falsches Sicherheitsgefühl, während Anwendungen für Privilegieneskalationsangriffe anfällig bleiben. Privilegienablegungsfunktionen können aus verschiedenen Gründen fehlschlagen: unzureichende Berechtigungen zum Ändern von Privilegien, Race Conditions in Multithread-Umgebungen, Systemkonfigurationsprobleme oder plattformspezifische Verhaltensweisen, die von Entwicklererwartungen abweichen. Wenn diese Fehler unentdeckt bleiben, arbeitet die Anwendung mit erhöhten Privilegien, während Entwickler und Sicherheitsauditoren glauben, dass sie mit eingeschränktem Zugang läuft. Jede nachfolgende Schwachstelle in der Anwendung kann dann mit den beibehaltenen erhöhten Privilegien ausgenutzt werden. Dieses Risiko ist besonders schwer in setuid-Programmen und Daemons, wo die gesamte Anwendung mit falschen Privilegienstufen läuft.
Lösung
Verifizieren Sie immer die Rückgabewerte von Privilegienablegungsfunktionen und behandeln Sie Fehler angemessen. Nach dem Aufrufen von setuid(), setgid() oder ähnlichen Funktionen prüfen Sie, dass der Rückgabewert Erfolg anzeigt. Verifizieren Sie zusätzlich, dass die Privilegienänderung tatsächlich wirksam wurde, indem Sie getuid()/geteuid() oder äquivalente Funktionen aufrufen und mit erwarteten Werten vergleichen. Testen Sie, dass Sie abgelegte Privilegien nicht wiedererlangen können (z.B. verifizieren Sie, dass setuid(0) nach dem Ablegen von root fehlschlägt). In konkurrierenden Umgebungen achten Sie auf Race Conditions, bei denen mehrere Threads Privilegienoperationen storen könnten. Unter Windows bestätigen Sie, dass SeImpersonatePrivilege ordnungsgemäß zugewiesen ist und validieren Sie, dass ImpersonateNamedPipeClient() oder ähnliche Funktionen erfolgreich waren. Wenn Privilegienablegung fehlschlägt, beenden Sie das Programm anstatt mit erhöhten Privilegien fortzufahren. Implementieren Sie Logging für Privilegienoperationen zur Unterstützung bei Debugging und Sicherheitsaudits.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Bereich: Zugriffskontrolle Unentdeckte Privilegienbeibehaltung ermöglicht Angreifern den Zugriff auf Ressourcen mit erhöhten Privilegien. Wenn die Anwendung glaubt, Privilegien abgelegt zu haben, aber sie tatsächlich beibehält, erreicht jeder Exploit erhöhten Zugang anstatt des begrenzten Zugangs, den die Entwickler beabsichtigten. |
| Nicht-Abstreitbarkeit | Bereich: Zugriffskontrolle, Nicht-Abstreitbarkeit Das System kann Aktionen unter einer imitierten Identität protokollieren anstatt dem tatsächlichen Benutzer. Wenn Impersonation fehlschlägt, aber der Fehler nicht erkannt wird, werden Aktionen dem falschen Prinzipal zugeschrieben, was Audit-Trails kompromittiert. |
Beispielcode
Anfälliger Code (C/C++)
Die folgenden Beispiele demonstrieren unsachgemäße Privilegienablegungsverifikation:
// Anfällig: Keine Prüfung des setuid-Rückgabewerts
#include <unistd.h>
#include <sys/types.h>
void vulnerable_drop_privileges(uid_t target_uid) {
// Versuche Privilegien abzulegen
setuid(target_uid); // Rückgabewert ignoriert!
// Anfällig: Fährt fort unabhängig davon, ob Ablegung erfolgreich war
// Wenn setuid fehlschlug, läuft immer noch als root
process_untrusted_input();
}
// Anfällig: Unvollständige Verifikation
void vulnerable_partial_check(uid_t target_uid) {
if (setuid(target_uid) == 0) {
// Geprüft, dass setuid Erfolg zurückgab, aber...
// Anfällig: Hat nicht verifiziert, dass sich die TATSÄCHLICHE uid geändert hat
// Auf manchen Systemen kann setuid(x) erfolgreich sein, aber euid nicht ändern
process_untrusted_input();
}
}
// Anfällig: Verifiziert nicht Unfähigkeit, Privilegien wiederzuerlangen
void vulnerable_no_regain_check(uid_t target_uid) {
if (setuid(target_uid) != 0) {
exit(1); // Fehler behandeln
}
if (getuid() != target_uid) {
exit(1); // Verifizieren, dass uid geändert wurde
}
// Anfällig: Hat nicht verifiziert, dass wir root nicht wiedererlangen können
// Saved-set-uid könnte immer noch 0 sein
process_untrusted_input();
// Angreifer könnte möglicherweise setuid(0) auslösen, um root wiederzuerlangen
}
// Anfällig: Windows-Impersonation ohne Verifikation
#include <windows.h>
bool DoSecureStuff(HANDLE hPipe) {
// Anfällig: Rückgabewert nicht geprüft!
ImpersonateNamedPipeClient(hPipe);
// Wenn Impersonation fehlschlug, läuft immer noch als Original (wahrscheinlich SYSTEM)
HANDLE hFile = CreateFile(
TEXT("C:\\Users\\target\\secret.txt"),
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
// ... Datei verarbeiten ...
RevertToSelf();
return true;
}
# Anfällig: Python-Privilegienablegung ohne Verifikation
import os
def vulnerable_drop_privileges(uid, gid):
try:
os.setgid(gid)
os.setuid(uid)
# Anfällig: Keine Verifikation, dass Ablegung tatsächlich funktionierte
except OSError:
pass # Anfällig: Ignoriert Fehler stillschweigend!
# Fährt mit Ausführung fort - könnte immer noch root sein
handle_untrusted_data()
Korrigierter Code (C/C++)
// Korrigiert: Gründliche Privilegienablegungsverifikation
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int secure_drop_privileges(uid_t target_uid, gid_t target_gid) {
// Zuerst zusätzliche Gruppen ablegen
if (setgroups(0, NULL) != 0) {
fprintf(stderr, "Ablegen zusätzlicher Gruppen fehlgeschlagen: %s\n",
strerror(errno));
return -1;
}
// Gruppenprivileg ablegen
if (setgid(target_gid) != 0) {
fprintf(stderr, "Ablegen des Gruppenprivilegs fehlgeschlagen: %s\n",
strerror(errno));
return -1;
}
// Gruppenänderung verifizieren
if (getgid() != target_gid || getegid() != target_gid) {
fprintf(stderr, "Verifikation der Gruppenprivilegienablegung fehlgeschlagen\n");
return -1;
}
// Benutzerprivileg ablegen
if (setuid(target_uid) != 0) {
fprintf(stderr, "Ablegen des Benutzerprivilegs fehlgeschlagen: %s\n",
strerror(errno));
return -1;
}
// Benutzeränderung verifizieren - ALLE uid-Werte prüfen
if (getuid() != target_uid || geteuid() != target_uid) {
fprintf(stderr, "Verifikation der Benutzerprivilegienablegung fehlgeschlagen\n");
return -1;
}
// KRITISCH: Verifizieren, dass wir Root-Privilegien nicht wiedererlangen können
if (target_uid != 0) {
if (setuid(0) != -1) {
// Dies sollte fehlgeschlagen sein! Saved-set-uid ist immer noch 0
fprintf(stderr, "SICHERHEIT: Kann immer noch Root-Privilegien wiedererlangen!\n");
return -1;
}
// Gut: setuid(0) fehlgeschlagen wie erwartet
}
return 0;
}
int main(void) {
// Privilegien mit vollständiger Verifikation ablegen
if (secure_drop_privileges(NOBODY_UID, NOBODY_GID) != 0) {
fprintf(stderr, "Privilegienablegung fehlgeschlagen - beende\n");
exit(EXIT_FAILURE);
}
// Sicher: Jetzt verifiziert als unprivilegierter Benutzer laufend
process_untrusted_input();
return 0;
}
// Korrigiert: Windows-Impersonation mit Verifikation
#include <windows.h>
#include <stdio.h>
bool DoSecureStuffSafe(HANDLE hPipe) {
// Rückgabewert der Impersonation prüfen
if (!ImpersonateNamedPipeClient(hPipe)) {
DWORD error = GetLastError();
fprintf(stderr, "Impersonation fehlgeschlagen: %lu\n", error);
return false;
}
// Verifizieren, dass Impersonation erfolgreich war durch Prüfung des aktuellen Tokens
HANDLE hToken = NULL;
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken)) {
RevertToSelf();
fprintf(stderr, "Öffnen des Thread-Tokens fehlgeschlagen\n");
return false;
}
// Verifizieren, dass wir nicht als privilegiertes Konto laufen
TOKEN_USER *tokenUser = NULL;
DWORD tokenInfoLength = 0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &tokenInfoLength);
tokenUser = (TOKEN_USER*)malloc(tokenInfoLength);
if (!GetTokenInformation(hToken, TokenUser, tokenUser,
tokenInfoLength, &tokenInfoLength)) {
free(tokenUser);
CloseHandle(hToken);
RevertToSelf();
return false;
}
// Prüfen, ob immer noch als SYSTEM laufend (SID S-1-5-18)
PSID systemSid;
SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
AllocateAndInitializeSid(&ntAuthority, 1, SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0, &systemSid);
if (EqualSid(tokenUser->User.Sid, systemSid)) {
// Läuft immer noch als SYSTEM - Impersonation funktionierte nicht korrekt
FreeSid(systemSid);
free(tokenUser);
CloseHandle(hToken);
RevertToSelf();
fprintf(stderr, "Läuft nach Impersonation immer noch als SYSTEM\n");
return false;
}
FreeSid(systemSid);
free(tokenUser);
CloseHandle(hToken);
// Sicher: Jetzt verifiziert als Client impersoniert
HANDLE hFile = CreateFile(
TEXT("C:\\Users\\target\\secret.txt"),
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
// ... Datei verarbeiten ...
RevertToSelf();
return true;
}
# Korrigiert: Python-Privilegienablegung mit Verifikation
import os
import sys
def secure_drop_privileges(uid, gid):
"""Privilegien mit gründlicher Verifikation ablegen"""
original_uid = os.getuid()
original_gid = os.getgid()
# Zusätzliche Gruppen ablegen
try:
os.setgroups([])
except OSError as e:
print(f"Ablegen zusätzlicher Gruppen fehlgeschlagen: {e}", file=sys.stderr)
return False
# Gruppenprivileg ablegen
try:
os.setgid(gid)
except OSError as e:
print(f"Setzen der gid auf {gid} fehlgeschlagen: {e}", file=sys.stderr)
return False
# Gruppenänderung verifizieren
if os.getgid() != gid or os.getegid() != gid:
print("Verifikation der Gruppenprivilegienablegung fehlgeschlagen", file=sys.stderr)
return False
# Benutzerprivileg ablegen
try:
os.setuid(uid)
except OSError as e:
print(f"Setzen der uid auf {uid} fehlgeschlagen: {e}", file=sys.stderr)
return False
# Benutzeränderung verifizieren
if os.getuid() != uid or os.geteuid() != uid:
print("Verifikation der Benutzerprivilegienablegung fehlgeschlagen", file=sys.stderr)
return False
# Verifizieren, dass wir ursprüngliche Privilegien nicht wiedererlangen können
if uid != 0 and original_uid == 0:
try:
os.setuid(0)
# Wenn wir hier ankommen, könnten wir root wiedererlangen!
print("SICHERHEIT: Kann immer noch Root-Privilegien wiedererlangen!", file=sys.stderr)
return False
except PermissionError:
# Erwartet - wir sollten setuid(0) nicht können
pass
return True
def main():
if not secure_drop_privileges(NOBODY_UID, NOBODY_GID):
print("Privilegienablegung fehlgeschlagen - beende", file=sys.stderr)
sys.exit(1)
# Sicher: Jetzt verifiziert als unprivilegierter Benutzer laufend
handle_untrusted_data()
Die Korrektur verifiziert Rückgabewerte, bestätigt den tatsächlichen Privilegienzustand nach Operationen und testet, dass erhöhte Privilegien nicht wiedererlangt werden können.
Ausgenutzt in der Praxis
Setuid-Programm-Privilegienbeibehaltung (Unix-Systeme, Historisch)
Mehrere setuid-Programme wurden ausgenutzt, weil sie nicht verifizierten, dass Privilegienablegungsoperationen erfolgreich waren. Die CVE-2006-4447-Schwachstelle demonstrierte, wie Programme, die Rückgabewerte von Privilegienablegungsfunktionen nicht prüften, ausgenutzt werden könnten, wenn die Ablegung stillschweigend fehlschlug.
Container-Privilegieneskalation (Container-Plattformen, 2018-Gegenwart)
Container-Runtimes haben Schwachstellen erfahren, bei denen Privilegienablegung innerhalb von Containern in bestimmten Konfigurationen fehlschlug, aber der Fehler nicht erkannt wurde. Dies ermöglichte Container-Escape, wenn der containerisierte Prozess glaubte, unprivilegiert zu laufen, aber tatsächlich Host-Privilegien beibehielt.
Webserver-Impersonationsfehler (Windows IIS, Historisch)
Windows-Webserver, die Impersonation verwendeten, um Anfragen als begrenzte Benutzer auszuführen, haben Schwachstellen erfahren, wenn Impersonation stillschweigend fehlschlug. Anfragen, die als begrenzter Benutzer hätten laufen sollen, wurden stattdessen mit den höheren Privilegien des Worker-Prozesses ausgeführt.
Tools zum Testen/Ausnutzen
-
strace — Systemaufruf-Tracer, der setuid/setgid-Aufrufe und ihre Rückgabewerte überwachen kann.
-
ltrace — Bibliotheksaufruf-Tracer zur Analyse privilegienbezogener Funktionsaufrufe.
-
gdb — Debugger zur Analyse des Privilegienzustands während der Programmausführung.
CVE-Beispiele
-
CVE-2006-4447 — Privilegienablegungsfunktionen ohne Rückgabewertverifikation aufgerufen.
-
CVE-2006-2916 — Versäumnis zu validieren, dass Privilegienaufgabe erfolgreich war.
-
CVE-2011-1485 — PolicyKit-Race-Condition, bei der Privilegienablegungsverifikation umgangen werden könnte.
Referenzen
-
MITRE Corporation. "CWE-273: Improper Check for Dropped Privileges." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/273.html
-
Chen, H., Wagner, D., and Dean, D. "Setuid Demystified." USENIX Security Symposium. https://www.usenix.org/legacy/events/sec02/full_papers/chen/chen.pdf
-
CERT C Secure Coding Standard. "POS37-C. Ensure that privilege relinquishment is successful." https://wiki.sei.cmu.edu/confluence/display/c/POS37-C
-
Microsoft. "Security Considerations for Impersonation." https://docs.microsoft.com/en-us/windows/win32/secauthz/security-considerations-for-impersonation