Hochladen von Dateien
Es kommt häufig vor, dass Anwendungen es Benutzern ermöglichen müssen, eine Datei (entweder zur Verwendung oder nur zur Speicherung) irgendwo innerhalb der Anwendung hochzuladen. Auch wenn es einfach erscheint, kann die Implementierung dieser Funktion aufgrund der potenziellen Risiken, die mit der Handhabung von Datei-Uploads verbunden sind, ziemlich kritisch sein.
Schauen Sie sich dieses kurze Beispiel an, damit Sie besser verstehen, was wir meinen.
Nehmen wir an, es handelt sich um eine Anwendung, die es den Nutzern ermöglicht, ein Profilbild hochzuladen:
public string UploadProfilePicture(FormFile uploadedFile)
{
// Generate path to save the uploaded file at
var path = $"./uploads/avatars/{request.User.Id}/{uploadedFile.FileName}";
// Save the file
var localFile = File.OpenWrite(path);
localFile.Write(uploadedFile.ReadToEnd());
localFile.Flush();
localFile.Close();
// Update the profile picture
UserProfile.UpdateUserProfilePicture(request.User, path)
return path;
}
Dies wäre eine sehr einfache Upload-Funktion, die auch anfällig für Path Traversal ist.
Je nach der genauen Implementierung der Anwendung könnte ein Angreifer eine andere Seite/ein anderes Skript hochladen (z. B. .asp-, .aspx- oder .php-Dateien), die einen direkten Aufruf und die Ausführung von beliebigem Code ermöglichen würden. Dies könnte auch das Überschreiben bestehender Dateien ermöglichen.
Problem 1 - Speichern auf der lokalen Festplatte statt in einem externen Datenspeicher
Mit der zunehmenden Nutzung von Cloud-Diensten werden Anwendungen in Containern bereitgestellt, Hochverfügbarkeitskonfigurationen sind zum Standard geworden, und die Praxis, hochgeladene Dateien auf die lokale Festplatte der Anwendung zu schreiben, sollte um jeden Preis vermieden werden.
Die Dateien sollten nach Möglichkeit in einen zentralen Speicher hochgeladen werden (Blockspeicher oder Datenbank). Dadurch können in diesem Fall ganze Klassen von Sicherheitslücken vermieden werden.
Problem 2 - Nicht validierte Erweiterungen
In vielen Fällen, in denen eine Sicherheitslücke beim Hochladen von Dateien ausgenutzt wird, hängt dies von der Möglichkeit ab, eine Datei mit einer bestimmten Erweiterung hochzuladen. Daher ist es sehr ratsam, eine "Zulassen-Liste" von Erweiterungen für Dateien zu verwenden, die hochgeladen werden können.
Stellen Sie sicher, dass Sie die von Ihrer Sprache/Framework bereitgestellten Methoden verwenden, um die Dateierweiterung zu ermitteln, um Probleme wie Null-Byte-Injektion zu vermeiden.
Es mag auch verlockend sein, den Inhaltstyp des Uploads zu validieren, aber das kann die Sache sehr unsicher machen, da sich die für bestimmte Dateien verwendeten Inhaltstypen von Betriebssystem zu Betriebssystem unterscheiden können. Außerdem sagt es nichts über die Datei selbst aus, da der Inhaltstyp lediglich eine Zuordnung aus einer Erweiterung ist.
Problem 3 - Pfadüberquerung nicht verhindert
Ein weiteres häufiges Problem bei Datei-Uploads ist, dass sie auch anfällig für Path Traversal sind. Das ist ein ganz eigenes Thema. Anstatt zu versuchen, es hier zusammenzufassen, sollten Sie sich den vollständigen Leitfaden zu Path Traversal ansehen.
Weitere Beispiele
Nachfolgend finden Sie einige weitere Beispiele für sichere und unsichere Datei-Uploads, die Sie sich ansehen können.
C# - Unsicher
public string UploadProfilePicture(IFormFile uploadedFile)
{
// Generate path to save the uploaded file at
var path = $"./uploads/avatars/{request.User.Id}/{uploadedFile.FileName}";
// Save the file
var localFile = File.OpenWrite(path);
localFile.Write(uploadedFile.ReadToEnd());
localFile.Flush();
localFile.Close();
// Update the profile picture
UserProfile.UpdateUserProfilePicture(request.User, path)
return path;
}
C# - Sicher
public List<string> AllowedExtensions = new() { ".png", ".jpg", ".gif"};
public string UploadProfilePicture(IFormFile uploadedFile)
{
// NOTE: The best option is to avoid saving files to the local disk.
var basePath = Path.GetFullPath("./uploads/avatars/");
// Prevent path traversal by not utilizing the provided file name. Also needed to avoid filename conflicts.
var newFileName = GenerateFileName(uploadedFile.FileName);
// Generate path to save the uploaded file at
var canonicalPath = Path.Combine(basePath, newFileName);
// Ensure that we did not accidentally save to a folder outside of the base folder
if(!canonicalPath.StartsWith(basePath))
{
return BadRequest("Attempted to save file outside of upload folder");
}
// Ensure only allowed extensions are saved
if(!IsFileAllowedExtension(uploadedAllowedExtensions))
{
return BadRequest("Extension is not allowed");
}
// Save the file
var localFile = File.OpenWrite(canonicalPath);
localFile.Write(uploadedFile.ReadToEnd());
localFile.Flush();
localFile.Close();
// Update the profile picture
UserProfile.UpdateUserProfilePicture(request.User, canonicalPath)
return path;
public bool GenerateFileName(string originalFileName) {
return $"{Guid.NewGuid()}{Path.GetExtension(originalFileName)}";
}
public bool IsFileAllowedExtension(string fileName, List<string> extensions) {
return extensions.Contains(Path.GetExtension(fileName));
}