
CVE-2025-8088
Avertissement : Ce contenu est uniquement destiné à des fins éducatives et de recherche. N'utilisez pas ces informations pour des activités malveillantes.
Aujourd'hui, nous allons explorer en profondeur une fascinante vulnérabilité de traversée de chemin, identifiée sous le nom CVE-2025-8088. Cette vulnérabilité permet à un attaquant d'écrire un fichier arbitraire à n'importe quel endroit du système d'une victime lorsqu'elle extrait une archive RAR spécialement conçue. Ce qui rend cette exploitation particulièrement intéressante, c'est son approche « shotgun » pour garantir la livraison du payload, quel que soit l'endroit où l'utilisateur extrait l'archive.
Cet article décompose la technique utilisée dans la preuve de concept Python (PoC) fournie pour fabriquer le fichier .rar malveillant.
L'objectif final de ce PoC est classique et efficace : la persistance. En exploitant la vulnérabilité de traversée de chemin, l'exploit vise à déposer un payload (payload.bat) dans le dossier de démarrage de Windows (AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup). Tout exécutable ou script placé ici s'exécutera automatiquement à la prochaine connexion de l'utilisateur, offrant ainsi à l'attaquant un accès persistant.
Le script Python automatise un processus en plusieurs étapes pour construire le exploit.rar final. Parcourons sa logique.
La première chose que fait le script est de préparer les composants :
payload.bat est créé. C'est le fichier malveillant que nous voulons déposer sur la machine de la victime.PAYLOAD = "@echo off\necho Payload executed!\npause\n"
file1.txt, file2.txt, etc.). Ces fichiers servent à deux fins : rendre l'archive légitime aux yeux de l'utilisateur et, plus important encore, servir de supports pour notre payload caché.def create_files() -> Tuple[List[Path], Path]:
# ...
for i in range(NUM_DEPTHS):
decoy = Path(f"{DECOY_FILE_PREFIX}{i+1}.txt")
" X " * 200) est définie. Cet espace réservé sera intégré aux métadonnées de l'archive, puis remplacé par notre chemin malveillant.C'est un élément clé de la technique. Au lieu de nommer un fichier avec un chemin de traversée, le script utilise une fonctionnalité du système de fichiers NTFS appelée flux de données alternatifs (Alternate Data Streams, ADS). Les ADS permettent de « cacher » des données dans un fichier existant.
Le script attache le payload.bat en tant que flux ADS à chaque fichier leurre. Le nom de ce flux est l'endroit où l'espace réservé est utilisé. En substance, il crée des chemins de fichiers qui ressemblent à ceci pour le système de fichiers :
file1.txt:XXXXXXXXXXXXXXXXXXXX...
Le contenu de payload.bat est maintenant secrètement stocké à l'intérieur de file1.txt.
def attach_ads(decoy: Path, placeholder: str):
"""Attach payload to decoy as Alternate Data Stream"""
ads_path = f"{decoy}:{placeholder}"
with open(ads_path, "wb") as f:
f.write(Path("payload.bat").read_bytes())
Ensuite, le script localise l'outil en ligne de commande Rar.exe sur le système et l'utilise pour créer une archive de base légitime (base.rar). Cette archive contient tous les fichiers leurres, qui transportent désormais secrètement le payload dans leurs flux de données alternatifs.
À ce stade, base.rar est une archive parfaitement valide et non malveillante. WinRAR peut l'extraire sans aucun problème et il créera les fichiers leurres avec leurs noms ADS longs et étranges.
def create_base_rar(rar_exe: str, decoys: List[Path]) -> Path:
# ...
subprocess.run(
f'"{rar_exe}" a -ep -os "{base_rar}" {files}',
# ...
)
C'est là que l'exploit devient ingénieux et résilient. L'attaquant ne sait pas où la victime extraira l'archive. Est-ce que ce sera C:\Users\victim\Downloads ? C:\ ? D:\Temp ?
Pour surmonter cette incertitude, le script génère une liste de différentes chaînes de traversée de chemin, chacune avec une profondeur différente :
..\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.bat..\..\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.bat..\..\..\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\payload.batL'idée est qu'en fournissant plusieurs profondeurs, au moins l'une d'entre elles réussira à naviguer depuis le répertoire d'extraction jusqu'à la racine du système de fichiers (comme C:\) puis vers le dossier de démarrage cible.
def build_relative_paths() -> List[str]:
"""Generate paths with different traversal depths"""
paths = []
for depth in range(1, NUM_DEPTHS + 1):
paths.append(("..\\" * depth) + RELATIVE_DROP_PATH)
return paths
C'est le cœur de l'exploit. Le script ouvre base.rar en mode binaire et lui fait subir une opération chirurgicale.
Il parcourt la structure interne de l'archive, à la recherche de l'en-tête de chaque fichier (file1.txt, file2.txt, etc.). Dans chaque en-tête, il trouve le nom ADS de l'espace réservé (:XXXXXXXXXXXXXXXXXXXX...) et le remplace par l'un des chemins de traversée malveillants générés à l'étape précédente.
file1.txt reçoit le chemin avec ..\.file2.txt reçoit le chemin avec ..\..\.def patch_rar(base_rar: Path, placeholder: str, relative_paths: List[str]) -> Path:
# ...
while pos + 4 <= len(data) and current_file_index < len(target_paths_utf8):
# ...
# Find the placeholder and replace it with the target path
c = patch_placeholder_in_header(hdr, placeholder_utf8, target_utf8)
# ...
Modifier simplement les données de l'en-tête corromprait l'archive. WinRAR utilise une somme de contrôle CRC32 pour vérifier l'intégrité de chaque bloc d'en-tête. Si la somme de contrôle ne correspond pas au contenu de l'en-tête, une erreur sera générée.
Le PoC anticipe cela. Après avoir corrigé un en-tête avec le chemin malveillant, il recalcule la somme de contrôle CRC32 correcte pour la totalité du bloc d'en-tête modifié et réécrit la nouvelle somme de contrôle dans le fichier. Cela permet à l'archive falsifiée de paraître parfaitement valide aux yeux de WinRAR.
def rebuild_all_header_crc(buf: bytearray) -> int:
"""Recalculate CRC checksums for all headers"""
# ...
while pos + 4 <= len(buf):
# ...
# Calculate CRC for the header region
region = buf[block_start + 4:header_end]
crc = zlib.crc32(region) & 0xFFFFFFFF
# Write the new CRC back into the buffer
struct.pack_into("<I", buf, block_start, crc)
# ...