Téléchargement de fichiers
Il est très fréquent que les applications doivent, à un moment ou à un autre, permettre aux utilisateurs de télécharger un fichier (soit pour l'utiliser, soit pour le stocker) quelque part dans l'application. Bien que cela semble assez simple, la manière dont cette fonction est mise en œuvre peut être très critique en raison des risques potentiels associés à la manière dont les téléchargements de fichiers sont gérés.
Jetez un coup d'œil à cet exemple rapide, pour mieux comprendre ce que nous voulons dire.
Supposons qu'il s'agisse d'une application permettant aux utilisateurs de télécharger une photo de profil :
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;
}
Il s'agirait d'une fonction de téléchargement très basique qui s'avère également vulnérable au détournement de chemin.
En fonction de l'implémentation exacte de l'application, un attaquant pourrait télécharger une autre page/script (pensez aux fichiers .asp, .aspx, ou .php) qui permettrait d'appeler directement et d'exécuter un code arbitraire. Cela pourrait également permettre de remplacer des fichiers existants.
Problème 1 - Enregistrement sur le disque local plutôt que dans un magasin de données externe
À mesure que l'utilisation des services en nuage se banalise, les applications sont livrées dans des conteneurs, les configurations à haute disponibilité sont devenues la norme et la pratique consistant à écrire les fichiers téléchargés sur le disque local de l'application devrait être évitée à tout prix.
Les fichiers doivent être téléchargés vers une forme de stockage central si possible (stockage en bloc ou base de données). Cela permet d'éviter des catégories entières de failles de sécurité dans ce cas.
Problème 2 - Non validation des extensions
Dans de nombreux cas où une vulnérabilité de téléchargement de fichiers est exploitée, elle repose sur la possibilité de télécharger un fichier avec une extension spécifique. Il est donc fortement conseillé d'utiliser une liste d'extensions autorisées pour les fichiers pouvant être téléchargés.
Veillez à utiliser les méthodes fournies par votre langage/cadre de travail pour obtenir l'extension du fichier afin d'éviter des problèmes tels que l'injection d'octets nuls.
Il peut également être tentant de valider le type de contenu du téléchargement, mais cela peut le rendre très fragile, étant donné que les types de contenu utilisés pour des fichiers spécifiques peuvent différer d'un système d'exploitation à l'autre. En outre, cela ne vous apprend rien sur le fichier lui-même, puisque le type de contenu n'est qu'un mappage à partir d'une extension.
Problème 3 - Ne pas empêcher la traversée du chemin
Un autre problème commun aux téléchargements de fichiers est qu'ils ont tendance à être vulnérables au détournement de chemin. Il s'agit là d'un sujet à part entière, aussi, plutôt que d'essayer de le résumer ici, jetez un coup d'œil à l'intégralité des lignes directrices sur le Path Traversal.
Plus d'exemples
Vous trouverez ci-dessous quelques autres exemples de téléchargements de fichiers sécurisés et non sécurisés.
C# - Insecure
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# - Sécurisé
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));
}