Richtlinien

Datei-Upload

Es ist sehr üblich, dass Anwendungen an irgendeinem Punkt Benutzern erlauben müssen, eine Datei irgendwo innerhalb der Anwendung hochzuladen (entweder zur Verwendung oder einfach zur Speicherung). Obwohl dies einfach erscheint, kann die Implementierung dieser Funktion aufgrund der potenziellen Risiken im Zusammenhang mit der Handhabung von Datei-Uploads sehr kritisch sein.

Werfen Sie einen Blick auf dieses kurze Beispiel, um ein besseres visuelles Verständnis davon zu bekommen, was wir meinen.

Nehmen wir an, dies ist eine Anwendung, die es Benutzern ermöglicht, ein Profilbild hochzuladen:

public string UploadProfilePicture(FormFile uploadedFile)
{
    // Pfad zum Speichern der hochgeladenen Datei generieren
    var path = $"./uploads/avatars/{request.User.Id}/{uploadedFile.FileName}";  

    // Datei speichern
    var localFile = File.OpenWrite(path);
    localFile.Write(uploadedFile.ReadToEnd());
    localFile.Flush();
    localFile.Close();

    // Profilbild aktualisieren
    UserProfile.UpdateUserProfilePicture(request.User, path)

    return path;
}

Dies wäre eine sehr grundlegende Upload-Funktion, die zudem anfällig für Path Traversal ist.

Je nach genauer Implementierung der Anwendung könnte ein Angreifer eine andere Seite oder ein Skript hochladen (denken Sie an .asp-, .aspx- oder .php-Dateien), das direkt aufgerufen und zur Ausführung von beliebigem Code genutzt werden könnte. Dies könnte auch das Überschreiben bestehender Dateien ermöglichen.

Problem 1 – Speichern auf lokalem Datenträger statt in einem externen Datenspeicher

Da die Nutzung von Cloud-Diensten immer üblicher wird, Anwendungen in Containern bereitgestellt werden und Hochverfügbarkeits-Setups zum Standard geworden sind, sollte die Praxis, hochgeladene Dateien auf dem lokalen Datenträger der Anwendung zu speichern, grundsätzlich um jeden Preis vermieden werden.

Dateien sollten nach Möglichkeit in einer zentralen Speicherlösung (Block-Speicher oder Datenbank) hochgeladen werden. Dies kann in diesem Fall ganze Klassen von Sicherheitslücken vermeiden.

Problem 2 – Keine Validierung von Dateiendungen

In vielen Fällen, in denen eine Datei-Upload-Schwachstelle ausgenutzt wird, beruht dies auf der Möglichkeit, eine Datei mit einer bestimmten Endung hochzuladen. Daher ist es dringend ratsam, eine „Allow-List“ (Positivliste) für die Dateiendungen zu verwenden, die hochgeladen werden dürfen.

Stellen Sie sicher, dass Sie die von Ihrer Sprache oder Ihrem Framework bereitgestellten Methoden verwenden, um die Dateiendung zu ermitteln und Probleme wie Null-Byte-Injection zu vermeiden.

Es mag verlockend sein, den Inhaltstyp (Content-Type) des Uploads zu validieren, aber dies kann sehr fehleranfällig sein, da die für bestimmte Dateien verwendeten Inhaltstypen zwischen Betriebssystemen variieren können. Zudem sagt dies nichts über die Datei selbst aus, da der Inhaltstyp lediglich eine Zuordnung zu einer Dateiendung ist.

Problem 3 – Kein Schutz gegen Path Traversal

Ein weiteres häufiges Problem bei Datei-Uploads ist, dass sie oft anfällig für Path Traversal sind. Das ist ein komplexes Thema für sich; anstatt es hier zusammenzufassen, werfen Sie einen Blick auf den vollständigen Leitfaden zu Path Traversal.

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)
{
    // Pfad zum Speichern der hochgeladenen Datei generieren
    var path = $"./uploads/avatars/{request.User.Id}/{uploadedFile.FileName}";

    // Datei speichern
    var localFile = File.OpenWrite(path);
    localFile.Write(uploadedFile.ReadToEnd());
    localFile.Flush();
    localFile.Close();

    // Profilbild aktualisieren
    UserProfile.UpdateUserProfilePicture(request.User, path)

    return path;
}

C# – Sicher

public List<string> AllowedExtensions = new() { ".png", ".jpg", ".gif"};

