Unzureichende Einschränkung von Namen für Dateien und andere Ressourcen
Beschreibung
Unzureichende Einschränkung von Namen für Dateien und andere Ressourcen tritt auf, wenn eine Anwendung Dateien, Verzeichnisse, Datenbanktabellen oder andere Ressourcen mit Namen erstellt, die von Benutzereingaben abgeleitet sind, ohne ordnungsgemäße Validierung. Angreifer können Namen mit Sonderzeichen, Pfadtrennern oder reservierten Namen erstellen, um unbeabsichtigtes Verhalten zu verursachen, einschließlich Path-Traversal, Ressourcen-Hijacking, Denial of Service oder Code-Ausführung.
Risiko
Path-Traversal durch Namen mit "../"-Sequenzen. Überschreiben kritischer Systemdateien. Kollisionsangriffe durch Erstellen von Ressourcen mit Namen, die mit bestehenden kollidieren. Denial of Service durch reservierte Namen (CON, NUL unter Windows). Erstellen ausführbarer Dateien in Web-Verzeichnissen. SQL-Injection durch Tabellen-/Spaltennamen. Shell-Befehlsinjektion durch Dateinamen.
Lösung
Validieren Sie Ressourcennamen gegen eine strikte Whitelist erlaubter Zeichen. Lehnen Sie Namen mit Pfadtrennern, Null-Bytes und Steuerzeichen ab. Bereinigen oder lehnen Sie reservierte Namen ab. Begrenzen Sie die Namenslänge. Verwenden Sie generierte Namen anstelle von benutzerbereitgestellten. Implementieren Sie ordnungsgemäße Zugriffskontrollen unabhängig von der Benennung.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Dateisystem Unbefugte Dateierstellung oder -überschreibung. |
| Verfügbarkeit | Bereich: Denial of Service Reservierte Namen verursachen Systemprobleme. |
| Vertraulichkeit | Bereich: Informationsoffenlegung Zugriff auf Dateien außerhalb des beabsichtigten Verzeichnisses. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: Benutzergesteuerter Dateiname
public class VulnerableFileUpload {
public void saveUpload(String userFilename, byte[] content) {
// Keine Validierung des Dateinamens
File file = new File("/uploads/" + userFilename);
Files.write(file.toPath(), content);
}
// Angriff: userFilename = "../../../etc/cron.d/malicious"
// Angriff: userFilename = "../../webapps/ROOT/shell.jsp"
}
// VERWUNDBAR: Datenbanktabellen aus Benutzereingabe erstellen
public class VulnerableDatabaseCreator {
public void createUserTable(String username) {
String tableName = "user_" + username;
// SQL-Injection durch Tabellennamen!
String sql = "CREATE TABLE " + tableName + " (id INT, data TEXT)";
connection.executeUpdate(sql);
}
// Angriff: username = "test; DROP TABLE users; --"
}
// VERWUNDBAR: Verzeichniserstellung
public class VulnerableDirectoryService {
public void createProjectDir(String projectName) {
// Keine Validierung
File dir = new File("/projects/" + projectName);
dir.mkdirs();
}
// Angriff: projectName = "../../sensitive/admin"
}
# VERWUNDBAR: Python-Dateioperationen mit Benutzereingabe
import os
def save_file_vulnerable(username, filename, content):
# Keine Validierung
path = f"/data/{username}/{filename}"
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w') as f:
f.write(content)
# Angriff: filename = "../../../etc/passwd"
# VERWUNDBAR: Datenbank mit benutzergesteuerten Namen
def create_user_schema_vulnerable(username):
schema_name = f"user_{username}"
# SQL-Injection durch Schemanamen
cursor.execute(f"CREATE SCHEMA {schema_name}")
# Angriff: username = "test; DROP DATABASE production;--"
# VERWUNDBAR: Python-Module dynamisch erstellen
def create_plugin_vulnerable(plugin_name, code):
# Schreibt in plugins-Verzeichnis
filename = f"/app/plugins/{plugin_name}.py"
with open(filename, 'w') as f:
f.write(code)
# Angriff: plugin_name = "__init__"
# Angriff: plugin_name = "../../../app/main"
# VERWUNDBAR: Session-Datei-Speicherung
def save_session_vulnerable(session_id, data):
# Session-ID als Dateiname verwendet
path = f"/tmp/sessions/{session_id}"
with open(path, 'w') as f:
f.write(data)
# Angriff: session_id = "../../root/.ssh/authorized_keys"
// VERWUNDBAR: Node.js Dateioperationen
const fs = require('fs');
const path = require('path');
function saveUserFileVulnerable(userId, filename, content) {
// Keine Validierung
const filePath = `/uploads/${userId}/${filename}`;
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content);
}
// Angriff: filename = "../../../app/routes/backdoor.js"
// VERWUNDBAR: Temporäre Dateien erstellen
function createTempFileVulnerable(name, data) {
const tempPath = `/tmp/${name}`;
fs.writeFileSync(tempPath, data);
}
// Angriff: name = "../../etc/ld.so.preload"
// VERWUNDBAR: Datenbank-Collection-Namen (MongoDB)
async function createCollectionVulnerable(collectionName) {
// Benutzer kontrolliert Collection-Namen
await db.createCollection(collectionName);
}
// Angriff: collectionName = "admin.system.users"
// VERWUNDBAR: Log-Datei-Benennung
function createLogFileVulnerable(logName) {
const logPath = `/logs/${logName}.log`;
return fs.createWriteStream(logPath);
}
// Angriff: logName = "../../../var/www/shell.php\x00"
Lösungscode
// SICHER: Validierte Dateinamen-Behandlung
public class SafeFileUpload {
private static final Pattern SAFE_FILENAME = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$");
private static final Set<String> RESERVED_NAMES = Set.of(
"con", "prn", "aux", "nul",
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"
);
public void saveUpload(String userFilename, byte[] content) {
String safeName = sanitizeFilename(userFilename);
// Festes Basisverzeichnis verwenden
Path basePath = Paths.get("/uploads").toAbsolutePath().normalize();
Path filePath = basePath.resolve(safeName).normalize();
// Verifizieren, dass Pfad innerhalb Basisverzeichnis ist
if (!filePath.startsWith(basePath)) {
throw new SecurityException("Ungültiger Pfad");
}
Files.write(filePath, content);
}
private String sanitizeFilename(String filename) {
if (filename == null || filename.isEmpty()) {
throw new IllegalArgumentException("Dateiname erforderlich");
}
// Pfadkomponenten entfernen
String name = Paths.get(filename).getFileName().toString();
// Null-Bytes und Steuerzeichen entfernen
name = name.replaceAll("[\\x00-\\x1f\\x7f]", "");
// Gegen sicheres Muster validieren
if (!SAFE_FILENAME.matcher(name).matches()) {
throw new IllegalArgumentException("Ungültiger Dateiname");
}
// Reservierte Namen prüfen
String baseName = name.contains(".") ?
name.substring(0, name.lastIndexOf('.')) : name;
if (RESERVED_NAMES.contains(baseName.toLowerCase())) {
throw new IllegalArgumentException("Reservierter Dateiname");
}
return name;
}
}
// SICHER: Datenbank mit parametrisierten Identifikatoren
public class SafeDatabaseCreator {
private static final Pattern SAFE_IDENTIFIER = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{0,63}$");
public void createUserTable(String username) {
// Identifikator validieren
String safeUsername = validateIdentifier(username);
String tableName = "user_" + safeUsername;
// Identifikator-Quoting verwenden
String sql = "CREATE TABLE " + quoteIdentifier(tableName) +
" (id INT, data TEXT)";
connection.executeUpdate(sql);
}
private String validateIdentifier(String name) {
if (!SAFE_IDENTIFIER.matcher(name).matches()) {
throw new IllegalArgumentException("Ungültiger Identifikator");
}
return name;
}
private String quoteIdentifier(String identifier) {
// Datenbankspezifisches Quoting
return "\"" + identifier.replace("\"", "\"\"") + "\"";
}
}
import re
import os
import uuid
from pathlib import Path
# SICHER: Validierte Dateioperationen
class SafeFileHandler:
SAFE_NAME_PATTERN = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}
```javascript
// SICHER: Node.js mit ordnungsgemäßer Validierung
const path = require('path');
const fs = require('fs');
const { v4: uuidv4 } = require('uuid');
class SafeFileHandler {
constructor(baseDir) {
this.baseDir = path.resolve(baseDir);
}
static SAFE_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$/;
static RESERVED_NAMES = new Set([
'con', 'prn', 'aux', 'nul',
'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'
]);
sanitizeFilename(filename) {
if (!filename) {
throw new Error('Dateiname erforderlich');
}
// Nur Dateinamen extrahieren
const name = path.basename(filename);
// Null-Bytes und Steuerzeichen entfernen
const cleaned = name.replace(/[\x00-\x1f\x7f]/g, '');
// Muster validieren
if (!SafeFileHandler.SAFE_NAME_REGEX.test(cleaned)) {
throw new Error('Ungültiges Dateinamenformat');
}
// Reservierte Namen prüfen
const baseName = cleaned.includes('.') ?
cleaned.substring(0, cleaned.lastIndexOf('.')) : cleaned;
if (SafeFileHandler.RESERVED_NAMES.has(baseName.toLowerCase())) {
throw new Error('Reservierter Dateiname');
}
return cleaned;
}
saveFile(filename, content) {
const safeName = this.sanitizeFilename(filename);
const filePath = path.resolve(this.baseDir, safeName);
// Innerhalb Basisverzeichnis verifizieren
if (!filePath.startsWith(this.baseDir + path.sep) &&
filePath !== this.baseDir) {
throw new Error('Path-Traversal erkannt');
}
fs.writeFileSync(filePath, content);
return safeName;
}
saveWithGeneratedName(extension, content) {
// Erweiterung validieren
if (extension && !/^\.[a-zA-Z0-9]+$/.test(extension)) {
throw new Error('Ungültige Erweiterung');
}
const filename = uuidv4() + (extension || '');
return this.saveFile(filename, content);
}
}
// SICHER: MongoDB Collection-Namen
const SAFE_COLLECTION_NAME = /^[a-zA-Z][a-zA-Z0-9_]{0,63}$/;
const RESERVED_COLLECTIONS = ['admin', 'local', 'config', 'system'];
function createCollectionSafe(name) {
if (!SAFE_COLLECTION_NAME.test(name)) {
throw new Error('Ungültiger Collection-Name');
}
if (RESERVED_COLLECTIONS.some(r => name.startsWith(r + '.'))) {
throw new Error('Reservierter Collection-Name');
}
return db.createCollection(name);
}
Ausgenutzt in der Praxis
Path-Traversal-Uploads
Bösartige Dateinamen verwendet, um außerhalb des Upload-Verzeichnisses zu schreiben.
Windows-DoS
Reservierte Gerätenamen verursachen Anwendungs-Hänger.
SQL-Injection
Tabellen-/Spaltennamen ermöglichen Injection-Angriffe.
Tools zum Testen/Ausnutzen
- Burp Suite — Dateinamen-Fuzzing.
- Path-Traversal-Wortlisten.
- Reservierte-Namen-Tests.
CVE-Beispiele
- CVE-2019-12415: Apache POI Path-Traversal über Dateinamen.
- Zahlreiche Zip-Slip-Schwachstellen.
Referenzen
- MITRE. "CWE-641: Improper Restriction of Names for Files and Other Resources." https://cwe.mitre.org/data/definitions/641.html
- OWASP. "Path Traversal."