英雄背景无分隔线
指令

Injection - Traversée de trajectoire

Path Traversal est un autre type de vulnérabilité d'injection assez courant. Ils ont tendance à se produire 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 prévu chemin.

Il est important de souligner que la traversée de chemin peut également être considérée comme une vulnérabilité d' *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 global qui a été effectué. Mais avant d'entrer dans le vif du sujet, examinons un exemple pratique rapide de cette vulnérabilité pour voir de quoi nous parlons :

Un aperçu rapide

Envisagez un point de terminaison de votre candidature qui propose des documents, tels que des modèles de contrats ou d'offres d'emploi. Il peut s'agir de fichiers, tels que des PDF, qui sont statiques dans votre application.

Dans ce cas, vous pourriez avoir un bout de code comme celui-ci pour récupérer les fichiers sur demande :

let BaseFolder = « /var/www/api/documents/ » ;
let path = BaseFolder + request.params.filename ;

renvoie file.read (chemin) ;

Afin de démontrer l'évolution 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 à « /var/www/api/».

Nous savons que l'application prend un paramètre « nom de fichier ». Regardons quelques exemples d'entrées et quel en est le résultat :

文件名 未解决的路径 已解决的路径
隐私.pdf /var/www/api/documents/Privacy.pdf /var/www/api/documents/Privacy.pdf
./config/prod.config /var/www/api/documents/../config/prod.config /var/www/api/config/prod.config
./././././././etc/shadow /var/www/api/documents/../../../../etc/shadow /etc/shadow

Remarquez comment nous pouvons parcourir le système de fichiers à l'aide de «.. /». Nous sommes en mesure de quitter le dossier « documents » où se trouvent habituellement les PDF vers le dossier « /etc/ » contenant le fichier « fantôme », qui, sous Linux, contient des hachages de mot de passe. Comme vous pouvez l'imaginer, ce n'est vraiment pas l'idéal.

Consulter Traversal dans les URL

Une autre variante du parcours de chemin peut se produire lors de la création d'URL destinées à interagir avec une API. Supposons que nous ayons une API avec les méthodes suivantes :

URL 模式 说明
/api/v1/order/get/{id} 获取指定 ID 订单的详细信息
/api/v1/order/delete/{id} 删除具有特定 ID 的订单

Une autre application interagit avec l'API qui peut l'appeler, par exemple, lorsque vous essayez d'obtenir des informations sur une commande :

laissez 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 du numéro de commande fourni par l'utilisateur ? Vous trouverez ci-dessous l'URL effective invoquée en fonction de l'entrée fournie.

La canonisation n'est généralement pas effectuée côté client (bien que cela soit possible), mais les serveurs Web canoniseront la demande dans le format indiqué ci-dessous.

订单 ID 号 实际调用的 URL
1 /api/v1/order/get/1
1/.../.../delete/1 /api/v1/order/delete/1

Avec la saisie du deuxième exemple, plutôt que de récupérer la commande portant le numéro d'identification « 1 », nous avons en fait invoqué la méthode delete, ce qui entraîne bien sûr la suppression de la commande.

Atténuations

Lorsque l'on parle de traversée de trajectoire, il existe à la fois des mesures d'atténuation directes et des techniques indirectes/de défense qui peuvent et doivent être appliquées aussi souvent que possible. Voyons d'abord 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 du chemin, ou canonisation du chemin, et son importance.

Lorsque vous avez un chemin tel que '/var/www/api/documents/../../.. /etc/shadow ', il se trouve dans un chemin non canonique. Si vous demandez ce chemin à votre système de fichiers, celui-ci le canonisera en « /etc/shadow ». Il est essentiel que vous n'essayiez pas d'ouvrir des voies non canoniques. Vous devez plutôt d'abord canoniser les chemins, vérifier qu'ils pointent uniquement vers le fichier ou le dossier souhaité, puis les lire.

let BaseFolder = « /var/www/api/documents/ » ;
let path = BaseFolder + request.params.filename ;

let resolvedPath = path.resolve (path) ;