public string UploadProfilePicture(IFormFile uploadedFile)
{
    // HINWEIS: Am besten ist es, das Speichern von Dateien auf dem lokalen Datenträger zu vermeiden.
    var basePath = Path.GetFullPath("./uploads/avatars/");

    // Pfad-Traversal verhindern, indem der bereitgestellte Dateiname nicht verwendet wird. Auch zur Vermeidung von Namenskonflikten.
    var newFileName = GenerateFileName(uploadedFile.FileName);

    // Pfad zum Speichern der hochgeladenen Datei generieren
    var canonicalPath = Path.Combine(basePath, newFileName);

    // Sicherstellen, dass nicht versehentlich außerhalb des Basisordners gespeichert wird
    if(!canonicalPath.StartsWith(basePath))
    {
        return BadRequest("Versuch, Datei außerhalb des Upload-Ordners zu speichern");
    }

    // Sicherstellen, dass nur erlaubte Erweiterungen gespeichert werden
    if(!IsFileAllowedExtension(uploadedAllowedExtensions))
    {
        return BadRequest("Erweiterung nicht zulässig");
    }

    // Datei speichern
    var localFile = File.OpenWrite(canonicalPath);
    localFile.Write(uploadedFile.ReadToEnd());
    localFile.Flush();
    localFile.Close();

    // Profilbild aktualisieren
    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));
}

‍

Java - Unsicher

@Controller
public class FileUploadController {

   @RequestMapping(value = "/files/upload", method = RequestMethod.POST)
   @ResponseBody
   public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file, @AuthenticationPrincipal User user) {

       try {

           String uploadPath = "./uploads/avatars/" + principal.getName() + "/" + file.getOriginalFilename();

           File transferFile = new File(uploadPath);
           file.transferTo(transferFile);

       } catch (Exception e) {
           return new ResponseEntity<>("Upload-Fehler", HttpStatus.INTERNAL_SERVER_ERROR);
       }

       return new ResponseEntity<>(uploadPath, HttpStatus.CREATED);
   }
}

Java - Sicher

@Controller
public class FileUploadController {

    @RequestMapping(value = "/files/upload", method = RequestMethod.POST)
    @ResponseBody
    public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file, @AuthenticationPrincipal User user) {

        try {
            String baseFolder = Paths.get("./uploads/avatars/").normalize();
            String uploadPath = Paths.get(baseFolder.toString() +
GenerateFileName(file.getOriginalFilename())).normalize();
           // Sicherstellen, dass die Erweiterung ein zulässiger Typ ist
            if(!IsAllowedExtension(file.getOriginalFilename()) {
                return new ResponseEntity<>("Erweiterung nicht zulässig", HttpStatus.FORBIDDEN);
            }

            // Sicherstellen, dass die Datei nicht außerhalb des Upload-Roots gespeichert wird
           if(!uploadPath.toString().startsWith(baseFolder.toString()))            {
                return new ResponseEntity<>("Dateien dürfen nicht außerhalb des Basisordners gespeichert werden.", HttpStatus.FORBIDDEN);
           }

            File transferFile = new File(uploadPath.toString());
            file.transferTo(uploadPath.toString());

        } catch (Exception e) {
            return new ResponseEntity<>("Upload-Fehler", HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return new ResponseEntity<>(uploadPath, HttpStatus.CREATED);
    }

    private string GenerateFileName(String fileName) {
        return UUID.randomUUID().toString() + "." + FilenameUtils.getExtension(fileName);
    }

    private boolean IsAllowedExtension(String fileName) {
        String[] allowedExtensions = {"jpg", "png", "gif"};
        String extension = FilenameUtils.getExtension(filename);
        return allowedExtensions.contains(extension);
    }
}

Python – Flask – Unsicher

@app.route('/files/upload', methods=['POST'])
def upload_file():

    file = request.files['file']

    savedFilePath = os.path.join("./uploads/avatars/", file.filename)
    file.save(savedFilePath)

    return savedFilePath

Python – Flask – Sicher

@app.route('/files/upload', methods=['POST'])
def upload_file():

    file = request.files['file']
    baseFolder = os.path.normpath("./uploads/avatars/")
    savedFilePath = os.path.normpath(os.path.join(baseFolder, generate_file_name(file.filename)))

    # Sicherstellen, dass die Erweiterung in der Liste der erlaubten Typen ist
    if not is_extension_allowed(file.filename):
        return "Diese Erweiterung ist nicht zulässig"

    # Sicherstellen, dass die Datei nicht außerhalb des Basisordners gespeichert wird
    if not savedFilePath.startsWith(baseFolder):
        return "Versuch, Datei außerhalb des Basisordners zu speichern"

    file.save(savedFilePath)

    return savedFilePath

def generate_file_name(filename):
    return str(uuid.uuid4()) + os.path.splitext(filename)[1]

def is_extension_allowed(filename):
    return os.path.splitext(filename)[1] in (".png", ".jpg", ".gif")
Slogan

Sichere Programmierung in der Praxis

Entdecken Sie Richtlinien für sicheres Programmieren, um Schwachstellen wie die OWASP Top 10 zu verstehen und zu beheben, und tauchen Sie in geführte Trainingsmissionen ein, um diese in realistischen Anwendungssimulationen zu üben.

Weitere Richtlinien
Demo buchen