Injection - Traversée de chemin
Le détournement de chemin est un autre type assez courant de vulnérabilité par injection. Elles se produisent généralement lorsque la construction d'un URI (qu'il s'agisse d'une URL, d'un chemin de fichier ou autre) ne garantit pas correctement que le chemin entièrement résolu ne pointe pas en dehors de la racine du chemin prévu.
Il est important de souligner que la traversée de chemin peut également être considérée comme une vulnérabilité par *injection* de chemin.
L'impact d'une vulnérabilité de traversée de chemin dépend fortement du contexte dans lequel la traversée se produit et du renforcement général qui a été effectué. Mais avant d'entrer dans le vif du sujet, passons en revue un rapide exemple pratique de cette vulnérabilité pour voir de quoi nous parlons :
Une analyse rapide
Prenons l'exemple d'un point de terminaison de votre application qui sert des documents, comme des modèles de contrats ou d'offres d'emploi. Il peut s'agir de fichiers, comme des PDF, qui sont statiques dans votre application.
Dans cette situation, vous pouvez avoir un morceau de code comme celui-ci pour récupérer les fichiers à la demande :
let baseFolder = "/var/www/api/documents/" ;
let path = baseFolder + request.params.filename ;
return file.read(path) ;
Afin de démontrer le fonctionnement de la vulnérabilité, nous devons également savoir où se trouve la racine de notre application. Pour cet exemple, supposons que la racine de l'application se trouve dans '/var/www/api/'.
Nous savons que l'application prend un paramètre "nom de fichier", examinons quelques exemples d'entrées et de résultats :
Remarquez que nous pouvons parcourir le système de fichiers en utilisant '../'. Nous pouvons sortir du dossier "documents" où se trouvent habituellement les PDF et entrer dans le dossier "/etc/" contenant le fichier "shadow", qui, sous Linux, contient les hachages de mots de passe. Comme vous pouvez l'imaginer, ce n'est vraiment pas idéal.
Examiner le trafic dans les urls
Une autre variante de traversée de chemin peut se produire lors de la construction d'URL destinées à interagir avec une API. Supposons que nous ayons une API avec les méthodes suivantes :
L'API est utilisée par une autre application qui peut l'appeler, par exemple, pour obtenir des informations sur une commande :
let apiBase = "https://my.api/api/v1" ;
let orderApi = apiBase + "/order/get" ;
let apiUrl = orderApi + request.params.orderId ;
let response = http.get(apiUrl) ;
Que se passe-t-il maintenant, en fonction de l'identifiant de commande fourni par l'utilisateur ? Vous voyez ci-dessous l'URL effective invoquée en fonction des données fournies.
La canonisation n'est généralement pas effectuée du côté du client (bien qu'elle puisse l'être), mais les serveurs web canonisent la demande dans le format ci-dessous.
Dans le deuxième exemple, au lieu de récupérer la commande portant le numéro d'identification "1", nous avons invoqué la méthode "delete", ce qui a bien sûr pour effet de supprimer la commande.
Atténuations
Lorsque l'on parle de traversée de chemin, il existe à la fois des atténuations directes et des techniques indirectes/défenses qui peuvent, et doivent, être appliquées aussi souvent que possible. Tout d'abord, voyons comment gérer les chemins.
Atténuation directe
Lorsqu'il s'agit de gérer un chemin, nous devons comprendre le processus de résolution de chemin, ou canonisation de chemin, et son importance.
Lorsque vous avez un chemin comme '/var/www/api/documents/../../../../etc/shadow', il s'agit d'un chemin non canonique. Si vous demandez ce chemin à votre système de fichiers, il le canonisera en "/etc/shadow". Il est essentiel que vous n'essayiez pas d'ouvrir des chemins non canoniques. Au contraire, vous devez d'abord canoniser les chemins, vérifier qu'ils ne pointent que vers le fichier ou le dossier voulu, puis le lire.
let baseFolder = "/var/www/api/documents/" ;
let path = baseFolder + request.params.filename ;
let resolvedPath = path.resolve(path) ;
if(!resolvedPath.startswith(baseFolder))
return "Tried to read outside of base folder" ;
else
return file.read(resolvedPath) ;
Anti-modèle - Essayer d'assainir les noms de fichiers
Il peut être tentant de faire quelque chose comme cela :
let baseFolder = "/var/www/api/documents/" ;
let path = baseFolder + request.params.filename.replace("../", "") ;
...
Cependant, cette approche ne doit pas être utilisée. La clé de la gestion des chemins d'accès est de toujours considérer le chemin canonique.
Tant que le chemin canonique n'enfreint aucune règle, la façon dont il est construit ne fait pas vraiment de différence. Essayer d'assainir un chemin comme celui-ci est très sujet aux erreurs et n'est que rarement sûr, voire jamais.
Limiter l'accès
Dans nos exemples précédents, nous avons utilisé la lecture du fichier '/etc/shadow', qui est le fichier contenant les hachages de mots de passe sous Linux. Mais il n'y a aucune raison pour qu'une application puisse lire ce fichier, ou d'autres fichiers, en dehors de sa racine.
Si vous utilisez des conteneurs, il est probable que vous atténuiez déjà de nombreux risques. Il est essentiel de prendre des mesures pour renforcer le conteneur (ne pas l'exécuter en tant que root, etc.). Il est fortement recommandé de retirer tous les privilèges à votre processus web et de limiter ses autorisations de lecture sur le système de fichiers aux seuls fichiers dont il a strictement besoin.
Exemples
Nous allons maintenant vous présenter quelques exemples dans différentes langues afin de vous aider à mieux comprendre les choses pendant qu'elles se déroulent.
C# - Insecure
En ne résolvant pas le chemin complet, ou en s'assurant que vous n'utilisez que la partie nom de fichier d'un chemin, le code est vulnérable au Path Traversal.
var baseFolder = "/var/www/app/documents/" ;
var fileName = "../../../../../../etc/passwd" ;
// INSECURE : Lit /etc/passwd
var fileContents = File.ReadAllText(Path.Combine(baseFolder, fileName)) ;
C# - Sécurisé - canonique
Dans cet exemple, nous nous protégeons contre le détournement de chemin en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu se trouve dans notre dossier de base.
var baseFolder = "/var/www/app/documents/" ;
var fileName = "../../../../../etc/passwd" ;
var canonicalPath = Path.GetFullPath(Path.Combine(baseFolder, fileName)) ;
// SECURE : Rejette toute tentative de lecture en dehors de la base spécifiée.
if(!canonicalPath.StartsWith(baseFolder))
return "Trying to read file outside of base folder" ;
var fileContents = File.ReadAllText(canonicalPath) ;
C# - Sécurisé - nom de fichier
Dans cet exemple, nous nous protégeons contre le "Path Traversal" en ne prenant que la partie "nom de fichier" du chemin, ce qui garantit qu'il est impossible de sortir du dossier spécifié.
var baseFolder = "/var/www/app/documents/" ;
// N'utilisez ceci que si vous n'autorisez pas la navigation dans d'autres sous-dossiers
var fileName = Path.GetFileName("../../../../../etc/passwd") ;
// SECURE : Lit /var/www/app/documents/passwd
var fileContents = File.ReadAllText(Path.Combine(baseFolder, fileName)) ;
Java - Insécurisé
En ne résolvant pas le chemin complet, ou en s'assurant que vous n'utilisez que la partie nom de fichier d'un chemin, le code est vulnérable au Path Traversal.
String baseFolder = "/var/www/app/documents/";
String fileName = "../../../../../etc/passwd";
// INSECURE: Reads /etc/passwd
Path filePath = Paths.get(baseFolder + fileName);
List<String> lines = Files.readAllLines(filePath);
Java - Sécurisé - Canonical
Dans cet exemple, nous nous protégeons contre le détournement de chemin en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu se trouve dans notre dossier de base.
String baseFolder = "/var/www/app/documents/";
String fileName = "../../../../../etc/passwd";
// INSECURE: Reads /etc/passwd
Path normalizedPath = Paths.get(baseFolder + fileName).normalize();
if(!normalizedPath.toString().startsWith(baseFolder))
{
return "Trying to read path outside of root";
}
else
{
List<String> lines = Files.readAllLines(normalizedPath);
}
Java - Sécurisé - Nom de fichier
Dans cet exemple, nous nous protégeons contre le "Path Traversal" en ne prenant que la partie "nom de fichier" du chemin, ce qui garantit qu'il est impossible de sortir du dossier spécifié.
String baseFolder = "/var/www/app/documents/";
// Only use this if you don't allow navigating into other subfolders
String fileName = Paths.get("../../../../../etc/passwd").getFileName().toString();
// SECURE: Reads /var/www/app/documents/passwd
Path filePath = Paths.get(baseFolder + fileName);
List<String> lines = Files.readAllLines(filePath);
Javascript - Insecure
En ne résolvant pas le chemin complet, ou en s'assurant que vous n'utilisez que la partie nom de fichier d'un chemin, le code est vulnérable au Path Traversal.
const fs = require('fs') ;
const baseFolder = "/var/www/app/documents/" ;
const fileName = "../../../../../etc/passwd" ;
// INSECURE : Lecture de /etc/passwd
const data = fs.readFileSync(baseFolder + fileName, 'utf8') ;
Javascript - Sécurisé - Canonical
Dans cet exemple, nous nous protégeons contre le détournement de chemin en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu se trouve dans notre dossier de base.
const fs = require("fs") ;
const path = require("path") ;
const baseFolder = "/var/www/app/documents/" ;
const fileName = "../../../../../etc/passwd" ;
const normalizedPath = path.normalize(path.join(baseFolder, fileName)) ;
// SECURE : Lit /var/www/app/documents/passwd
const data = fs.readFileSync(normalizedPath, 'utf8') ;
Javascript - Sécurisé - Nom de fichier
Dans cet exemple, nous nous protégeons contre le "Path Traversal" en ne prenant que la partie "nom de fichier" du chemin, ce qui garantit qu'il est impossible de sortir du dossier spécifié.
const fs = require("fs") ;
const path = require("path") ;
const baseFolder = "/var/www/app/documents/" ;
const fileName = path.basename("../../../../../../etc/passwd") ;
// SECURE : Lit /var/www/app/documents/passwd
const data = fs.readFileSync(path.join(baseFolder, fileName), 'utf8') ;
Python - Insécurisé
En ne résolvant pas le chemin complet, ou en s'assurant que vous n'utilisez que la partie nom de fichier d'un chemin, le code est vulnérable au Path Traversal.
baseFolder = "/var/www/app/documents/"
fileName = "../../../../../etc/passwd"
# INSECURE : Lit /etc/passwd
fileContents = open(baseFolder + fileName).read()
Python - Sécurisé - Canonical
Dans cet exemple, nous nous protégeons contre le détournement de chemin en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu se trouve dans notre dossier de base.
import os.path
baseFolder = "/var/www/app/documents/"
fileName = "../../../../../../etc/passwd"
normalizedPath = os.path.normpath(baseFolder + fileName)
# SECURE : Rejette toute tentative de lecture de fichiers en dehors du dossier de base spécifié
if not normalizedPath.startswith(baseFolder) :
return "Trying to read out of base folder"
# SECURE : Lit /var/www/app/documents/passwd
fileContents = open(normalizedPath).read()
Python - Sécurisé - Nom de fichier
Dans cet exemple, nous nous protégeons contre le "Path Traversal" en ne prenant que la partie "nom de fichier" du chemin, ce qui garantit qu'il est impossible de sortir du dossier spécifié.