si (! ResolvedPath.StartWith (dossier de base)
return « J'ai essayé de lire en dehors du dossier de base » ;
autre
renvoie file.read (ResolvedPath) ;

Anti-pattern - Tentative de nettoyage des noms de fichiers

Il peut être tentant de faire quelque chose comme ceci :


let BaseFolder = « /var/www/api/documents/ » ;
let path = BaseFolder + request.params.filename.replace («../», « ») ;
...

Toutefois, cette approche devrait pas être utilisé. La clé de la gestion des chemins est de toujours regarder le chemin canonique.

Tant que le chemin canonique n'enfreint aucune règle, la façon dont le chemin est finalement construit ne fait pas vraiment de différence. Essayer de désinfecter un chemin comme celui-ci est très sujet aux erreurs et est rarement sécurisé, 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 le hachage des mots de passe sous Linux. Mais il n'y a vraiment aucune raison pour qu'une application puisse lire ce fichier, ou d'autres fichiers, en dehors de sa racine.

Si vous utilisez des conteneurs, vous réduisez probablement déjà de nombreux risques. Il est essentiel de prendre des mesures pour durcir le contenant (ne pas courir sous forme de racine, etc.). Il est vivement recommandé de supprimer tous les privilèges de votre processus Web et de limiter ses autorisations de lecture sur le système de fichiers aux seuls fichiers dont il a strictement besoin.

示例

Nous allons maintenant partager quelques exemples dans différentes langues pour vous aider à mieux démontrer les choses pendant qu'elles sont en action.

C# - 非安全

En ne résolvant pas le chemin complet ou en vous assurant de n'utiliser que la partie du nom de fichier d'un chemin, le code est vulnérable à Path Traversal.

var BaseFolder = « /var/www/app/documents/ » ;
var FileName = «../../../../.. /etc/mot de passe « ;

//NON SÉCURISÉ : lit /etc/passwd
var FileContents = File.ReadAllText (Path.Combine (BaseFolder, FileName)) ;

C# - Sécurisé - Canonique

Dans cet exemple, nous nous protégeons contre la traversée de chemins en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu du fichier se trouve dans notre dossier de base.

var BaseFolder = « /var/www/app/documents/ » ;
var FileName = «../../../../.. /etc/mot de passe « ;

var CanonicalPath = Path.getFullPath (Path.Combine (BaseFolder, FileName)) ;

//SECURE : Rejette toute tentative de lecture en dehors de la base spécifiée.
si (! CanonicalPath. Commence par (dossier de base)
renvoie « Essayer de lire le fichier en dehors du dossier de base » ;

var FileContents = File.ReadAllText (CanonicalPath) ;

C# - Secure - nom de fichier

Dans cet exemple, nous nous protégeons contre Path Traversal en ne prenant que la partie du chemin contenant le nom de fichier, en veillant à ce qu'il soit impossible de sortir du dossier spécifié.

var BaseFolder = « /var/www/app/documents/ » ;

//À utiliser uniquement si vous n'autorisez pas la navigation dans d'autres sous-dossiers
var FileName = Path.getFileName («../../../../.. /etc/passwd «) ;

//SÉCURISÉ : lit /var/www/app/documents/passwd
var FileContents = File.ReadAllText (Path.Combine (BaseFolder, FileName)) ;

Java - Non sécurisé

En ne résolvant pas le chemin complet ou en vous assurant de n'utiliser que la partie du nom de fichier d'un chemin, le code est vulnérable à Path Traversal.

String BaseFolder = « /var/www/app/documents/ » ;
Nom de chaîne = «../../../../../.. /etc/mot de passe « ;

//NON SÉCURISÉ : lit /etc/passwd
Chemin FilePath = Paths.get (BaseFolder + FileName) ;
<String>Lignes de liste = Files.readAllLines (FilePath) ;

Java - Sécurisé - Canonical

Dans cet exemple, nous nous protégeons contre la traversée de chemins en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu du fichier se trouve dans notre dossier de base.

String BaseFolder = « /var/www/app/documents/ » ;
Nom de chaîne = «../../../../../.. /etc/mot de passe « ;

//NON SÉCURISÉ : lit /etc/passwd
Path NormalizedPath = Paths.get (baseFolder + FileName) .normalize () ;
si (! Chemin normalisé. ToString (). Commence par (BaseFolder)
{
return « Essayer de lire le chemin en dehors de la racine » ;
}
autre
{
<String>Lignes de liste = Files.ReadAllLines (NormalizedPath) ;
}

Java - Secure - Nom de fichier

Dans cet exemple, nous nous protégeons contre Path Traversal en ne prenant que la partie du chemin contenant le nom de fichier, en veillant à ce qu'il soit impossible de sortir du dossier spécifié.

String BaseFolder = « /var/www/app/documents/ » ;

//À utiliser uniquement si vous n'autorisez pas la navigation dans d'autres sous-dossiers
Chaîne FileName = Paths.get («../../../../../.. /etc/passwd «) .getFileName () .toString () ;

//SÉCURISÉ : lit /var/www/app/documents/passwd
Chemin FilePath = Paths.get (BaseFolder + FileName) ;
<String>Lignes de liste = Files.readAllLines (FilePath) ;

Javascript - Non sécurisé

En ne résolvant pas le chemin complet ou en vous assurant de n'utiliser que la partie du nom de fichier d'un chemin, le code est vulnérable à Path Traversal.

const fs = require ('fs') ;

const BaseFolder = « /var/www/app/documents/ » ;
const FileName = «../../../../.. /etc/mot de passe « ;

//NON SÉCURISÉ : lit /etc/passwd
const data = fs.ReadFileSync (BaseFolder + FileName, 'utf8') ;

Javascript - Sécurisé - Canonical

Dans cet exemple, nous nous protégeons contre la traversée de chemins en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu du fichier se trouve dans notre dossier de base.

const fs = require (« fs ») ;
const path = require (« chemin ») ;

const BaseFolder = « /var/www/app/documents/ » ;
const FileName = «../../../../.. /etc/mot de passe « ;

const NormalizedPath = path.normalize (path.join (baseFolder, FileName)) ;

//SÉCURISÉ : 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 Path Traversal en ne prenant que la partie du chemin contenant le nom de fichier, en veillant à ce qu'il soit impossible de sortir du dossier spécifié.

const fs = require (« fs ») ;
const path = require (« chemin ») ;

const BaseFolder = « /var/www/app/documents/ » ;
const FileName = path.basename («../../../../.. /etc/passwd «) ;

//SÉCURISÉ : lit /var/www/app/documents/passwd
const data = fs.ReadFileSync (path.join (baseFolder, FileName), 'utf8') ;

Python - Non sécurisé

En ne résolvant pas le chemin complet ou en vous assurant de n'utiliser que la partie du nom de fichier d'un chemin, le code est vulnérable à Path Traversal.

Dossier de base = « /var/www/app/documents/ »
Nom du fichier = «../../../../.. /etc/mot de passe »

# NON SÉCURISÉ : lit /etc/passwd
Contenu du fichier = ouvert (dossier de base + nom de fichier) .read ()

Python - Sécurisé - Canonique

Dans cet exemple, nous nous protégeons contre la traversée de chemins en résolvant le chemin complet (absolu) et en nous assurant que le chemin résolu du fichier se trouve dans notre dossier de base.

importer os.path

Dossier de base = « /var/www/app/documents/ »
Nom du fichier = «../../../../.. /etc/mot de passe »

chemin normalisé = os.path.normpath (dossier de base + nom de fichier)

# SECURE : Rejette toute tentative de lecture de fichiers en dehors du dossier de base spécifié
si ce n'est pas NormalizedPath.StartsWith (BaseFolder) :
return « Essayer de lire à partir du dossier de base »

# SÉCURISÉ : lit /var/www/app/documents/passwd
Contenu du fichier = ouvert (chemin normalisé) .read ()

Python - Secure - Nom de fichier

Dans cet exemple, nous nous protégeons contre Path Traversal en ne prenant que la partie du chemin contenant le nom de fichier, en veillant à ce qu'il soit impossible de sortir du dossier spécifié.

importer os.path

Dossier de base = « /var/www/app/documents/ »
Nom de fichier = os.path.basename («../../../../.. /etc/mot de passe «)

# SÉCURISÉ : lit /var/www/app/documents/passwd
FileContents = open (os.path.join (baseFolder, FileName)) .read ()