Held-Hintergrund ohne Trennlinie
Richtlinien

Download of files

Il est très courant que les applications aient besoin, à un moment ou à un autre, de permettre aux utilisateurs de télécharger un fichier (pour l'utiliser ou simplement pour le stockage) quelque part dans l'application. Bien que cela semble assez simple, la manière dont cette fonction est implémentée peut être assez critique en raison des risques potentiels associés à la gestion des téléchargements de fichiers.

Jetez un œil à cet exemple rapide, juste pour vous donner une meilleure compréhension visuelle de ce que nous voulons dire.

Disons qu'il s'agit d'une application qui permet aux utilisateurs de télécharger une photo de profil :

chaîne publique UploadProfilePicture (FormFile UploadedFile)
{
//Génère le chemin pour enregistrer le fichier téléchargé dans
var path = $ ». /uploads/avatars/ {request.user.id}/{uploadedFile.FileName} » ;

//Sauvegarde du fichier
var LocalFile = File.OpenWrite (chemin) ;
LocalFile.write (UploadedFile.readToEnd ()) ;
Fichier local .Flush () ;
LocalFile.close () ;

//Mise à jour de la photo de profil
UserProfile.UpdateUserProfilePicture (request.User, chemin)

chemin de retour ;
}

Il s'agirait d'une fonction de téléchargement très basique qui s'avère également vulnérable à Path Traversal.

En fonction de l'implémentation exacte de l'application, un attaquant pourrait télécharger une autre page/un autre script (pensez à des fichiers .asp, .aspx ou .php) qui permettrait d'appeler directement et d'exécuter du code arbitraire. Cela pourrait également permettre de remplacer les fichiers existants.

Problème 1 : enregistrement sur un disque local plutôt que sur un magasin de données externe

À mesure que l'utilisation des services cloud se généralise, les applications sont fournies dans des conteneurs, les configurations de haute disponibilité sont devenues la norme et la pratique consistant à écrire des fichiers téléchargés sur le disque local de l'application doit être évitée à tout prix.

Les fichiers doivent être téléchargés dans une forme de stockage central dans la mesure du possible (stockage par blocs 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 : les extensions ne sont pas validées

Dans de nombreux cas, lorsqu'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 vivement conseillé d'utiliser une « liste d'extensions autorisées » pour les fichiers qui peuvent être téléchargés.

Assurez-vous d'utiliser les méthodes fournies par votre langage/framework pour obtenir l'extension du fichier afin d'éviter des problèmes, comme 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. Il ne vous dit rien non plus sur le fichier lui-même puisque le type de contenu est purement un mappage provenant d'une extension.

Problème 3 : ne pas empêcher la traversée du chemin

Un autre problème courant lié aux téléchargements de fichiers est qu'ils ont également tendance à être vulnérables à la traversée de chemins. C'est un tout en soi, donc plutôt que d'essayer de le résumer ici, donnez la directive complète sur Traversée de chemin un coup d'œil.

Plus d'exemples

Vous trouverez ci-dessous quelques exemples supplémentaires de téléchargements de fichiers sécurisés et non sécurisés que vous pouvez consulter.

C# - Non sécurisé

chaîne publique UploadProfilePicture (IFormFile UploadedFile)
{
//Génère le chemin pour enregistrer le fichier téléchargé dans
var path = $ ». /uploads/avatars/ {request.user.id}/{uploadedFile.FileName} » ;

//Sauvegarde du fichier
var LocalFile = File.OpenWrite (chemin) ;
LocalFile.write (UploadedFile.readToEnd ()) ;
Fichier local .Flush () ;
LocalFile.close () ;

//Mise à jour de la photo de profil
UserProfile.UpdateUserProfilePicture (request.User, chemin)

chemin de retour ;
}

C# - Sécurisé

liste publique <string>AllowedExtensions = new () {« .png », « .jpg », « .gif"} ;

chaîne publique UploadProfilePicture (IFormFile UploadedFile)
{
//REMARQUE : La meilleure option est d'éviter d'enregistrer des fichiers sur le disque local.
var basePath = path.getFullPath (». /uploads/avatars/ «) ;

//Empêche la traversée des chemins en n'utilisant pas le nom de fichier fourni. Également nécessaire pour éviter les conflits de noms de fichiers.
var NewFileName = GenerateFileName (UploadedFile.FileName) ;

//Génère le chemin pour enregistrer le fichier téléchargé dans
var CanonicalPath = Path.Combine (BasePath, NewFileName) ;

//Assurez-vous que nous n'avons pas accidentellement enregistré dans un dossier en dehors du dossier de base
si (! Chemin canonique. Commence par (BasePath)
{
return BadRequest (« Tentative d'enregistrement du fichier en dehors du dossier de téléchargement ») ;
}

//Assurez-vous que seules les extensions autorisées sont enregistrées
si (! Extension de fichier autorisée (Extensions autorisées téléchargées)
{
return BadRequest (« L'extension n'est pas autorisée ») ;
}

//Sauvegarde du fichier
var LocalFile = File.OpenWrite (CanonicalPath) ;
LocalFile.write (UploadedFile.readToEnd ()) ;
Fichier local .Flush () ;
LocalFile.close () ;

//Mise à jour de la photo de profil
Profil utilisateur. Mettre à jour l'image du profil utilisateur (request.User, CanonicalPath)

chemin de retour ;

public bool generateFileName (chaîne OriginalFileName) {
return $ "{guid.newGUID ()} {path.getExtension (OriginalFileName)} » ;
}

<string>public bool IsFileAllowedExtension (chaîne FileName, liste des extensions) {
renvoie Extensions.contains (Path.getExtension (FileName)) ;
}

Java - Non sécurisé

@Controller
classe publique FileUploadController {

@RequestMapping (valeur = « /files/upload », méthode = RequestMethod.POST)
@ResponseBody
public ResponseEntity <String>UploadFile (@RequestParam (« fichier ») fichier MultiPartFile, utilisateur @AuthenticationPrincipal) {

essayez {

Chaîne UploadPath = ». /uploads/avatars/ "+ principal.getName () + «/» + File.getOriginalFileName () ;

File TransferFile = nouveau fichier (UploadPath) ;
File.transferTo (Transférer le fichier) ;

} catch (Exception e) {
return new ResponseEntity<> (« Erreur de téléchargement », HttpStatus.INTERNAL_SERVER_ERROR) ;
}

renvoie new ResponseEntity<> (UploadPath, HttpStatus.Created) ;
}
}

Java - Sécurisé

@Controller
classe publique FileUploadController {

@RequestMapping (valeur = « /files/upload », méthode = RequestMethod.POST)
@ResponseBody
public ResponseEntity <String>UploadFile (@RequestParam (« fichier ») fichier MultiPartFile, utilisateur @AuthenticationPrincipal) {

essayez {
String BaseFolder = Paths.get (». /uploads/avatars/ «) .normalize () ;
String UploadPath = Paths.get (baseFolder.toString () +
Générer un nom de fichier (File.getOriginalFileName ())) .normalize () ;
//Assurez-vous que l'extension est d'un type autorisé
si (! isAllowExtension (File.getOriginalFileName ()) {
return new ResponseEntity<> (« Extension non autorisée », HttpStatus.Forbidden) ;
}

//Assurez-vous que le fichier n'est pas enregistré en dehors de la racine de téléchargement
si (! uploadPath.toString () .StartWith (BaseFolder.toString ())) {
return new ResponseEntity<> (« Les fichiers ne peuvent pas être enregistrés en dehors du dossier de base. «, HttpStatus.Forbidden) ;
}

File TransferFile = nouveau fichier (UploadPath.toString ()) ;
File.transferTo (uploadPath.toString ()) ;

} catch (Exception e) {
return new ResponseEntity<> (« Erreur de téléchargement », HttpStatus.INTERNAL_SERVER_ERROR) ;
}

renvoie new ResponseEntity<> (UploadPath, HttpStatus.Created) ;
}

chaîne privée GenerateFileName (String FileName) {
return UUID.RandomUUID () .toString () + «. » + FileNôtils.getExtension (FileName) ;
}

private boolean isAllowedExtension (String FileName) {
String [] AllowedExtensions = {"jpg », « png », « gif »} ;
extension de chaîne = FileNôtils.getExtension (nom de fichier) ;
renvoie AllowedExtensions.contains (extension) ;
}
}

Python - Flask - Non sécurisé

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

file = request.files ['fichier']

SavedFilePath = os.path.join (». /uploads/avatars/ «, file.filename)
file.save (chemin du fichier enregistré)

renvoie SavedFilePath

Python - Flask - Sicher

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

file = request.files ['fichier']
Dossier de base = os.path.normpath (». /uploads/avatars/ «)
SavedFilePath = os.path.normpath (os.path.join (BaseFolder, generate_file_name (file.filename)))

# Assurez-vous que l'extension est dans l'ensemble autorisé
si ce n'est pas le cas is_extension_allowed (file.filename) :
return « Cette extension n'est pas autorisée »

# Assurez-vous que le fichier dans lequel nous essayons d'enregistrer n'est pas en dehors de la base
si ce n'est pas SavedFilePath.StartsWith (BaseFolder) :
return « Tentative d'enregistrement du fichier en dehors du dossier de base »

file.save (chemin du fichier enregistré)

renvoie SavedFilePath

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

def is_extension_allowed (nom de fichier) :
renvoie os.path.splitext (nom de fichier) [1] dans (« .png », « .jpg », « .gif »)