Unsichere Operation auf Windows Junction / Mount Point
Beschreibung
Unsichere Operation auf Windows Junction / Mount Point tritt auf, wenn ein Produkt eine Datei oder ein Verzeichnis öffnet, ohne ordnungsgemäß zu verhindern, dass es mit einem Junction oder Mount Point assoziiert ist, der auf Ressourcen außerhalb der beabsichtigten Kontrollsphäre zeigt. Diese Schwachstelle ermöglicht es Angreifern, Windows NTFS Reparse Points (Junctions und Mount Points) auszunutzen. Ein privilegiertes Programm, das auf eine Datei zugreift, kann dazu verleitet werden, unautorisierte Dateien zu lesen, zu schreiben oder zu löschen, wenn ein Angreifer das Ziel durch einen Hard Link auf eine sensible Datei ersetzt. Dies ermöglicht Rechteeskalation und Datenmanipulationsangriffe.
Risiko
Junction- und Mount-Point-Angriffe haben schwerwiegende Auswirkungen. Rechteeskalation durch Dateimanipulation. Beliebiges Dateilesen durch privilegierte Prozesse. Beliebiges Dateischreiben oder -ändern. Löschung kritischer Systemdateien. Permanenter Denial of Service durch Dateilöschung. Ausnutzung von Installern zur Codeausführung. Umgehung von Zugangskontrollen. Hohe Wahrscheinlichkeit, wenn privilegierte Programme auf benutzerbeschreibbare Verzeichnisse zugreifen.
Lösung
Bei der Entwicklung von Software mit anderen Rechten als der Ausführende prüfen, ob Dateien, mit denen interagiert wird, keine unsachgemäßen Hard Links oder Mount Points sind, während der Architektur- und Designphase. Windows-Befehle wie dir /al /s /b oder PowerShells LinkType-Filter verwenden, um Junctions zu erkennen. Dateiauthentifizierung über Signierung implementieren. Sicherstellen, dass Prüfungen atomar mit Dateiaktionen sind, um TOCTOU-Race-Conditions während der Implementierungsphase zu vermeiden.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Angreifer können beliebige Dateien lesen, indem sie benutzerkontrollierte Ordner durch Mount Points und Hard Links ersetzen. |
| Integrität | Bereich: Integrität Beliebige Dateimodifikation möglich, insbesondere Installer-Rollback-Dateien, die bei Neuinstallation ausgeführt werden. |
| Verfügbarkeit | Bereich: Verfügbarkeit Löschung kritischer Dateien bei Ausführung als SYSTEM/Admin kann permanenten Denial of Service verursachen. |
Beispielcode
Verwundbarer Code
// VERWUNDBAR: C-Code ohne Junction/Mount-Point-Schutz
#include <windows.h>
#include <stdio.h>
// VERWUNDBAR: Keine Junction-Point-Erkennung
BOOL vulnerable_write_log(const char* log_dir, const char* message) {
char log_path[MAX_PATH];
snprintf(log_path, MAX_PATH, "%s\\app.log", log_dir);
// VERWUNDBAR: Öffnet Datei ohne Prüfung auf Reparse Points
HANDLE hFile = CreateFileA(
log_path,
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
// Angriffsszenario:
// 1. Angreifer erstellt Junction: log_dir -> C:\Windows\System32
// 2. Privilegierter Prozess schreibt in log_dir\app.log
// 3. Schreibt tatsächlich in C:\Windows\System32\app.log
// 4. Beliebiges Dateischreiben im Systemverzeichnis
DWORD written;
WriteFile(hFile, message, strlen(message), &written, NULL);
CloseHandle(hFile);
return TRUE;
}
// VERWUNDBAR: Installer-Bereinigung ohne Junction-Schutz
void vulnerable_cleanup_temp(const char* temp_dir) {
char search_path[MAX_PATH];
snprintf(search_path, MAX_PATH, "%s\\*", temp_dir);
WIN32_FIND_DATAA find_data;
HANDLE hFind = FindFirstFileA(search_path, &find_data);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (strcmp(find_data.cFileName, ".") == 0 ||
strcmp(find_data.cFileName, "..") == 0) {
continue;
}
char file_path[MAX_PATH];
snprintf(file_path, MAX_PATH, "%s\\%s", temp_dir, find_data.cFileName);
// VERWUNDBAR: Löscht ohne Prüfung auf Reparse Points
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
RemoveDirectoryA(file_path); // Kann echtes Verzeichnis löschen
} else {
DeleteFileA(file_path); // Kann über Hard Link löschen
}
// Angriffsszenario:
// 1. Junction erstellen: temp_dir\subdir -> C:\Windows\System32
// 2. Oder Hard Link erstellen: temp_dir\file.txt -> C:\Windows\System32\config.sys
// 3. Privilegierte Bereinigung löscht Systemdateien
} while (FindNextFileA(hFind, &find_data));
FindClose(hFind);
}
}
// VERWUNDBAR: Service-Konfigurationsaktualisierung
BOOL vulnerable_update_config(const char* user_config_dir) {
char config_path[MAX_PATH];
snprintf(config_path, MAX_PATH, "%s\\service.conf", user_config_dir);
// VERWUNDBAR: Kopiert Benutzerdatei an Systemstandort ohne Validierung
char system_config[] = "C:\\ProgramData\\MyService\\service.conf";
// Angriff: user_config_dir\service.conf ist Hard Link auf bösartige Datei
// oder user_config_dir ist Junction auf angreiferkontrollierten Speicherort
return CopyFileA(config_path, system_config, FALSE);
}
// VERWUNDBAR: C#-Code ohne Symlink-Schutz
using System;
using System.IO;
public class VulnerableFileOperations
{
// VERWUNDBAR: Keine Prüfung auf Reparse Points
public void VulnerableWriteData(string directory, string data)
{
string filePath = Path.Combine(directory, "data.txt");
// VERWUNDBAR: Schreibt ohne Validierung des Ziels
File.WriteAllText(filePath, data);
// Angriff: directory ist Junction, die anderswohin zeigt
// Privilegierter Prozess schreibt an unbeabsichtigten Speicherort
}
// VERWUNDBAR: Verzeichnislöschung ohne Junction-Prüfung
public void VulnerableCleanupDirectory(string path)
{
// VERWUNDBAR: Löscht rekursiv ohne Prüfung auf Junctions
if (Directory.Exists(path))
{
Directory.Delete(path, true); // recursive = true
// Angriff:
// 1. Angreifer erstellt: path\subdir -> C:\Windows\System32
// 2. Delete(path, true) folgt der Junction
// 3. System32-Inhalte gelöscht
}
}
// VERWUNDBAR: Ausnutzung des Installer-Rollbacks
public void VulnerableInstallFile(string sourcePath, string destPath)
{
// VERWUNDBAR: Keine Verifikation des Quellstandorts
File.Copy(sourcePath, destPath, true);
// Angriff: sourcePath zeigt auf bösartige Datei über Hard Link
// Installer kopiert bösartigen Inhalt an geschützten Speicherort
}
// VERWUNDBAR: Service schreibt in benutzerkontrollierbaren Pfad
public void VulnerableServiceLog(string userLogDir, string message)
{
// Service läuft als SYSTEM
string logPath = Path.Combine(userLogDir, "service.log");
// VERWUNDBAR: Benutzer kann über Junction umleiten
using (StreamWriter sw = File.AppendText(logPath))
{
sw.WriteLine(message);
}
}
}
Sichere Lösung
// SICHER: C-Code mit Junction/Mount-Point-Schutz
#include <windows.h>
#include <stdio.h>
// SICHER: Prüfen ob Pfad einen Reparse Point enthält
BOOL is_reparse_point(const char* path) {
DWORD attrs = GetFileAttributesA(path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
return FALSE;
}
return (attrs & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
}
// SICHER: Gesamten Pfad auf Junctions prüfen
BOOL path_contains_junction(const char* path) {
char temp_path[MAX_PATH];
strncpy(temp_path, path, MAX_PATH);
// SICHER: Jede Komponente des Pfads prüfen
char* component = temp_path;
while (*component) {
char* next_sep = strchr(component, '\\');
if (next_sep) {
*next_sep = '\0';
}
if (is_reparse_point(temp_path)) {
return TRUE; // Junction im Pfad gefunden
}
if (next_sep) {
*next_sep = '\\';
component = next_sep + 1;
} else {
break;
}
}
return FALSE;
}
// SICHER: Sicheres Dateischreiben mit Junction-Schutz
BOOL safe_write_log(const char* log_dir, const char* message) {
// SICHER: Auf Junctions im Pfad prüfen
if (path_contains_junction(log_dir)) {
SetLastError(ERROR_REPARSE_POINT_ENCOUNTERED);
return FALSE;
}
char log_path[MAX_PATH];
snprintf(log_path, MAX_PATH, "%s\\app.log", log_dir);
// SICHER: Mit Flag öffnen, das bei Reparse Points fehlschlägt
HANDLE hFile = CreateFileA(
log_path,
GENERIC_WRITE,
0,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT, // Reparse erkennen
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
// SICHER: Prüfen ob Datei ein Reparse Point ist
BY_HANDLE_FILE_INFORMATION file_info;
if (GetFileInformationByHandle(hFile, &file_info)) {
if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
CloseHandle(hFile);
SetLastError(ERROR_REPARSE_POINT_ENCOUNTERED);
return FALSE;
}
}
DWORD written;
WriteFile(hFile, message, strlen(message), &written, NULL);
CloseHandle(hFile);
return TRUE;
}
// SICHER: Sichere Verzeichnisbereinigung
void safe_cleanup_temp(const char* temp_dir) {
// SICHER: Prüfen ob temp_dir selbst eine Junction ist
if (is_reparse_point(temp_dir)) {
// SICHER: Nur die Junction entfernen, ihr nicht folgen
RemoveDirectoryA(temp_dir);
return;
}
char search_path[MAX_PATH];
snprintf(search_path, MAX_PATH, "%s\\*", temp_dir);
WIN32_FIND_DATAA find_data;
HANDLE hFind = FindFirstFileA(search_path, &find_data);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (strcmp(find_data.cFileName, ".") == 0 ||
strcmp(find_data.cFileName, "..") == 0) {
continue;
}
char file_path[MAX_PATH];
snprintf(file_path, MAX_PATH, "%s\\%s", temp_dir, find_data.cFileName);
// SICHER: Auf Reparse Points prüfen
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
// SICHER: Junction entfernen ohne ihr zu folgen
RemoveDirectoryA(file_path);
continue;
}
// SICHER: Auf Hard Links prüfen durch Verifikation der Link-Anzahl
if (!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
HANDLE hFile = CreateFileA(
file_path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
);
if (hFile != INVALID_HANDLE_VALUE) {
BY_HANDLE_FILE_INFORMATION info;
if (GetFileInformationByHandle(hFile, &info)) {
if (info.nNumberOfLinks > 1) {
// SICHER: Datei ist per Hard Link verknüpft, nicht löschen
CloseHandle(hFile);
continue;
}
}
CloseHandle(hFile);
}
}
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
safe_cleanup_temp(file_path); // Rekursiv mit gleichen Prüfungen
RemoveDirectoryA(file_path);
} else {
DeleteFileA(file_path);
}
} while (FindNextFileA(hFind, &find_data));
FindClose(hFind);
}
}
// SICHER: Sichere Pfadvalidierung
BOOL validate_path_security(const char* path, const char* allowed_base) {
char resolved_path[MAX_PATH];
char resolved_base[MAX_PATH];
// SICHER: Kanonische Pfade ermitteln
if (!GetFullPathNameA(path, MAX_PATH, resolved_path, NULL)) {
return FALSE;
}
if (!GetFullPathNameA(allowed_base, MAX_PATH, resolved_base, NULL)) {
return FALSE;
}
// SICHER: Prüfen ob Pfad unter erlaubtem Basispfad liegt
size_t base_len = strlen(resolved_base);
if (_strnicmp(resolved_path, resolved_base, base_len) != 0) {
return FALSE;
}
// SICHER: Auf Junctions im Pfad prüfen
if (path_contains_junction(resolved_path)) {
return FALSE;
}
return TRUE;
}
// SICHER: C#-Code mit Junction/Mount-Point-Schutz
using System;
using System.IO;
using System.Runtime.InteropServices;
public class SafeFileOperations
{
// SICHER: Prüfen ob Pfad ein Reparse Point ist
private bool IsReparsePoint(string path)
{
FileAttributes attrs = File.GetAttributes(path);
return (attrs & FileAttributes.ReparsePoint) != 0;
}
// SICHER: Gesamten Pfad auf Junctions prüfen
private bool PathContainsReparsePoint(string path)
{
string[] components = path.Split(Path.DirectorySeparatorChar);
string currentPath = "";
foreach (string component in components)
{
if (string.IsNullOrEmpty(component)) continue;
currentPath = string.IsNullOrEmpty(currentPath)
? component + Path.DirectorySeparatorChar
: Path.Combine(currentPath, component);
if (Directory.Exists(currentPath) && IsReparsePoint(currentPath))
{
return true;
}
}
return false;
}
// SICHER: Sicheres Dateischreiben mit Junction-Schutz
public void SafeWriteData(string directory, string data)
{
// SICHER: Validieren dass Pfad keine Junctions enthält
if (PathContainsReparsePoint(directory))
{
throw new SecurityException("Pfad enthält Reparse Point");
}
string filePath = Path.Combine(directory, "data.txt");
// SICHER: Prüfen ob Ziel nicht als Reparse Point existiert
if (File.Exists(filePath) && IsReparsePoint(filePath))
{
throw new SecurityException("Ziel ist ein Reparse Point");
}
File.WriteAllText(filePath, data);
}
// SICHER: Sichere Verzeichnislöschung
public void SafeCleanupDirectory(string path)
{
// SICHER: Wenn oberste Ebene eine Junction ist, nur die Junction entfernen
if (IsReparsePoint(path))
{
Directory.Delete(path, false); // Nur Junction entfernen
return;
}
// SICHER: Manuell iterieren ohne Junctions zu folgen
foreach (string file in Directory.GetFiles(path))
{
// SICHER: Auf Hard Links prüfen
FileInfo fi = new FileInfo(file);
if (IsHardLink(file))
{
// Per Hard Link verknüpfte Dateien überspringen
continue;
}
File.Delete(file);
}
foreach (string dir in Directory.GetDirectories(path))
{
if (IsReparsePoint(dir))
{
// SICHER: Junction entfernen ohne ihr zu folgen
Directory.Delete(dir, false);
}
else
{
SafeCleanupDirectory(dir); // Rekursiv mit gleichen Prüfungen
Directory.Delete(dir, false);
}
}
}
// SICHER: Prüfen ob Datei ein Hard Link ist
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool GetFileInformationByHandle(
IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[StructLayout(LayoutKind.Sequential)]
private struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
}
private bool IsHardLink(string path)
{
using (FileStream fs = File.Open(path, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite))
{
BY_HANDLE_FILE_INFORMATION info;
if (GetFileInformationByHandle(fs.SafeFileHandle.DangerousGetHandle(), out info))
{
return info.NumberOfLinks > 1;
}
}
return false;
}
// SICHER: Prüfen ob Pfad innerhalb des erlaubten Verzeichnisses bleibt
public bool ValidatePathSecurity(string path, string allowedBase)
{
string fullPath = Path.GetFullPath(path);
string fullBase = Path.GetFullPath(allowedBase);
// SICHER: Sicherstellen dass Pfad unter erlaubter Basis liegt
if (!fullPath.StartsWith(fullBase, StringComparison.OrdinalIgnoreCase))
{
return false;
}
// SICHER: Auf Junctions im Pfad prüfen
if (PathContainsReparsePoint(fullPath))
{
return false;
}
return true;
}
}
CVE-Beispiele
- CVE-2020-0668: Windows Service Tracing beliebiges Dateischreiben durch Junction.
- CVE-2020-1088: Windows-Fehlerberichterstattung Rechteeskalation über Symlink.
- CVE-2019-1069: Windows Task Scheduler Dateiüberschreibung durch Hard Link.
Verwandte CWEs
- CWE-59: Improper Link Resolution Before File Access (übergeordnet)
- CWE-362: Race Condition (verwandt)
- CWE-367: Time-of-Check Time-of-Use (TOCTOU) Race Condition (verwandt)
Referenzen
- MITRE Corporation. "CWE-1386: Insecure Operation on Windows Junction / Mount Point." https://cwe.mitre.org/data/definitions/1386.html
- Microsoft. "Hard Links and Junctions"
- Project Zero. "Windows Symbolic Link Attacks"