Fournit directement un chemin absolu du système de fichiers (/etc/passwd) quand l'application concatène l'entrée utilisateur sans préfixe de répertoire de base.
TL;DR
/etc/passwd directement — contourne les filtres ../ sans aucune séquence de traverséeos.path.join("/uploads/", "/etc/passwd") retourne /etc/passwd — la base est silencieusement abandonnéeurljoin abandonne l'URL de base quand le chemin commence par /, créant du SSRFrealpath — ne jamais filtrer l'entrée avant le join, valider la sortie résolueL'injection de chemin absolu (CWE-36, un enfant de CWE-22) exploite une propriété comportementale des fonctions de jointure de chemin dans plusieurs langages : quand l'entrée utilisateur commence par un indicateur de chemin absolu (/ sur Unix, C:\ sur Windows), le langage abandonne tout répertoire de base précédemment spécifié et traite l'entrée utilisateur comme le chemin complet.
L'attaque ne nécessite aucune séquence ../. Un filtre qui bloque avec succès toutes les formes de traversée relative — y compris toutes les variantes d'encodage — reste entièrement exposé s'il permet à l'entrée utilisateur de commencer par /. Du point de vue de l'attaquant, c'est le payload le plus simple possible : ?file=/etc/passwd. Du point de vue du défenseur, c'est la variante la plus fréquemment manquée parce que les développeurs supposent qu'un chemin ne contenant pas .. est sûr.
Sous OWASP A01:2021, c'est un échec de contrôle d'accès rompu au niveau de la construction du chemin. Elle se distingue du path traversal de base (CWE-22) par sa cause racine : la traversée relative exploite une canonicalisation insuffisante, tandis que l'injection absolue exploite la sémantique documentée mais surprenante de la jointure de chemin du runtime du langage.
Le comportement de os.path.join en Python est l'exemple canonique de cette classe :
import os
# Intention du développeur : restreindre l'accès aux fichiers au répertoire /uploads/
BASE = "/var/www/uploads"
def vulnerable_open(user_input):
# VULNÉRABLE : os.path.join abandonne BASE quand user_input commence par /
path = os.path.join(BASE, user_input)
return open(path, "rb")
# Résultat de os.path.join("/var/www/uploads", "/etc/passwd")
# → "/etc/passwd" (BASE est complètement abandonné)
print(os.path.join("/var/www/uploads", "/etc/passwd"))
# /etc/passwd# Même comportement avec pathlib de Python
from pathlib import Path
base = Path("/var/www/uploads")
result = base / "/etc/passwd"
print(result)
# /etc/passwdpath.resolve de Node.js a un comportement équivalent :
const path = require('path');
// path.resolve abandonne tous les arguments précédents quand un chemin absolu est rencontré
console.log(path.resolve('/var/www/uploads', '/etc/passwd'));
// /etc/passwd
// path.join NE se comporte PAS ainsi — il normalise la chaîne complète
console.log(path.join('/var/www/uploads', '/etc/passwd'));
// /var/www/uploads/etc/passwd (note : toujours dangereux si non validé)urllib.parse.urljoin expose la même classe de vulnérabilité dans la construction d'URLs :
from urllib.parse import urljoin
# Quand le second argument commence par /, le chemin du premier URL est remplacé
print(urljoin("https://api.internal/v1/reports/", "/etc/passwd"))
# https://api.internal/etc/passwd
# Attaque dans un contexte de proxy microservice
base_url = "https://internal-api.corp/api/v2/"
user_path = "/admin/users"
# Résultat : https://internal-api.corp/admin/users
# Le préfixe /api/v2/ est silencieusement abandonnéGET /proxy?path=/admin/users HTTP/1.1
Host: app.example.com
HTTP/1.1 200 OK
Content-Type: application/json
{"users": [...tous les utilisateurs incluant les admins...]}| Variante | Payload | Runtime du langage | Impact |
|---|---|---|---|
| Chemin absolu Unix | /etc/passwd | Python, PHP, Ruby | Lecture du fichier de credentials |
| Proc environ | /proc/self/environ | Python, PHP | Secrets d'exécution — clés cloud, URLs DB |
| Absolu Windows | C:\Windows\win.ini | PHP sur Windows | Confirmation du système de fichiers |
| Config web | C:\inetpub\wwwroot\web.config | ASP.NET sur IIS | Chaînes de connexion DB, secrets app |
| SSRF urljoin | /admin/reset via urljoin(base, user_input) | Proxies Python requests | Accès API interne |
| Contournement pathlib | Opérateur / de pathlib avec composant absolu | Python 3.x | Identique à os.path.join |
| Contournement File Java | new File("/uploads", "/etc/passwd") | Java (rare) | Chemin absolu — dépend de l'implémentation |
Sous Linux, les cibles les plus impactantes accessibles via un chemin absolu sans nécessiter des privilèges élevés :
/etc/passwd → énumération d'utilisateurs
/proc/self/environ → secrets d'exécution (clés cloud, URLs DB, tokens API)
/proc/self/cmdline → identification du processus en cours
/proc/version → version du noyau pour sélection d'exploit
/app/.env → tous les secrets de l'application en un seul fichier
/home/ubuntu/.ssh/id_rsa → clé privée SSH
/home/ubuntu/.aws/credentials → fichier de credentials AWS
/var/run/secrets/kubernetes.io/serviceaccount/token → JWT KubernetesSous Windows, quand l'application tourne sous IIS :
C:\Windows\win.ini → marqueur de confirmation d'accès au FS ([fonts])
C:\inetpub\wwwroot\web.config → chaînes de connexion ASP.NET, clés secrètes
C:\Windows\System32\drivers\etc\hosts → noms d'hôtes internesCVE-2026-32871 — FastMCP OpenAPIProvider (CVSS 9.8) : le fournisseur OpenAPI de FastMCP utilisait urllib.parse.urljoin(base_url, user_path) pour construire des URLs pour le proxying d'API interne. Quand le chemin fourni par l'utilisateur commençait par /, urljoin abandonnait le composant de chemin de l'URL de base. Un attaquant authentifié envoyait des requêtes avec des chemins commençant par / pour rediriger les requêtes HTTP sortantes du serveur vers des endpoints internes arbitraires — combinant injection de chemin absolu et SSRF. Les requêtes transférées incluaient les en-têtes d'authentification de la requête originale, accordant l'accès aux services internes protégés.
CVE-2023-50164 — Apache Struts2 File Upload (CVSS 9.8) : les versions Apache Struts2 2.0.0 à 6.3.0 étaient vulnérables au path traversal dans les paramètres d'action d'upload de fichier. Les attaquants manipulaient la casse du paramètre de chemin d'upload pour réaliser une écriture de fichier absolue effective — écrivant des webshells JSP en dehors du répertoire d'upload prévu. L'exploitation non authentifiée réalisait une exécution de code à distance. La vulnérabilité affectait toute application Struts2 utilisant la fonctionnalité d'upload de fichier.
CVE-2023-34362 — MOVEit Transfer (CVSS 9.8) : l'exploitation massive de MOVEit Transfer par le groupe ransomware Cl0p combinait une injection SQL avec un composant de path traversal qui permettait une lecture de fichier arbitraire. L'élément de path traversal utilisait des spécifications de chemin absolu pour cibler des fichiers de configuration en dehors du webroot de l'application, permettant un vol massif de données affectant des centaines d'organisations incluant des agences gouvernementales et des institutions financières.
/etc/passwd directement — sans séquences de traversée — et vérifier la présence de root:x:0:0: dans le corps de la réponse.../, ce test révèle spécifiquement si l'entrée de chemin absolu est également filtrée.C:\Windows\win.ini et rechercher [fonts] dans la réponse./ pour remplacer le chemin cible./var/run/secrets/kubernetes.io/serviceaccount/token — si retourné, l'application tourne dans un pod Kubernetes avec injection de token de compte de service, et le JWT fournit un accès à l'API du cluster./ et comparer les tailles de réponse avec des valeurs de test qui ne commencent pas par /.BreachVex inclut "/{file}" comme variante de payload dédiée à l'absolu dans son jeu de payloads de traversée, spécifiquement pour tester l'injection de chemin absolu séparément de la traversée relative. La validation par porte de contenu applique la même vérification à trois critères : HTTP 200, longueur du corps ≥ 10 octets, et correspondance regex du marqueur spécifique au fichier.
import os
BASE_DIR = os.path.realpath("/var/www/app/uploads")
def safe_open(user_input: str) -> bytes:
# Join d'abord, puis résolution — neutralise à la fois l'injection relative et absolue
full_path = os.path.join(BASE_DIR, user_input)
resolved = os.path.realpath(full_path)
# os.path.realpath sur os.path.join("/uploads", "/etc/passwd")
# → full_path = "/etc/passwd" (join abandonne la base)
# → resolved = "/etc/passwd"
# → la vérification startsWith ÉCHOUE → exception levée
if not resolved.startswith(BASE_DIR + os.sep):
raise PermissionError("Injection de chemin absolu bloquée")
with open(resolved, "rb") as f:
return f.read()// Node.js — path.resolve puis valider
const path = require('path');
const fs = require('fs');
const BASE = fs.realpathSync('/var/www/uploads');
function safeRead(userInput) {
// path.resolve gère à la fois les composants absolus et les séquences ..
const resolved = path.resolve(BASE, userInput);
const real = fs.realpathSync(resolved);
if (!real.startsWith(BASE + path.sep)) {
throw new Error('Accès refusé : tentative d\'injection de chemin absolu');
}
return fs.readFileSync(real);
}Filtrer / du début de l'entrée utilisateur n'est pas suffisant. Certaines applications ne suppriment que les slashs initiaux, manquant les cas où le slash apparaît après encodage (%2f), ou quand le chemin absolu est construit différemment. La défense correcte est de valider la sortie résolue — pas d'assainir l'entrée.
from urllib.parse import urljoin, urlparse
INTERNAL_BASE = "https://internal-api.corp/api/v2/"
def safe_proxy(user_path: str) -> str:
# Rejeter les chemins commençant par / pour éviter le remplacement de base urljoin
if user_path.startswith("/"):
raise ValueError("Chemin absolu non autorisé dans le paramètre proxy")
# De plus : liste blanche des préfixes de chemin
allowed_prefixes = ["reports/", "documents/", "exports/"]
if not any(user_path.startswith(p) for p in allowed_prefixes):
raise ValueError("Chemin absent de la liste des préfixes autorisés")
return urljoin(INTERNAL_BASE, user_path)Remplacer les paramètres de chemin contrôlés par l'utilisateur par des identifiants opaques qui mappent vers des chemins côté serveur :
import uuid
from typing import Optional
# Registre de fichiers : UUID → chemin réel
FILE_REGISTRY: dict[str, str] = {}
def register_file(real_path: str) -> str:
file_id = str(uuid.uuid4())
FILE_REGISTRY[file_id] = real_path
return file_id
def get_file(file_id: str) -> Optional[bytes]:
path = FILE_REGISTRY.get(file_id)
if path is None:
return None
with open(path, "rb") as f:
return f.read()L'injection de chemin absolu (CWE-22, CWE-36) survient quand une application passe directement l'entrée utilisateur à une fonction du système de fichiers sans préfixer un répertoire de base fixe, ou quand la fonction de jointure de chemin du langage abandonne silencieusement la base si l'entrée utilisateur commence par /. L'attaquant fournit un chemin complet comme /etc/passwd sans avoir besoin de séquences de traversée ../.
Le comportement de Python os.path.join est documenté mais surprenant : si un composant est un chemin absolu, tous les composants précédents sont abandonnés. os.path.join('/uploads/', '/etc/passwd') retourne '/etc/passwd', pas '/uploads//etc/passwd'. Un filtre qui supprime les séquences ../ mais autorise / au début de l'entrée utilisateur est en échec face à cette variante.
Le path traversal de base utilise des séquences ../ pour naviguer vers le haut depuis un répertoire de base contraint. L'injection de chemin absolu saute la base entièrement en fournissant un chemin complet. Un filtre côté serveur qui bloque ../ mais accepte / au début de l'entrée utilisateur est vulnérable à l'injection absolue mais pas à la traversée de base. Les deux nécessitent des tests séparés.
CVE-2023-50164 (Apache Struts2, CVSS 9.8) utilisait la manipulation de casse dans les paramètres d'upload pour réaliser une traversée de chemin équivalente à une écriture de fichier absolue. CVE-2026-32871 (FastMCP, CVSS 9.8) exploitait le comportement de urllib.parse.urljoin — quand le chemin fourni par l'utilisateur commence par /, l'URL de base est abandonnée, dirigeant les requêtes du serveur vers des chemins contrôlés par l'attaquant via SSRF. CVE-2023-34362 (MOVEit, CVSS 9.8) chaînait une injection SQL avec un path traversal pour accéder à des fichiers arbitraires.
CWE-36 (Absolute Path Traversal) est l'identifiant spécifique pour cette variante. C'est un enfant de CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) dans la taxonomie MITRE. Les deux CWE sont pertinents lors du catalogage des findings.
Soumettre /etc/passwd directement comme valeur du paramètre (sans aucune séquence ../). Vérifier la présence de root:x:0:0: dans la réponse. Sur Windows, essayer C:\Windows\win.ini et rechercher [fonts]. Tester aussi /proc/self/environ — la confirmation est toute paire clé=valeur comme PATH= ou DATABASE_URL=. Si les payloads ../ échouent mais que /etc/passwd réussit, l'application filtre les séquences de traversée mais pas les chemins absolus.
path.join() dans Node.js n'abandonne pas les composants précédents pour les chemins absolus — il normalise la chaîne jointe complète. Cependant, path.resolve() suit le comportement de type Python et retourne le chemin absolu du dernier composant absolu. Les applications utilisant path.resolve(BASE, userInput) où userInput commence par / sont vulnérables. Toujours valider le résultat de path.resolve() avec startsWith(base + path.sep).
urllib.parse.urljoin(base_url, user_path) abandonne tout dans base_url après le dernier / quand user_path commence par /. Cela signifie que urljoin('https://api.internal/v1/', '/etc/passwd') produit 'https://api.internal/etc/passwd' — ignorant le préfixe /v1/. Dans les architectures de microservices où les APIs internes proxient des chemins fournis par l'utilisateur, cela crée simultanément du SSRF et un path traversal.