Obscure
Dans Obscure, un attaquant a déposé un webshell PHP sur un serveur Apache grâce à une faille d'upload. On dispose du fichier et d'une capture réseau des deux minutes précédant l'arrêt du service.
Regarder les fichiers fournis
Après extraction de l'archive je retrouve trois fichiers :
19-05-21_22532255.pcap
support.php
to-do.txt
support.php contient le webshell et le PCAP ses échanges réseau. to-do.txt reprend le scénario.
file 19-05-21_22532255.pcap
La capture est bien un PCAP avec des paquets au format Linux cooked. Le PHP est beaucoup moins lisible. Il contient plusieurs morceaux de chaînes et une suite de str_replace avant de construire une fonction et de l'exécuter.
Retrouver les échanges dans Wireshark
Un premier filtre isole les requêtes HTTP :
http.request
Pour écarter les requêtes vers les images et le JavaScript du site, je cible les accès au webshell :
http.request.uri contains "support.php"
Quatre POST vers /uploads/support.php relient 23.129.64.207 à 10.132.0.2.
Le premier échange se trouve dans le flux tcp.stream eq 1.
Le serveur répond 200 OK, mais les corps HTTP sont encodés. L'analyse de support.php doit permettre de retrouver leur contenu.
Comprendre le webshell PHP
Dans support.php, les variables $V, $d, $P, $c et $B contiennent les morceaux du programme. Ils sont concaténés puis la chaîne u) est supprimée :
$u = str_replace('u)', '', $V.$d.$P.$c.$B);
Une autre substitution reconstruit create_function, qui sert ensuite à exécuter le code assemblé.
Pour afficher le programme reconstruit sans l'exécuter je reproduis uniquement la concaténation et le remplacement en Python :
import re
from pathlib import Path
php = Path("support.php").read_text()
parts = dict(re.findall(r"\$(V|d|P|c|B)='([^']*)';", php))
code = "".join(parts[k] for k in ["V", "d", "P", "c", "B"])
print(code.replace("u)", "").replace(";", ";\n"))
Le résultat fait apparaître les valeurs qui vont servir au décodage :
$k = "80e32263";
$kh = "6f8af44abea0";
$kf = "351039f4a7b5";
$p = "0UlYyJHG87EJqEz6";
$k est la clé utilisée par la fonction XOR. $kh et $kf sont les marqueurs qui encadrent les données utiles. $p est un préfixe ajouté aux réponses.
La partie importante du traitement reçu est celle-ci :
@eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));
Le traitement enchaîne un décodage Base64, un XOR avec la fonction x puis une décompression zlib. Le résultat est exécuté par eval.
La clé XOR est répétée sur toute la longueur du message. Elle est présente dans le PHP, ce qui permet de décoder le trafic.
Les réponses prennent le chemin inverse. Le webshell capture la sortie, la compresse, applique le XOR puis l'encode en Base64 avant de l'entourer avec ses marqueurs. La même routine de décodage permet donc de lire les requêtes et les réponses.
Décoder les commandes du PCAP
J'utilise TShark pour extraire les corps HTTP avec leur numéro de trame et leur IP source :
tshark -r 19-05-21_22532255.pcap -Y http.file_data \
-T fields -e frame.number -e ip.src -e http.file_data
Je récupère le contenu entre les deux marqueurs puis je reproduis le décodage du PHP. Voici les opérations appliquées à chaque message :
payload += b"=" * (-len(payload) % 4)
data = base64.b64decode(payload)
key = b"80e32263"
data = bytes(value ^ key[i % len(key)] for i, value in enumerate(data))
plain = zlib.decompress(data)
Les = complètent le padding Base64 lorsqu'il manque. Le XOR utilise la clé répétée sur toute la longueur du message puis zlib.decompress retrouve le texte d'origine.
Vérifier l'utilisateur courant
La première requête contient :
chdir('/var/www/html/uploads');@error_reporting(0);@system('id 2>&1');
La commande id renvoie ce résultat dans la trame 22 :
uid=33(www-data) gid=33(www-data) groups=33(www-data)
La réponse confirme que les commandes tournent sous le compte www-data.
Chercher des fichiers dans les dossiers personnels
La requête suivante lance :
ls -lah /home/* 2>&1
La réponse révèle le dossier personnel de developer et un fichier intéressant :
-rw-r--r-- 1 developer developer 1.6K May 21 20:37 pwdb.kdbx
C'est une base KeePass. Ses permissions autorisent la lecture par les autres utilisateurs, donc www-data peut la récupérer même si elle appartient à developer.
La troisième requête change le dossier courant vers /home/developer et affiche le chemin. La réponse confirme :
/home/developer
Récupérer la base KeePass
La dernière commande est :
base64 -w 0 pwdb.kdbx 2>&1
La base est exfiltrée en Base64 dans la réponse du webshell, sans retours à la ligne avec -w 0.
La réponse se trouve dans la trame 464. Une fois le message du webshell décodé, il reste le fichier lui-même en Base64. Je décode cette dernière couche et j'enregistre les octets dans work/pwdb.kdbx.
Je vérifie le fichier reconstruit :
file work/pwdb.kdbx
Retrouver le mot de passe de la base
La base est chiffrée. Je tente de retrouver son mot de passe maître avec John.
J'utilise keepass2john pour extraire les paramètres nécessaires à une attaque hors ligne. Sur mon Mac, John est installé avec Homebrew :
"$(brew --prefix)/share/john/keepass2john" work/pwdb.kdbx \
> work/keepass.hash
Je tente le dictionnaire password.lst fourni avec John :
john --session=work/keepass \
--wordlist="$(brew --prefix)/share/john/password.lst" \
--pot=work/john.pot work/keepass.hash
John retrouve chainsaw dans le dictionnaire. Pour réafficher le résultat :
john --show --pot=work/john.pot work/keepass.hash
Ouvrir la base et récupérer le flag
J'ouvre la base avec KeePassXC et le mot de passe chainsaw :
cd work
open -a KeePassXC pwdb.kdbx
Le groupe Passwords contient une seule entrée, Flag, associée à l'utilisateur artikrh.
Le flag se trouve dans le champ mot de passe, masqué sur la capture.
L'entrée contient aussi un lien vers le projet epinna/weevely3. Il permet de rattacher le webshell à Weevely après l'analyse de son fonctionnement.
Je soumets le flag sur HackTheBox et le challenge est validé.
Ce que je retiens
Le PHP donnait la méthode pour décoder le trafic. En l'appliquant aux échanges HTTP, j'ai pu suivre les commandes de l'attaquant et reconstruire la base KeePass qu'il avait récupérée.
La faille d'upload donne accès au serveur sous www-data. Les permissions de la base permettent ensuite de la lire et son mot de passe se retrouve dans un dictionnaire courant.




